It makes me sad that having monad sugar is now just a given feature for a functional language.
Especially when that language has unrestricted side effects and the example code using the monad syntax uses those side effects, completely obviating the need for, and in fact rendering useless, monads as used in the example.
This is cargo cult programming at its worst. Including monad sugar because it's the thing to do, without even understanding what monads are or why they're useful. This is quite clear from the "tracing monad" example code. This "tracing monad" is like the Writer monad, stripped of all utility and sense.
What drove you to unleash this horror on the programming masses, Moggi? Why have you cursed us so?
I didn't add monadic sugar just to be part of the "cargo cult". I added it because I'm eventually going to use it to implement an automatic continuation-passing transform. I've done a similar thing before with ClojureScript's macros:
Monads are not simply a tool for managing side-effects. Why would you consider parser combinator/List Monads useless in a language with unrestricted side effects? Writing monads without sugar becomes clumsy very quickly and monads have been demonstrated to be useful innumerable times, why wouldn't you support some syntactic sugar for it?
I never said monads are made useless in a language with unrestricted side effects. I understand very well their utility even in the presence of side effects.
However, if you'll take a look at the example monad code, the bind and return methods themselves use side effects. I think that speaks for itself.
So, his example monad is bad. Indulging in hyperbole and invoking Moggi can at best be seen as attention-seeking behavior and at worst as an underhanded FUD tactic to turn people off the project. Peaker has pointed out the problem with the example monad without indulging in antics. There are better ways to communicate the mistake made in the example program. There is nothing irrecoverably terrible with Roy, but your comment conveys a false impression to the uninformed reader.
My issue is not with Roy. It's absolutely fantastic to have a functional language that compiles to JavaScript.
My issue is with the use of monads in particular, and I think my comment pretty well communicates that it's solely about that. I don't think it conveys a false impression about Roy as a whole at all.
Your statement that my comment "can at best be seen as attention-seeking behavior and at worst as an underhanded FUD tactic to turn people off the project" is just absolutely ridiculous. I have nothing against the project Roy, and I wish it nothing but success. Why would I possibly have any motivation to use underhanded FUD tactics to turn people off the project? And I don't even know what to say to your accusation of attention-seeking behavior. In what world is initiating a productive discussion about an important issue, and not in a self-promoting way at all, "attention-seeking behavior"? Get off your high horse.
The tracing monad is fascinating. What would be a practical application of it? I've been studying monads through Learn You a Haskell for Great Good but haven't been able to put them into context (other than isolating side effects and enforcing order of execution).
let traceMonad = {
return: fn x =
console.log "Return:" x
x
bind: fn x f =
console.log "Binding:" x
f x
}
console.log (do traceMonad
bind w = 1
let x = 2
bind y = 3
bind z = 4
return w + x + y + z
)
The option monad example might be closer. I'm having trouble figuring out how to get the value out of it though
data Option a =
Some a | None
let optionMonad = {
return: fn x =
Some x
bind: fn x f = match x
case (Some a) = f a
case None = None
}
let m = (do optionMonad
bind x = Some 1
let y = 2
bind z = Some 3
return x + y + z
)
match m
case (Some x) = console.log x
shows an error Error: Type error: Native is not Option 'dwk
Know what would be awesome? If you could use the monad syntax to clean up async, cb-passing code. I've been learning a lot of Haskell lately too, and I like to think of the monads as returning a set of instructions with side effects to be executed in order. I think this example is kind of trivial. I'd love to see one that does something more than simple addition (IO!)
...
(* long_running_call : unit -> string Deferred.t *)
long_running_call ()
>>= fun result ->
print_endline result
...
Running calls in parallel is easy. Say we want to run a couple of queries against a db at once, and only perform an action once both return:
...
(* Db.query : Db -> Db.Query -> Db.Result Deferred.t *)
let d1 = Db.query db query1 in
let d2 = Db.query db query2 in
d1 >>= fun r1 ->
d2 >>= fun r2 ->
...
Doesn't this have the same problem that Python's Twisted does -- that async calls may lack handlers for error conditions, and so errors may be silently ignored?
Async includes a module called Monitor that lets you correctly handle exceptions in async code. The signature of one of the most commonly used methods is this:
It takes two arguments. Name is used to tell you what monitor the error was caught by. The second is a function that returns some kind of Deferred.t. The whole thing returns a (deferred) Result.t (: [`Ok of 'a | `Error of exn]), letting you either do something with the results of the async call or handle the error. Usage might be something like this:
(* query_or_print : Db.t -> Db.Query.t -> Db.Result.t option Deferred.t *)
let query_or_print db query =
Monitor.try_with (fun () -> Db.query db query)
>>| function
| `Ok result -> Some result
| `Error e -> print_endline (Exn.to_string e); None
This looks great! It has all the essentials and compiles to readable JavaScript, should be a real productivity booster.
Looking at the JavaScript, though, all those anonymous functions can't be very fast.. I wonder if the compiler could try to defunctionalize the output..
The only gratuitous functions I saw were the "case" statement and the "with" statement. The with statement can really only be implemented with that function (though there is something to be said of only defining it once instead of n times). The "case" statement may be gratuitous but it feels like the compiler has a reason for wrapping it in a leakless function wrapper.
For "match", it needs to be an expression for some things so that means wrapping it in a function and returning the result. In the future I'm going to do some optimisation to see if the expression is the last in a function and remove the wrapper. For example:
Can't you take te same approach ClojureScript took, i.e. to generate a straitforward translation to JavaScript, and then let Google's closure compiler take care of the optimizing thing ?
var True = function() {
}, False = function() {
}, getBool = function(b, c, d) {
var a;
b instanceof True ? a = c : b instanceof False && (a = d);
return a
};
Roy has the same goal as CoffeeScript - create output that would be similar to how a JS dev would write it. If you use Roy but then decide you don't like it, just take the output and keep going.
I could rely on Closure for optimal performance but what I really want is optimal readability.
For some reason the parser breaks with that factorial definition, but adding spaces in between `a-1` seem to do the trick:
let fact a =
if a == 0 then
1
else
(fact (a - 1)) * a
console.log (fact 5)
Now, it's true that the Haskell syntax does not make that much sense if there is no partial application. I think that the variadic-arguments-are-always-allowed nature of JavaScript is not very compatible with partial application though.
> I think that the variadic-arguments-are-always-allowed nature of JavaScript is not very compatible with partial application though.
It could be implemented by compiling Roy functions to chains of 1-arg functions, but the amount of recursion and funcalling would probably be expensive (JS is not very good at these).
A SmartEnoughCompiler® you could also see partial application points and craft a partial application right there:
let add x y = x + y
console.log (add 1 32)
let add2 = add 2
console.log (add2 5)
var add = function (x, y) { return x + y; };
console.log(add(1, 32));
var add2 = function (y) { return add(2, y); };
console.log(add2(5));
As long as the number of arguments can be understood at compile time (which it can), it's not that hard: if the number of arguments provided make the function's arity reach 0 you partially apply it (and decrease the arity of the result), otherwise you fully apply it.
Currying is a nice-to-have but definitely not a requirement for a usable functional language. It might be possible to add it to Roy by abusing JavaScript dictionaries and identifying functions that are partially applied but I'm not convinced it's worth the effort.
Sure, but that can get costly fast (at runtime) when the language implementations are not highly optimized for funcalling and recursion (javascript VMs tends not to be), so ideally you'd only use partial application in the underlying (compiler result) code when that's needed rather than have all functions be curried.
I don't understand the monad because I'm too stupid, but I really really like Roy nevertheless.
I've really missed static typing on Javascript and Coffeescript; little typos are so hard to find! Structured typing really sounds like the way to go for putting type checking into Javascript, and I like how, just like Coffeescript, Roy tastes a lot like Javascript otherwise - avoiding all kinds of leaky abstractions you get with less JS-like languages. Plus, it has a sexy syntax.
I'd like to try this out in a real project. Is that being done already?
I think this has the same disadvantage with coffeescript.
The web is full with javascript and there isn't an easy way to swap between the languages. Also it's a pain to debug.
js2coffee helps there. I've gotten into the habit of converting old JS code with it as a first step of refactoring when I need to take over some old codebase.
Personally, I'd much prefer to have my compiled JavaScript have the same line numbers as the source. I think readability of the compiled js is great and all, but correct line numbers would be awesome
OTOH, string typing will greatly reduce your errors.
Especially when that language has unrestricted side effects and the example code using the monad syntax uses those side effects, completely obviating the need for, and in fact rendering useless, monads as used in the example.
This is cargo cult programming at its worst. Including monad sugar because it's the thing to do, without even understanding what monads are or why they're useful. This is quite clear from the "tracing monad" example code. This "tracing monad" is like the Writer monad, stripped of all utility and sense.
What drove you to unleash this horror on the programming masses, Moggi? Why have you cursed us so?