I think, that if you could integrate source-map capabilities (haven't tried http://hackage.haskell.org/package/sourcemap yet) it might make the debug process more bearable.
But if you would be looking for haskell-like-language with more comprehensible to-javascript output, http://www.purescript.org/ pleasantly suprised me (even though I had time to barely get past the hello world :-)
Nice short read.
The highlight of the article is having a unique Action sum type instead of a myriad of separate functions. But the action still has to be processed by a myriad of separate equations (is that the correct Haskell term? Not familiar with the language):
apply ClearCompleted tds = over todosItems (Map.filter (\x -> view itemStatus x /= Completed)) tds
apply (DeleteItem x) tds = over todosItems (Map.delete x) tds
apply (EditItem x) tds = set todosEditing (Just x) tds
...
"Only one of the 68 frameworks defined an Action" is probably because it's simpler to directly call the right function, rather than over-engineering things with a short-lived intermediary representation.If actions need to "be serialized, recorded for later analytics, and generated automatically" then it makes much more sense. But this is a TODO sample app. YAGNI.
And if we really need it, it's not like JS cannot do it:
function apply(action, todos) {
switch (action.type) {
case 'ClearCompleted':
return todos.filter(todo => !todo.completed);
case 'DeleteItem':
return todos.filter(todo => todo !== action.todo);
...
}
}
The above has probably been done a billion times in one form or another. Of course it's not safe from typos in the case strings or missing cases, but that's a broader issue with JS in general, not specifically related to sum types.I'm not trying to shoot down Haskell here, I really wish someone will point to something I'm missing and make it click. But right now it just looks like over-engineering that JS could do but chooses not to.
(Regarding footnote #4: Swift also has sum types and is fairly popular.)
That intermediary representation can enforce that your input is valid. Then you can create functions based on that intermediary representation and act as if your data is valid because it is.
The intermediary representation can also ensure you cover all cases if your states are encoded with sum/product types thanks to exhaustiveness checking in supported languages.
> I'm not trying to shoot down Haskell here, I really wish someone will point to something I'm missing and make it click. But right now it just looks like over-engineering that JS could do but chooses not to.
JS can't turn runtime errors into compile time errors because it doesn't have a compiler or a powerful type system. In fact it has a very weak/dynamic type system.
To see the distinction, notice that many Actions have some associated data:
data Action a
= ClearCompleted
| DeleteItem ItemId
| EditItem ItemId
| EditItemCancel ItemId
| EditItemDone ItemId a
| Filter (Maybe ItemStatus)
| NewItem a
| NoAction
| Refresh
| Toggle ItemId
| ToggleAll
In your analogy, the `action` value is actually quite complicated: it's an object (record) containing a field called "type" containing a string; if the "type" field contains the string "DeleteItem" then the action object also contains a "todo" field, containing an item ID; if the "type" field contains the string "EditItem" then the action object also contains a "todo" field, containing an item ID; and so on.This is known as a "dependent record", and requires a much more elaborate type system than polymonial types. In fact, without careful consideration, dependent type checking can end up being undecidable!
Compare this to the polynomial type, where the parameters (item IDs, etc.) are right there in the value. We can never have an Action without the corresponding parameters (if we try, we'll end up with a function rather than an Action, thanks to Currying). We can never switch the type of an action while forgetting to change the parameters; etc. Plus, of course, we've denoted a finite set of actions, rather than relying on strings (AKA "stringly typed" programming).
Another point to note:
> it's simpler to directly call the right function, rather than over-engineering things with a short-lived intermediary representation.
Of course it would be simpler, but the entire point of the exercise is to use MVC to separate concerns, even though it's clearly overkill. If we're going to do MVC anyway, then the Action type provides a very simple interface between the Controller and the Model: the Controller just spits out an Action, the Model just receives an Action; no need for any further coordination. Plus, it's all really easy to reason about and type check.
GHCJS has a ways to go methinks. Js_of_ocaml and Scala.js are far better suited for production use today as the type safety "tax" is much smaller (i.e. binary is at most 1/4 the size for equivalent functionality).
EDIT:
didn't realize you cannot yet call into Haskell from GHCJS, and even Haskell to GHCJS requires going through FFI[1]
Meh, might as well use Fay or Haste if that's the caste.
[1] http://stackoverflow.com/questions/29967135/how-to-call-hask...
https://github.com/tonyday567/mvc-todo/tree/master/library/T...
https://github.com/tonyday567/mvc-todo/blob/cb4bbb613aa31ba6...
I have 100% faith in the Haskell community's ability to keep doing what they've been doing for years. ;)
http://i.imgur.com/kPHl4L7.png
7834KB is way too large for just that, I'm afraid.
I've 120Mbit/s, that's why it still loads fast, but I remember how slow, but fascinating surfing with 56K modem was.
The raw unoptimized code was in the several megabyte range though.