12 ms·
Functional programming explained in Clojure with a terminal-based game
- platz 12y agoI'm sure this is great walkthrough. Although I'd like it if there weren't headlines like "FP explained". I realize this may be pedantic, but If I've learned anything about FP, the term means different things things to different people, and a statement which makes universal claim about FP In General then will misrepresent the other flavors of FP. At least qualify it somehow. Actually, why not just say something like "Terminal-Based Game Walkthrough in Clojure"?
- phren0logy 12y agoMaybe I'm a "glass is half full" kind of guy, but explaining one kind of FP (rather than all of them) still counts as "FP explained" in my book.
- ch4s3 12y agoI agree. I think of it like 'x' explained (from 'y' perspective or with reference to 'y'). e.g. 'Woodworking Explained, master the lathe"
- coffeeyesplease 12y agoThis site is an amazing resource. Higly recommend it if you want to learn Clojure
- rabino 12y agoThanks! This was awesome to visualize some abstract concepts I've read about but never implemented.
- brudgers 12y agoI've started diving into Clojure...or rather drinking it's Joy from the firehose due to a birthday Barnes and Nobel gift card used for Fogus in the first edition [I'm cheap]. To me, this discussion of pure functions is distinctly not Clojuresque. Clojure is the Love Child of Common Lisp and Java™ both of which are distinctly pragmatic languages and neither of which is bogged down in a theory of pure anything. Clojure is structured to limit mutability to allow for reasoning about state in an environment with concurrent processes, but it expects this reasoning to be done in the context of professional programming, not the ivory tower of academics. It's idea of functional programming is in the history of Common Lisp, and a good description of it is given by Graham in chapter 3 of On Lisp: http://unintelligible.org/onlisp/onlisp.html#SEC23 http://unintelligible.org/onlisp/onlisp.html#SEC23 or http://www.paulgraham.com/onlisptext.html http://www.paulgraham.com/onlisptext.html Essentially, the idea is to isolate mutation - just like Haskell and for exactly the same reasons but without an explicit proliferation of types. So long as we have von Neumann machines, we're going to have mutation. The goal is to abstract as much of it away as possible to facilitate the management of state. But it never goes away, and we shouldn't imagine or try to convince ourselves that that it does...nor that it should, completely. Polemic over; still a good article and worth a read. Certainly got me thinking.
- prospero 12y agoAll true (I gave a talk on this subject just recently [1]), but the easiest and most provable way to isolate mutation is to not use it at all. I think most people coming from an imperative background are surprised by how infrequently mutation is actually required. It's worthwhile to explore how far pure functions will take you. [1] https://www.youtube.com/watch?v=iQwQXVM6oiY https://www.youtube.com/watch?v=iQwQXVM6oiY
- brudgers 12y agoFrom farmer's pen to plate, now I know where sausage comes from. I enjoyed the presentation, thanks. While I have found it improved my understanding of functional idioms when I explained them to others, I believe I am more helpful if I point people directly toward Hickey and Graham and PLT. I can paraphrase The Value of Values but it's better watched without translation.
- nonrecursive 12y agoI think you may be misunderstanding a few things, and I hope I can clear them up :) First, the term "pure function" does not imply "ivory tower" any more than "recursion" does. Pure functions are tools that allow you to "isolate mutation". In fact, Paul Graham mentions that experienced lisp programmers "try to segregate side-effects in a few functions, allowing the greater part of the program to be written in a purely functional style" (http://unintelligible.org/onlisp/onlisp.html#SEC27 http://unintelligible.org/onlisp/onlisp.html#SEC27). So - don't let the terminology throw you :) Second, the point of the point of the chapter is to show how much can be accomplished without mutating data structures. I think that's something valuable to focus on. Saying "You can do a lot with pure functions and immutable data structures" is not the same as saying "You'll never need to mutate anything" and as the author, I apologize if my writing suggests otherwise :( Lastly, Clojure definitely places a huge emphasis on limiting mutability by using pure functions and immutable data structures. I think Rich Hickey even describes pure functions as stable bricks or stable atoms in one of his talks. My impression is that it's definitely not the love child of common lisp and java. Clojure is pragmatic, but that doesn't mean it doesn't place an emphasis on functional programming. I agree that it's "not bogged down in a theory of pure anything", but that doesn't mean it wasn't intentionally designed to support and encourage programming with pure functions and immutable data structures.
- thinkpad20 12y agoHow does Clojure handle piping state through a series of functions? Is there anything like monads in Haskell to abstract it away? Haskell makes immutable data a little bit more tenable because you don't need to explicitly thread your state. How does Clojure handle this? Does it provide escape hatches for mutable state, perhaps restricted to using Java libraries?
- weavejester 12y agoClojure has libraries that implement monads, but these aren't often used for threading state. I can't quite place my finger on why, but in Clojure I rarely find myself reaching for something like the state monad, as I would in Haskell. Clojure tends to view mutability as a concurrency problem, and the tools it provides to deal with mutability, such as atoms, refs, agents, channels and so forth, are not mechanisms to avoid mutation, as to provide various guarantees that restrict it in some fashion. It might be that in the cases where I'd use a state monad in Haskell, in Clojure I might instead use an atom. They're in no way equivalent, but they have some overlapping use-cases.
- oskarkv 12y agoFor very simple cases there are the threading macros, `->` et al., that you can use like this: (-> state (assoc :a val) (update-in [:b :c] f args) (dissoc :d) (some-other-fn args)) where :a - :d are keys. Or you could use monads in much the same way as you would in Haskell; there are libs for that. Or you could write a new macro (or maybe even function) that does exactly what you want. I did that for a game. It looked like this: (call-update-fns game-state hook (update-monster-positions) (update-monster-ai) (let-characters-attack) (some-other-stuff)) It would thread the game-state (immutable) through all the functions, and call hook (a function) in between all the functions to send network messages and stuff (to minimize latency), and also collect and return new events that the functions created that I needed to handle (e.g. someone died when the characters attacked). It's almost like the state monad but not quite.
- muhuk 12y agoAnd there's synthread[1] for more complex cases: (-> {:a 1 :b 2} (->/assoc :a inc) (->/as {:keys [b]} (->/when (> b 10) (->/assoc :large-b true)))) 1: https://github.com/LonoCloud/synthread https://github.com/LonoCloud/synthread
- sgeisenh 12y agoDoes anyone else find Clojure's syntax a bit obtuse? I feel like basic recursive definitions are much clearer in Haskell and ML. To sum over a list using an accumulator shouldn't take more than two or three trivial lines of code.
- LordQuas3000 12y agoNobody would actually implement an accumulator that way. He gave a super simplistic example to illustrate recursion.
- technomancy 12y agoIt's definitely unfortunate that Clojure doesn't ship with pattern matching out of the box, but there's a library that implements pattern matching macros. But obviously summing over a list can be done in one line of code in any decent language even without pattern matching.
- oskarkv 12y agoNo. I love it. It's so simple. Even if Haskell has pretty nice syntax, I found myself resenting it because I needed to remember operator precedence.
- ajuc 12y agoPattern matching would be nice, but destructuring helps. (defn sum-acc ([xs] (sum-acc xs 0)) ([[x & xs] acc] (if (nil? x) acc (recur xs (+ x acc))))) Could you show Haskell equivalent? BTW there should be a macro that adds argument with default value.
- malisper 12y agoI believe it should be something along the lines of: sumacc xs = summer xs 0 where summer [] n = n summer (x:xs) n = summer xs (x+n) I'm sure there are macros in some library somewhere that implement pattern matching in lisp (regardless of dialect).
- brudgers 12y ago