Hacker News new | past | comments | ask | show | jobs | submit login

Great points all around.

I'll add one thing to your point - multimethods are a fantastic example of how Clojure allows you to structure programs in functional way that takes a programming feature (in this case polymorphic method dispatch based on the arguments at runtime) to the nth degree.

In Java and many other languages that have polymorphic method dispatch - the dispatch value is usually the type of the object for whom the method is being called on. Functional languages like Haskell take a much more powerful and expressive approach to this and extend it to matching the dispatch values for the called function on the type or pattern matched value of any argument.

Multimethods are essentially Clojure's take on this in a dynamic language. They allow the programmer to define a function that constructs the dispatch value however it wants - usually from the arguments given as input, in any order, on any condition.

To illustrate, I'll give an example I showed my wife yesterday. She's writing a roguelike game in ClojureScript at the moment that you can play in the browser - naturally she needed a way to represent the game world and the players position as they move around.

So, for illustration I'll omit the code for the world state, but she needed to bind event listeners for the keyboard to properly dispatch the proper movement logic. So the code she wrote looked like this:

https://gist.github.com/aamedina/8114944

She came to me with this code with the following problem - how do I think she should organize it? Clearly the handling logic is going to be more complex than a single line per direction (:up, :down, :left, :right) could handle, she has to account for terrain differences, buildings, monsters, what have you.

So I suggested using multimethods to accomplish this. This is what the multimethod solution I cooked up would look like in this case.

https://gist.github.com/aamedina/8114949

Now she has room in those multimethods to handle the potentially complex logic needed to make those arrow keys move the character properly. But another effect of this is that it hides the underlying state mutation from the implementation.

Now your move functions are pure and isolated from the coords atom, in addition to the fact that multimethods made the logic more organized, at the expense of a few more lines of code.

edit: moved code to gist




In my experience mulltimethods get you a long way when writing Clojure code. My projects use them often and add two or three macros. That is normally the only "overhead" I have to impose over regular functions.

That said, in the example you posted I don't think you are using all the power of the multimethods, because you did not replace the case statement. You went from a case statement to a multimethod+a case statement. In this case, why not let the mutimethod dispatching to do all the routing/case functionality for you? Just use (.-keyCode e) as your dispatch function and use the different values of KeyCodes.XXX for each implementation of the multimethod. And you can even add a :default implementation that leaves the coords untouched.

In my experience the natural use of multimethods arise when you can choose the dispatch function for an element based on either some piece of data from the element or a computed value derived fron the element. But if you have to rely on case/cond/if statements the value of the multimetod is lessened, as you still have to touch your dispatch function when your hierarchy of values change.

edit:typos in the code


Very good point. This was an artifact of the way my wife originally structured the function (dispatching on the keywords :up, :down, :left, :right), and I just kept that.


The proximity of your handle to adriaanm's, Scala tech lead at Typesafe [1], made my first reading of your (good) posts very confusing.

[1] https://news.ycombinator.com/user?id=adriaanm


That's really funny. :)


A simple CASE statement would be better. It would replace a complex machinery plus lots of character-level syntax complication.

A typical case where a more complex construct does not help much.

Generic functions were introduced into Lisp, to replace a complex message sending mechanism with a more functional notation.


Did you miss the case statement both used in the original code and the dispatch function?


No, the original version was easier - though I would write it slightly different.


https://gist.github.com/aamedina/8115304

First version here: Uses only a case statement to dispatch the proper behavior, but as a pure function.

When she begins to take the state of the map and all of the other variables into account, this case statement will prove confusing to wade through - because it's one function that is doing four related but separate tasks. So version two is what you'd likely end up doing after the code got too unwieldy.

Oh wait... that's what a multimethod is! Now you've gone full circle. I hope you can see the point I was trying to make more generally, beyond the limited example I gave here.


That's because you're not considering the fact that the complexity of handling movement in each direction will increase -dramatically- as she continues to write out the game.

If the logic would forever stay a one liner, "increment the y coord!" when the up arrow key is pressed, I would agree with you - keep the case statement and go on your merry way. But that isn't the case, and my wife anticipated that being the case, hence the motivation for her question.

Spreading out complex logic into separate functions is usually considered good form. Like every design decision, you have to be wary not to overdo it - but I think my wife was spot on in identifying the need to do so in this case.

Putting the case statement aside for a second - you're also overlooking the fact that by using functions in this way the implementation becomes pure. The functions do not mutate anything and are completely oblivious to the fact that they are being used to swap! out the current position of the player for another.

Some benefits to writing pure functions include easier testing (you don't have to supply an atom to know if your function works, just call the function) and REPL use is vastly simplified. Additionally, I don't even need a browser or a keyboard handler to see if my movement functions work.


I can write it as multiple functions without the multimethods just as well.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: