> the idea that every Rails programmer will be switching to Clojure or Elixir! [...] I don't see any 10x advantages [...]
I do. While I cannot speak for Clojure and Elixir, I can speak for Haskell, which I have moved to from Rails.
10x for me is:
1. A 70MB all-in binary to deploy, using 1-3MB of memory when running (has a build in HTTP(S) server), responding in 2-10ms to my requests. Compare that to 700-1500MB of gems, using hundreds of MB while running, serving requests in 50-200ms... More then 10x! And my framework of choice (Yesod) consists of 23kLOC, where Rails has 210kLOC (source: openhub.net)... Another 10x!
2. Less bugs when growing the codebase; "strong compile time guarantees" is now my main weapon when fighting maintenance curve.
Back in my Rails days I'd say "typing is a very narrow form of automated testing", which given my exposure to C++/Java was defendable opinion.
Now I say "strong typing is a wonderful test suite you get for free and automatically stays up to date". Not that it replaces tests; but it literally a wonder how Haskell's HM-typesystem it rejects broken code at compile time.
I'm not sure that reduction in code size or better performance is necessarily the 10x metric you want to go for. Maybe in some cases, of course, but 'my framework has fewer lines of code' is absolutely irrelevant to the delivered value of a service, for example.
Does using Yesod mean that you can deliver code that better meets your goals? In a business environment, that might mean easier maintenance, or better performance. Equally, it might mean faster development times, where maintenance is less of a priority. If it meets your goals then awesome! If it doesn't, then there are lots of other things you can use – including Rails.
Often goals are: crank out X with programmers Y on deadline Z. I assume the client has no language/framework preference.
If X is a greenfield webapp and Y are not Haskellists yet (language has a steeper learning curve then, for instance, Ruby), and Z is very soon then I will probably choose Rails (given that programmers have some experience with dynamic languages and/MVC framework, and I'm fluent with Rails).
Notice that maintenance costs are not a factor. This is often not accounted for, but a large part of a webapp's costs are maintenance: and this is where Haskell shines: maintenance and refactoring.
Let's say I'm starting up, then low maintenance costs are of PRIME importance. You want to be able to keep adding/improving with a small team, not being bogged down by an endless stream of bugs only found on production -- nothing ruins flow like that.
Strong typing on the web has been an intractable problem for me so far. Sure, I can have strong typing in my server-side code. But so many errors result from the interaction between the server, CSS, HTML, and JS. For example, you define a route at the path `/apples` but send an AJAX request to `/oranges` instead. Or you write `<div class="apples">` but query it with `div.oranges` instead. These are very much like type errors or name errors, except they occur at the boundaries of languages and processes.
Have you worked out a way to catch these sorts of things at compile time? If not, do you think it's possible in the framework of the future?
The examples you give don't seem to be typing problems, they seem to be wrong-value problems. They might incidentally also involve typing issues (e.g., "/oranges" might not exist or might be an endpoint with a different signature than "/apples"), but that doesn't seem to be the central problem in any of the examples.
> Have you worked out a way to catch these sorts of things at compile time? If not, do you think it's possible in the framework of the future?
To the extent that they are typing problems, it would seem conceptually possible to catch them through a strongly typed language and framework that abstracts all the underlying technologies and compiles to a combination of backend executable(s), and front-end HTML, JS, and CSS, and includes all the routing for both ends.
Actually building such a beast would seem to be a non-trivial engineering challenge.
> The examples you give don't seem to be typing problems, they seem to be wrong-value problems.
They're like type or name errors because the "apple" and "orange" here are like identifiers, not data. Sure, to the browser, they're data. But in terms of the structure of the web application, they're identifiers like variables, function names, or types.
For example, the HTTP endpoint "/apples?count=5" is like a function "apples(int count)."
> Actually building such a beast would seem to be a non-trivial engineering challenge.
It certainly would. That's why I'm wondering if you consider it possible.
"[I]t would seem conceptually possible to catch them through a strongly typed language and framework that abstracts all the underlying technologies and compiles to a combination of backend executable(s), and front-end HTML, JS, and CSS, and includes all the routing for both ends"
That would certainly do it, but I think all you need is some definition of interface that you can check your code against on both sides. This could be generated by one side and consumed by the other, or produced directly by the programmer(s) and consumed by both. You would need some means of actually checking your code against the specification on the consuming side(s), but they needn't be part of some broader framework (beyond the trivial sense in which they already are).
Sure, you can do that; but the problem is that you then have to worry about type system mismatches between the interface definition language, and the back- and front-end application languages.
There have been lots of things that do something like this: SOAP and the associated WS-* standards are probably the best known.
Haskell has some typesafe template languages. I'm not a huge fan of them, tbh, as they're kind of rough at the moment.
More promising in my opinion is the fact that Javascript is becoming an increasingly popular backend for Haskell via GHCjs which will give a great space for building type-checked front ends which have all the guarantees you like. For instance, type checked routes already exist which prevent you from writing the wrong endpoints or sending invalid typed data to them... these can be transparently extended to the frontend without much more difficulty.
While a bit rough around the edges, the full type safe server-client stack can be done in Scala with Play[0] + Scala.js[1] + ScalaCSS[2]
I say rough because despite Scala.js' fantastic performance characteristics, you're looking at 100kb file size off the bat; from there generated code size is reasonable, but that's a pretty big hit, particularly for cache challenged mobile clients.
Otherwise, being able to tie Play's type safe reverse routing into the client is a big win. Previously with GruntJS + Coffeescript approach I'd get small file size, but complete lack of safety; just winging it with `routes.user.maybeNotExist(id)`.
> errors result from the interaction between the server, CSS, HTML, and JS
this may be true, but I'm not sure spending resources trying to solve those problems, are the best use of resources?
I would rather be happy with a strict separation between the front-end and the server than try and deal with such an impedance mismatch and the framework cruft that generates.
I guess it just seems overly ambitious to me.. finding the right abstraction for the server is difficult enough without polluting it with the front-end.
It seems to me people are very productive in other languages that don't tightly bind the front-end code to the server; why spend time solving problems are are more incidental than essential?
> I guess it just seems overly ambitious to me.. finding the right abstraction for the server is difficult enough without polluting it with the front-end.
Certainly. That's why I'm skeptical that this will ever happen.
> why spend time solving problems are are more incidental than essential?
I wouldn't characterize these kinds of errors as incidental, inasmuch as they account for a very high percentage of the web app bugs I've encountered.
Designers of languages like Rust and Haskell noted that null pointer dereferences were the single largest class of errors in other languages. Thus, the designers chose to make null pointer dereferences impossible at the language level. With that choice, they turned a huge number of run-time errors, which developers often miss, into compile-time errors, which developers cannot ignore. This has proven itself beneficial to productivity and software quality.
So too here: If I'm correct that client-side type and name errors constitute a large fraction of all web app errors, then catching them at compile time will be a big win.
But again, I don't know how feasible this is. Nor do I know whether it would involve compiling from a type-safe language to HTML/CSS/JS or just static analysis of raw HTML/CSS/JS.
the logic of your analysis is sound. I suppose I'm just not sure that client-side type and name errors constitute a large fraction of all web app errors.
Anecdotally, the team I am on doesn't have these issues (we certainly have other issues), but I could see them being important to prevent on certain projects.
When using Haskell it feels like I more clearly have to deal with those not-strongly-types-environment issues. I have to write some interface code (which surely takes some "extra" time) to pull un-strong into Haskell; but then the unstronglyness is represented strongly in Haskell types and has to be dealt with accordingly. This reduces funny bugs that may otherwise arise when overseeing corner cases.
Main hometaker: it's like in Haskell I have to do more work up-front, to enjoy much better productivity down the road.
But isn't that like saying a healthy person can run 10x faster than someone with a limp?
For context, my background has been predominantly in a dynamic language, but now I've drank all the typed kool-aid, so I'm sold, as it seems you are as well.
So we're essentially saying typed is 10x better than dynamic, but was dynamic ever really a good place to start with?
Dynamic has a nice exploratory quality. It pushes off the moment of reckoning, but sometimes you need that if you're sketching an idea in code. It was good for me when I first learned, and I think that's still true now. For "in the large" systems it's basically required that stuff gets a bit more gooshy and dynamic-y and interact in an indirect, protocol-driven fashion.
But when you actually have a specific piece of engineering in mind, it's nowhere near as helpful - it tends to solve things that could probably be solved more soundly with compile-time constructs. But there's a process of building up your understanding to where you can actually have confidence in what you're building, which isn't reached so easily. Having the compiler fail your code before it can run is very "magical" in that sense.
As a Haskell programmer who has also come from Rails -- I'm a Rails developer now -- and who is also interested in writing a web app in Haskell, I'm very interested in your experiences. Are you on any forums or online communities that have helped you in developing a Haskell web app?
No, just started to use Yesod to do simple things. Then moved on to hard things. Sure I'm on relevant mailinglists and /r/haskell; but apart form that no special community forum helped me.
Well, I haven't started my web app yet. Right now I'm finishing up a Haskell app that plays Connect Four: maybe that could be the backend for a web app.
It looks like you're using Yesod. What resources did you use to learn it -- I think there's a Yesod book, was that helpful? Did you consider any other Haskell frameworks? What did you find hard about writing an app using Yesod? Do you think it took less time to write the app than it would have given a similar level of knowledge about Ruby/Rails?
Although I'm way faster writing Haskell than I was, say, six months ago, I feel I'm still really slow compared to how fast I can code in Ruby. Having said that, when I write a Haskell program, the only errors are usually logic errors, not process errors. NoMethodError (an unexpected nil) must be one of the most common Ruby/Rails errors. It's really great to just not get that anymore. :-)
> Now I say "strong typing is a wonderful test suite you get for free and automatically stays up to date". Not that it replaces tests; but it literally a wonder how Haskell's HM-typesystem it rejects broken code at compile time.
But in practice I've come to the idea it's a meaningless distinction if you're not going to bother to declare the types on your methods/functions.
Instances may be Strongly typed in Ruby. Methods are not. So you get almost none of the benefit. It's like Types only exist in Ruby to support Mixins and inheritance chains.
Prefer Composition over Inheritance right? But what if through Composition you effectively lost all your Type information? That's the issue Ruby presents.
> Instances may be Strongly typed in Ruby. Methods are not.
Sure they are, just not all of them:
[].first('wat') # => TypeError: no implicit conversion of String into Integer
What Ruby lacks is _static typing_ and compile time checks of these types.
Instead we have runtime checks in a few places.
> Prefer Composition over Inheritance right? But what if through Composition you
> effectively lost all your Type information? That's the issue Ruby presents.
This is mainly advocated because subclassing core classes (e.g. Array) can lead
to weird behaviour. For example:
class MyArray < Array
end
m = MyArray.new([10, 20, 30])
p m.map { |number| number * 2 }.class # => Array
Here one would probably expect an instance of MyArray to be returned, but
instead we get a vanilla Array.
> This is mainly advocated because subclassing core classes (e.g. Array) can lead to weird behaviour
No. It is advocated because it produces simpler code in the long term in most cases. It is a quite different question to inheritance from core classes.
Using introspection in Ruby is generally against convention. There's no reason to care about the class name if it #responds_to? the method you care about calling.
That's not an example of a strongly typed method in Ruby. That's an example of an assertion (most likely implemented in C).
Though in hindsight you're right anyways. Methods define an implicit type for the arguments they operate over. It's just you don't know what it is without reading the whole method. Which I think gets to "expressiveness".
I agree with the push to get people on the same page with regards to terminology. However, this comment would be far more useful if it suggested an alternate term for the real difference the parent was intending to indicate, or made explicit a claim that the difference was in fact not real.
I think one has to bend backward in order to defend that claim; but you probably have some ground somewhere since the term "strongly" is weakly defined :)
By strong _I_ meant HM-typesystem strong, or stronger... (for instance I dont think the NullPointerException-king Java is strongly types either -- though it is strictly typed -- again a matter of personal definition)
I do. While I cannot speak for Clojure and Elixir, I can speak for Haskell, which I have moved to from Rails.
10x for me is:
1. A 70MB all-in binary to deploy, using 1-3MB of memory when running (has a build in HTTP(S) server), responding in 2-10ms to my requests. Compare that to 700-1500MB of gems, using hundreds of MB while running, serving requests in 50-200ms... More then 10x! And my framework of choice (Yesod) consists of 23kLOC, where Rails has 210kLOC (source: openhub.net)... Another 10x!
2. Less bugs when growing the codebase; "strong compile time guarantees" is now my main weapon when fighting maintenance curve.
Back in my Rails days I'd say "typing is a very narrow form of automated testing", which given my exposure to C++/Java was defendable opinion.
Now I say "strong typing is a wonderful test suite you get for free and automatically stays up to date". Not that it replaces tests; but it literally a wonder how Haskell's HM-typesystem it rejects broken code at compile time.
TL;DR: I believe 10x frameworks are out there.