From Rails to Clojure, Then to Java, Then Back to Rails
engineering-management.space
engineering-management.space
Focus on strong object oriented programming
What I've learned from Ruby is to favor functional programming concepts - minimize side effects, isolate state mutation, etc. Ruby is definitely OO, but when my Ruby code is more functional it's less buggy, easier to understand and move around, and generally less convoluted.All those methods, and reactive patterns (Observer and such) were already present in Smalltalk-80.
a = (1..1000).lazy.select(&:even?).map{|i| i*2 }.map{|i| i + 1 }.map(&:to_s).map(&:reverse)
There is no benefit from lazy if you do `a.join(",")`, since that iterates over the entire sequence. But if you do `a.take(10)` or `a.detect{|s| s == "14" }`, the chain will only be executed for each item in turn until 10 elements are produced or an element is the string "14", respectively.Ruby's #1 problem, for me at least, is that your small project someday runs into a performance wall. I don't know the latest benchmarks, but last I recall Python was about 8x faster than Ruby when both are being interpreted. Yes, there's JRuby, but that's not something you can drop into a new system and do useful things with immediately (without more setup).
And with Clojure, you get all the elegance and lovely collection manipulation tools you can possibly want, much faster performance, and a huge stable pile of Java libraries (compared to Ruby).
So with Python and Clojure as one's main tools, life is quite nice.
Granted, Python is faster for tasks like those Numpy solves, but in arbitrary execution performance, python is just slower. 8x has NEVER been true. That must have been you doing some really bad stuff in one of them but not both.
For sure, Ruby was slow for what we were doing... processing millions of rows of data was so slow that it caused me to decide to try Go (and the same Go program was hundreds of times faster). But I didn't try Python then.
The lazy version basically turns into
a = ""
(1..1000).each { |i|
next if i.odd?
i *= 2
i += 1
a << i.to_s.reverse + ", " # ya ya, trailing comma
}
Where as the non lazy version turns into: odds = []
(1..1000).each { |i| odds << i if i.even? }
doubled = []
odds.each { |o| doubled << o * 2 }
incremented = []
doubled.each { |d| incremented << d + 1 }
strs = []
incremented.each { |i| i.to_s }
a = ""
strs.each{ |s| a << s.reversed + ", " }
Which means that you have you went from N iterations to 5*N iterations, with 5 intermediate arrays in this case.https://gist.github.com/mboeh/bb480d93c71046e23816f2c24c23e3...
When applied to the same amount of work, lazy iterators consume more memory and take more CPU time than the equivalent non-lazy chained iteration.
There's a place for lazy iterators, but you should pretty much always profile first. If you have performance problems due to a long chain of iterators, you're always going to get better results by merging some of those iterators into a single block than by making the whole chain lazy. The best use case for lazy iterators is if you're trying to avoid having the original collection in memory (if it's being streamed from a file or the like). And at that point, if you're trying to optimize for performance, you should avoid chained iterations, period.
The good news is that in the vast majority of cases this just doesn't matter. Chaining iterators works fine until you start running into performance problems.
I'm about half way done. When I'm done, I'll post it on HN, but if you are interested here is what I've got so far: https://github.com/ygt-mikekchar/oojs/blob/master/oojs.org If anyone ends up reading it, I'm very happy to receive criticism, so feel free to leave an issue.
Its not about what is possible, we're all familiar with the turing completeness of everything. It's about what is: the default + easy + encouraged in the ecosystem.
I'm run into the downside, though, that Ruby isn't optimized for immutable operations - tons of object instances created. I've needed to 'pythonize' my Ruby code in the worst cases - using mutable functions to dramatically reduce memory usage.
Ruby actually isn't too bad if you don't use Rails. Between Rails and stuff like Spring over in Java-land I've developed a strong hatred of huge frameworks :)
Clojure, however, is the one language that just 'clicked' for me almost immediately. In fact, I'm pretty certain I'll be sticking to Lisps as much as possible from here on out. That is, when I have the choice: I work in a Java shop during the day like everyone else :O
I have used and deploy Go/Elixir/Clojure to some certain success. One thing I feel is that no code is as beautiful as Ruby code to my own eye.
What make Rails bad in your opinion? Is its performance? I think Rails is just a framework with many utility/helper to help you out.
Every framework has its downsides. Personally I think rails is nice (I am very productive with it).
Yes, I come into lots of issue with Spring autoloader to the point I have a zsh alias call `kill_rails` to make sure it kills all the rails and spring instances :(.
That's being said, Spring is an external tool to help solve the slowness of booting up Rails app. I have work in Java/Python and they are slow to bootup too just depend on how big the project is.
Tool like: https://github.com/Shopify/bootsnap help to speed up this as well.
Other option to look at: https://github.com/danielpclark/faster_path
These solves the issue of scanning path, address slowness in a different way compare to preload mechanism of Spring.
Over on Reddit[0] one of the problems for newbies to Clojure is not knowing what to use for web development.
https://www.reddit.com/r/Clojure/comments/858g9z/what_clojur...
There's a trade off there though, rails gives you so much, you almost forget what the problems are with slapping different libraries together.
Definitely room for a "blessed" path to web dev in clojure.
Even in my local area (not a tech hub), I'm aware of one small software shop using Clojure, and they have no trouble finding people.
I felt so strongly about not wanting to go "backwards" anymore and use Go/Scala/Java etc I started my own company so I could dictate the technology decisions, so naturally I used Clojure!
I still use Javascript in most of the UX because ClojureScript I felt wasn't ready for primetime production but we'll be phasing that out over the next few years into ClojureScript.
No fiddling with tooling, the entry barrier can't be lower. With CLJS it's always figuring out the latest versions, trying to get Figwheel off the ground is at least a day worth of work for me. What was piggieback again and why don't I have it? Which profiles are loaded implicitly by lein, should I use figwheel-sidecar? Then add building your app to be reload friendly (even though by default it's a good practice)
There's just so much going on even after having built 3-4 projects with CLJS and using the same stack, I am still struggling.
Once you get through the first few days of battling with libs it's breeze.
That's where Js gets behind. Redux / React is all about transforming and shaping your data. You end with lots of boilerplate. Even though ES6 is drastically better with syntactic sugar for manipulating data, it's still far behind Lisp nature of data transforming.
It's a tough choice nowadays. I want to get moving fast and feel strongly inclined towards sticking to Js.
"Clojure: considering the difference between being easy and being simple. Clear difference between data and logic that changes that data. Distinction between pure functions and functions with side effects, try to separate them. Let’s try to make simple code by default."
"Java: performance, concurrency and strictness."
Maybe they mean they were forced to think about concurrency in Java?
I don't think any reasonable person would argue that concurrency is easier in Java than in Clojure.
Of course, idiomatic Clojure might require less mindshare to make highly performant, concurrent code...
The best Rails codebases are the ones who have ample "vanilla" Ruby and just happen to use Rails alongside said Ruby.
I've started working on a Rails project. I don't have any professional experience with Rails in particular.
Can you recommend any code bases I can read that meet these criteria?
Form objects aren’t very common, but I’ve found it to be a really great patten for simplifying code. Strong params and ActiveRecord methods are fine for simple updates, but as soon as you get to something more complicated (e.g. what if you want validations to apply only for certain users?) it just becomes a mess - and you need to decide whether to have fat controllers or models.
The idea is you put all the logic for parsing params and updating records into a form object. It keeps controllers and models skinny, and can easily be tested without having to go through a controller. The reform gem provides a few helpers that make them easy:
The beauty of `&` is awesome and I discover that Elixir/Clojure has that and bring it to next level. I think Ruby has inspired many other language to take that path too.
One of the thing about Ruby is that it optimize for happiness, which sound weird. But I really feel happy to write Ruby code. I feel like the language really care about me.
Who don't want to have stuff like this:
``` 10.seconds.from_now ```
Or just call method without `()`, and without even wrapping hash in `{}`.
Of course, lots of these are do-able in Lisp and I like Clojure. But I think the flexibility Ruby gives us is under estimated.
*> 10.seconds.from_now*
I don't understand this kind of weird "urban sprawl OOP" where classes are extended with anything.Why is 'seconds' a method on numbers? Is there also a 'pixels' method so that I could draw a line like this:
10.pixels.from.top_left_corner.to.southeast.using.red.stroke
If this doesn't seem like a sensible way of programming graphics, why does the 10.seconds thing get a pass?But this is what drives me up the wall with the Ruby ecosystem: seems that whenever API designers had the choice between consistency and fun gimmick, they chose the latter. It makes some people feel happy, but often makes the next person who has to read and maintain the code miserable.
10.seconds.from_now
is beautiful to me.
Let's do a quick google for "Fluent API in Java" and you will get something like this:
Invoice inv = Invoice.builder() .createdOn(new Date()) .withDocumentNumber("5150") .shippingOnTrailer("2112") .suppliedBy(InvoiceActor.builder() .named("101") .as(InvoiceActorType.DC) .build()) .beingSentTo(InvoiceActor.builder() .named("42") .as(InvoiceActorType.STORE) .build()) .with(InvoiceItem.builder() .as("TK421") .orderedQuantity(10) .shippedQuantity(8) .build()) .with(InvoiceItem.builder() .as("FN2187") .orderedQuantity(5) .shippedQuantity(5) .build()) .build();
The only difference here is that Ruby allow you to attach method to anything.
But then you have category in Objective C, extension in Swift/Kotlin.
The language gives you the tool, to make your job easier, that's why I wrote "I feel happy when writing Ruby" because I have that power.
To even protected you from changing everything, you can also use refinement to scope this, though it seems underestimated currently IMHO.
When using it the right way, it helps everyone write readable code.
Back to your example:
> 10.pixels.from.top_left_corner.to.southeast.using.red.stroke
It's up to your however you want to design it.
irb(main):002:0> 10.methods.count
=> 133
Oh well...What I need to know is if you can think critically, not if you already know what we plan to teach.
There's some penalty in ramp-up time, but I find it more than pays for itself down the road.
My new business is built on Rails for a very long list of reasons, not the least of which is that both Ruby is quite a nice language, and Rails is actually quite good for back-office/lo-fi/quick-and-dirty, server-side-rendered UX with relatively low web traffic.
(Yes, several of those are implemented as reader macros. I don't see how that would make them any easier to learn than the same feature implemented as "syntax".)
That's not a knock against it. I think Clojure is the best general-purpose language today, but we've come a long way in the past few decades. Lisp is no longer a language whose implementation fits on a blackboard (and everyone extends it in their own way). We've discovered a bunch of common functionality that almost all programs need, and pushed it up into the stdlib and language.
That's not true. Lisp has lots of syntax - actually more than most programming language, because the syntax is arbitrarily extensible via macros.
The idea of (operator param param ...) is only valid for function calls, but even then the syntax is slightly more complex, since several Lisps have keyword arguments.
But besides (operator param param ...) Lisp also has:
((lambda (arg ...) . body) param ...)
(special-operator ...)
and
(macro-operator ...)
Each special operator comes with syntax. Example is LET.
LET is not
(LET param param)
but
(let (arg ...) declarations . body)
where arg is arg | (arg value)
declaration the is something complex like
(declare (type (integer 0 3) arg))
for a type declaration for the variable arg, which is an integer in the range of 0 to 3.
Systems don't always/often win because they're the best or most liked.
As with political views, most people prefer the ones they first learned. Only later in life, perhaps after becoming dissatisfied with what they know, they may risk trying something very different.
My CS education was the typical Pascal/C/C++, so it wasn't decades later until I actually learned a Lispy language. But I had read enough to know that a lot of very intelligent people had loved Lisp; surely there was something to that.
Likewise, the people who grew up on Lisp probably only grudgingly moved _down_ the ladder to use some of the more popular languages ;).
I'm surprised by this, to me the language used would be a long way down the list when evaluating an employer.
I really think Pedro (the blog post writer) has an excellent point about being a polyglot but trying to qualify as a Senior Engineer except I'm older and have even more platforms & languages behind me. Its a serious problem because I have the experience to easily be a Sr. Engineer or manager but my experience isn't concentrated long enough anywhere to make me useful enough to warrant the role.
The only good option being JRuby, as far as I understand. I am not at all into Ruby.
Regarding being polyglot and seniority, a possible solution is to work for consulting companies that focus on multiple stacks.
Because MRI Ruby is a threaded VM, it's easy to replace jumping to an instruction which dispatches to the next instruction, with jumping to some native code which dispatches to the next instruction.
Right now, AFAIK, only very basic optimizations are enabled but just removing the overhead of VM dispatch between each instruction is a huge performance improvement.
Besides, some languages give you vastly different career prospects (industries you can work in, salaries/rates, environment, etc.)
Years back, I've had people tell me they stopped using Rails because it was too hard to find good problem solvers. That didn't diminish the continued success of the framework.
As an antidote to the comment, I was recently at a Ruby meetup. Everyone was on Rails and everyone was younger.
It has often been to my advantage to be willing to learn a new language for a new project or a new job, giving me opportunities I wouldn't otherwise have had. I learned Oracle PL/SQL to integrate a 7-8 figure Rails app into an accounting system when nobody else volunteered. I learned Rails to get into ecommerce. I learned PHP to become a startup's only web developer so I could write cool audio processing tech in Ruby. I learned Ruby to speed web development in my own startup. I learned Scala to get into the San Francisco market. JS. Java. Kotlin. POSIX shell. C++. C. Etc.
Unless a developer/engineer just wants to be a cog forever, I highly recommend some flexibility on language. Solving real problems is way more interesting and more useful than being a framework stickler.
Edit: I guess I just hang out with like minded people, but our biases shouldn't negate the fact that this is a very common feeling where I am.
(Most prominent -> least prominent)
Web-related: Go, Elixir, JavaScript, Python
Desktop GUI: Python, C++
Game-Dev: C#, C++
Really its a matter of how much you'll pay us, and I think we've been scarred by too many bloated Rails apps and Wordpress sites to want to work on those.
I honestly thought Django was mostly dead now.
When that job went away, I thought I could get a job using something more modern. However, the only job I was able to land was a new development project in Ada2005 (actually it was a port from SPARC to x64 and a partial rewrite of the same legacy application I had been maintaining for 3 years).
It took me forever to find a company that would "take a chance" on me - which is when I started working with Java.
That whole experience taught me that I always need to have side projects going so I have actual work samples to show potential employers.
Good that it worked out for you.
I self-select myself out of jobs that have specific languages in the job listing if I can't honestly say I've used them professionally.
The majority of HR departments tend to suffer from myopia for anything beyond the last couple of months on the current employer.
So always take care not to land in something that will be hard to get out of.
public String getDescription() {
return “some constant”;
}
Versus public String getDescription() {
return FOO_DESCRIPTION;
}
it’s a fairly weak example but you get the idea.So you can refactor/find things that use Foo easier than 'foo'. You would probably get editor support for the constant "Foo" and not for the string "foo".
https://news.ycombinator.com/item?id=14506287
Enjoy. If you want to dip your toes in the water, you can go through
https://learnxinyminutes.com/docs/fsharp/
for a quick introduction.
If you have background in functional languages/programming, most of it should look familiar. If you come from an OO or a procedural background, then it might take some getting used to.
But if you are looking to get a job at a windows shop, I'd stick to C# ( or VB.Net ) as that's where most of the jobs are.
Delphi -> Microsoft stack -> Rails/Ruby -> Scala -> Clojure -> Rust
The stuff I remember well starts from Ruby. Good things:
- Ah man code is like a book.
- Lots of people were writing Ruby back then, so lots of libraries.
Bad:
- Deployments in general, this was before Docker, but I prefer having single binary/jar deployments
- Slow
- Lots of runtime errors
- You need lots of tests
Scala, good:
- If using IntelliJ, the development experience is kind of slick
- Quite modern, JVM libs work
- You can go functional or OOP, whatever you wish
- Fast
- Type system helps you out, so no need for that many tests
Bad:
- IntelliJ, I need my Emacs
- SBT is not very nice in the end, making a huge jar file can cause trouble sometimes
- Not the biggest fan of implicits
Clojure, good:
- Lisps are SO MUCH FUN to write
- ...especially if writing pure functions, stuff like code generators
- Emacs is good here, Cider rocks!
- JVM libs are available
- Leiningen is definitely nicer than SBT
- Everything is pretty easy and straightforward
Bad:
- Concurrency and effects can cause trouble, especially if doing it through Java libraries
- Leiningen is nice, but slow. You must use Cider.
- Runtime errors, as with every dynamic language
- Again lots of tests, but at least they're fun to write
Rust, good:
- Concurrency, IO and so on. Just damn nice with Rust
- Refactoring without fear
- Cargo is the best dependency management / build tool ever
- Small binaries
- Total control of CPU/RAM, you can do pretty fast systems
- If it compiles, it usually works
- Type system is great, libraries are quite nice already
- Community feels like the good old Ruby days, but better
Bad:
- Borrow checker still sometimes hurts, hopefully non-lexical lifetimes land to stable soon
- Problems with the current 0.1 tokio and futures can be pretty daunting. It's not a good idea to do super complex stuff with them yet
- Write your own libraries, especially if you started two years ago. That was fun though
I think the next thing I must grasp well is Haskell, just for the fun of it.
I really miss that old Ruby community. It was full of that hacker spirit, everyone trying stuff, no one to crush your creativity. Then the startup hipsters took over and came up with good practices, style guides, gems you absolutely had to know to get hired, etc.
It drove me away from Ruby, actually. It just wasn't fun anymore.
I hope it doesn't happen with Rust. I did not have so much fun writing code for a long time.