Interestingly, we had a couple of Clojure experiments.
Takeaway was that there were some benefits in terms of performance and maintenance, but they weren't worth the tradeoff in expressivity and increased developer time.
There's no reason that Rails should be restricted to 'monorail' or monolithic apps. We've had great success writing what are essentially microservices in Rails. Code is easy to read, easy to maintain, and performs well. It's hard to find any compelling reason to use another toolset under those circumstances!
I guess just another example of different strokes for different folks. For us developer time decreased, and expressivity increased.
I wasn't suggesting that rails shouldn't be used for microservices, we actually have a number of rails microservices in production as well.
I was more stating that I agree with OP in that if I were to write a monorail style application today, I would use ruby/rails over clojure. I was also pointing out that if you are moving to more of a SOA/microservices framework that we've had great experience with clojure, and that it's worth looking at.
I'm a huge fan of rails, and have been using it since pre-1.0. I think as we look back on this period in time, we're only going to appreciate rails more and more for really making self / low funded startups viable.
A great point, of course. That's why we had a crack at Clojure in the first place!
Basically, it's great to have a diversity of freely-available, open-source tools that can be used for such a wide variety of different tasks. I expect the 'my toolset is better than your toolset' nonsense will always be with us, and while it's useful to discuss some of the pros and cons, we should all bear in mind that most tools are awesome :)
If you feel Clojure is less expressive than Ruby, you're not good enough at Clojure yet. I'm not saying you have to prefer Clojure's syntax, and Ruby probably has more libraries for a lot of things you want to do. Going from Ruby to Clojure may be a productivity tradeoff for you and your team, but objectively it's not an expressivity tradeoff. Clojure has a bit more expressive power than Ruby.
In Ruby you can't express the concept of an empty List through a Type.
When comparing statically typed languages to non, that's generally what "expressiveness" means to me. Any idea can be expressed through expressions in a turing complete language right? That's not a useful metric.
In Ruby a method's type-signature expresses almost nothing for example.
def f(a,b)
Does the method side-effect? What types are a or b? Are they the same type? Does the method mutate the arguments? Return a new value? What sort of values might it possibly return?
In Ruby you can't express any of these things outside of naming conventions (that are entirely useless for answering any of these questions to any tooling).
Compare that to Scala:
def f(a: Int, b: Int): Int
Does the method side-effect? Almost certainly not (but it would typically depend on the containing Type). What are the types of a and b? Integers. What does it return? An Integer. Does it mutate the arguments? No; they're immutable. Is it possible for the method to return anything else? Throw an exception perhaps? That would be unexpected. Otherwise you'd write it to return a Try[Int], so you can assume more about the author's intent here.
Well now you're getting into statically vs dynamically typed, which doesn't apply to this conversation because both Ruby and Clojure are dynamically typed.
But in a statically typed language like Scala or Haskell, you can use the typechecker to express proofs about program correctness. That is a form of "expressiveness" that doesn't really exist in Ruby or Clojure (unless you use core.typed).
There's nothing about dynamic typing that prevents declaring types right? Because in Ruby you while you can extend an instance, you can not taketh away Types from it.
IOW, Ruby could use the Scala syntax I wrote, and still be just as Dynamic. You're already constrained by how you use that type within your method. You're just not expressing it concisely since you have to read the method to understand the implicit Type it's operating on.
In practice a lot of this gets naturally conflated though. Which I think you were getting at.
But if we're going to limit ourselves to functions with undeclared types, I guess I'd point to Clojure's partial as something you can't really express with Ruby. Or Pattern Matching in Elixer.
Well, Ruby is duck-typed with dynamic/late dispatch, so method receivers are not known ahead of time. When I call `foo.bar`, it does a dynamic lookup in the class hierarchy at runtime to see which class implements the `bar` method, and the result can be changed at runtime. So although you could theoretically declare types in Ruby, it would just be documentation. You'll still get the type errors at runtime because there's no ahead-of-time typechecking happening.
With Clojure you can actually compile your modules while developing them and discover certain classes of type errors ahead of time, so something like core.typed actually makes sense because you can run the typechecker over your code instead of having to run a separate test suite.
The main type error you always have to worry about in both languages, though, is accidentally calling functions/methods on nil at runtime. Of course that applies to null in Java too...
> You'll still get the type errors at runtime because there's no ahead-of-time typechecking happening.
That doesn't mean it's just documentation though. It could tell you that something is happening you didn't expect. Like passing a Role where you expected a User. (And if you wanted to operate on both interchangeably you shoulda put an interface on it.)
> The main type error you always have to worry about in both languages, though, is accidentally calling functions/methods on nil at runtime.
Agreed. Option/Maybe types FTW. The ?-operators sometimes presented as a solution feel very half-baked in comparison (they don't return a new type so just because you remembered to use it on line 10 doesn't mean anything to lines 20, 37 and 82).
Expressivity to me is generally about code size, for a given problem does the language allow a concise solution to be expressed?
The more concise the solution the more expressive the language.
A language that supports 4*4 is more expressive than a language that only supports 4+4+4+4, languages that require more type information to be expressed are in general less expressive.
where IAddition is an interface requiring the infix + operator to be defined, and the subsequent definition of said interface, where as a more expressive language will deduce the interface from the function.
An empty List would have to be a List first. So an Instance. But at that point you'd be facing a significant challenge to ensure it's both Immutable and still a List (or Array, whatever).
But your definition of "expressive" is (IMO) backwards. Because the first example only actually expresses a label that only has meaning because you give it. It doesn't tell you if the method prints to STDOUT, concats a String, adds an Int, adds a Float to a Decimal (and how that would be achieved), etc.
All that, and Ruby is (IME) more verbose than Scala (and I suspect Elixir, Erlang, Clojure, etc) for any real non-trivial example.
I think the biggest reason would be Clojure's macro system. The code itself is an abstract syntax tree that you can rewrite in arbitrary fashion at macro expansion time. There's a good example here of how Clojure's macros are more powerful/expressive than Ruby's instance_eval and class_eval: http://daveyarwood.github.io/2014/07/09/whys-guide-to-ruby-i...
Not sure what examples the author was thinking of but functional lisps tend to be extremely expressive just because of their nature. If there is expressiveness you are missing the language can be molded in ways that are simply not possible in non-homoiconic languages.
Takeaway was that there were some benefits in terms of performance and maintenance, but they weren't worth the tradeoff in expressivity and increased developer time.
There's no reason that Rails should be restricted to 'monorail' or monolithic apps. We've had great success writing what are essentially microservices in Rails. Code is easy to read, easy to maintain, and performs well. It's hard to find any compelling reason to use another toolset under those circumstances!