Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Functional programming explained in Clojure with a terminal-based game (braveclojure.com)
100 points by yawz on May 15, 2014 | hide | past | favorite | 30 comments


I'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"?


Maybe 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.


I agree. I think of it like 'x' explained (from 'y' perspective or with reference to 'y'). e.g. 'Woodworking Explained, master the lathe"


How 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?


Clojure 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.


For 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.


And 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


This site is an amazing resource. Higly recommend it if you want to learn Clojure


I'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 or 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.


I've been developing in Clojure for a while now, and I think your characterisation of the role of pure functions in Clojure is somewhat inaccurate.

In any given Clojure code base, you'll be dealing with pure functions 95% to 100% of the time. Most of the libraries I've written are completely pure, not out of idealism, but because side-effects are simply not necessary.

Yes, Clojure provides tools to limit and constrain mutability, but if you've only recently come from an imperative language, you may be surprised at how little these tools are actually used. Most of the time you're dealing almost exclusively with pure functions, so tools to manage mutability are not necessary.

Discussion of pure functions is certainly idiomatic Clojure, because you'll be dealing them most of the time. Just because Clojure doesn't enforce purity through its type system, doesn't mean you shouldn't prefer pure functions the vast majority of the time.


I 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). 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.


The article well written. It didn't throw me or make me confused. I simply was bothered by the lack of nuance in regard to mutation. Trillions of pixels have been burned describing pure functions and giving small examples of immutability and recursion. Trees have even been killed.

What makes Chapter 3 of On Lisp or SICP useful is they're Third Acts. They go deeper than pure functions. They're pivotal because that's where we start talking about the messy world where mutation is necessary. It's where we admit that there's a rationale for the way Java works even if it's implementation could stand improvement.

What makes Clojure interesting is the way in which it implements that depth. Lisp went on a journey to Java and was transformed by the experience. Clojure is not just another Lisp - that side of the family has always had shared structure and a propensity for functions. There's `is` and 'testing` and agents and vars and `.` and `..`. JavaScript the good parts is a book. Java the good parts are in Clojure. Clojure is an Act V not an Act I.


The book chapter aside, I think you might actually have it backwards. Hopefully I'm not misunderstanding you terribly! The reason why PG has to spend as much time as he does warning programmers about hidden ways in which Common Lisp mangles your data structures (nconc, for example) is because Common Lisp does not come with immutable data structures out of the box. My impression is that he's telling you how to work as well as you can (strive for purely functional code) with the tools at hand. Clojure, on the other hand, was built so that you could code without having to worry about that stuff by default.

Also, you say that lisps have "always had shared structure." I'm not sure what you mean by that? Clojure implements _structural sharing_, which is what allows it to have persistent, immutable data structures. Common Lisp certainly doesn't do the same.

I'm not sure what you mean by "Lisp went on a journey to Java and was transformed by the experience." I think you're saying that Rich Hickey designed Clojure to be a lisp that's more Java-like? Could you explain how? My impression is that Rich Hickey thinks OO is broken (see the talk "Are we there yet?"), as is the notion of mutability as implemented by Java. Clojure's interop with Java is a great convenience, but the emphasis is still on functional programming. I don't think I understand what you're saying, or maybe it's just that we disagree?

Finally, from clojure.org: "Clojure is predominantly a functional programming language, and features a rich set of immutable, persistent data structures. When mutable state is needed, Clojure offers a software transactional memory system and reactive Agent system that ensure clean, correct, multithreaded designs." Maybe it's these state management features you're concerned about? If so, then I would suggest that vars, atoms, refs, and agents are not as inspired by Java as you're saying. Or maybe we just disagree that, the majority of the time, you should be writing pure functions and using immutable data structures in Clojure?


PG isn't warning any experienced Common Lisp programmer about `nconc`. The [n] at the front means it's destructive...there's also `nsubst` and `nreverse` and `nstring-capitalize`. They all exist in Common Lisp to allow a programmer to avoid consing and the subsequent garbage collection. In other words, they exist to allow a programmer to optimize their code. They exist as exceptions to idiomatic Common Lisp practice of returning values in lieu of mutating locations. Sure it is not as idiomatic in Common Lisp as in Clojure in part because Common Lisp is largely a product of its time and that time was largely single threaded.

Avoiding mutation in Common Lisp is even less idiomatic than it is in Scheme - where there are not destructive versions of various functions by default and idiomatic practice is mutation is signaled with a bang [!]. But again, the preferred approach is persistence and writing side-effect free functions. Indeed at the front edge of Scheme, Racket implements immutable data structures by default.

Idiomatic functional style and defaulting to immutable data structures are not what makes Clojure unique as a Lisp - it's knob twiddling, not a new paradigm. It has some great built in syntax that allows for the best part of Lisp-2 programming - i.e. symbols representing a collection acting as functions over the collection and the association of a meta-data map with a symbol like a plist. But it's evolutionary as a Lisp.

On the other hand, though not unique, idiomatic functional style and defaulting to immutable data structures are unusual in relation to the Java platform. Its relationship to the Java platform is what makes Clojure unique. STM is not a product of it's Lisp heritage. Neither is it's implementation of objects with via interop [largely eschewing objects despite availability is also idiomatic Lisp].

Understand, I'm writing to think. What you've written is just a starting point I can use as an excuse for saying what I was primed to say anyway. Like I said in my original comment, I was already drinking from the Clojure firehose.

So what's the upshot? Well what did your article say that hasn't already been said before about functional programming? Why not point people to Chapter 3 of Graham? It's more convincing for those who need convincing and more in depth for those who don't. Why not point people to The Value of Values, it's again more convincing for tire-kickers; a comfortable sermon for the newly converted, and a reference point for those already in the cult.

What really makes Clojure unique?


I don't think I read On Lisp the same way. From 3.1: "If a function is advertised as destructive, that doesn't mean that it's meant to be called for side-effects. The danger is, some destructive functions give the impression that they are. For example,(nconc x y) almost always has the same effect as (setq x (nconc x y))". Calling it dangerous sounds like a warning to me. I definitely agree, though, that he makes the case that the preferred approach is writing side-effect free functions. I'm not sure what you mean by "persistence" though, because Common Lisp doesn't have persistent data structures.

I don't know if its emphasis on functional programming and values makes Clojure unique, but that's definitely one of its core design concerns. The "article" is a chapter from a book on Clojure, and learning to write in a purely functional style is essential to learning Clojure. The point of the chapter isn't to get to the heart of what makes the language unique, it's to offer instruction on how to use it.


Here's the recycled cons at the car of ANSI Common Lisp section 12.4:

Common Lisp includes several functions that are allowed to modify list structure. These functions are destructive for reasons of efficiency. Though they may receive conses passed to them as arguments, they are not meant to called for their side-effects.

For example, delete is a destructive version of remove. While it is allowed to trash the list passed to it as an argument, it doesn't promise to do anything. This is what happens in most implementations:

   > (setf lst '(a r a b i a))
   (A R A B I A)

   > (delete 'a lst)
   (R B I)

   > lst
   (A R B I)
As with remove, if you want side-effects, you should use setf with the return value:

   (setf lst (delete 'a lst))
Because garbage collection and homoiconicity can sometimes feel like magic, it can be hard to think of Common Lisp running closer to the metal than a language like C. But it does. Cons cells are locations in memory, and symbols are pointers and there's nothing in between. A programmer doesn't even get `free(array)`. Memory locations can be shared willy-nilly because just as in Clojure, two distinct lists/sequences can share a tail. From ANSI Common Lisp section 12.8, "Constant Structure":

   > (defun arith-op (x)
       (member x '(+ - * /)))
   <function:arith-op>

   > (setf lst '(as it were)
   (AS IT WERE)

   > (nconc (arith-op '*) lst)
   (* / AS IT WERE)

   > (arith-op '-)
   (- AS IT WERE)   ;; bad

   > (arith-op 'as) 
   (AS IT WERE)     ;; even worse
"Oh Shit!" moments like this are why Lispers like Graham developed a functional programming style. It's a lot of what motivated the design of Scheme. It's not really what motivated Clojure because Java already solves this problem. Clojure is designed to solve the problems Java doesn't more easily.

That fundamental goal is why what makes Clojure unique matters when explaining Clojure. It's also what makes Clojure a less than ideal vehicle for teaching functional programming style - it's designed for programmers who are solving problems on the JVM [or CLR or V8], and not really so much as a general purpose language. It's designed around interop. As weavejester says in his linked talk, Clojure is a Java library.

There are better languages for teaching functional programming - Scheme/Racket. What makes them objectively better is that helping people learn to program in a functional style is one of the problems they are trying to solve, and they have almost four decades of development toward that goal. Education is entirely orthogonal to Clojure, even more so than Java with its two decades of introductory text development and promotion via vocational arguments in CS departments. And, Racket/Scheme isn't trying to solve JVM problems.

Teaching functional programming style is hard, but a largely solved problem because the internet allows pointers to excellent materials.

http://learncodethehardway.org/blog/AUG_19_2012.html


You're right about being able to point to the same tail from two different lists in common lisp, of course. I'm not very experienced with scheme so I can't comment much on that. Clojure is different in the way it implements its data structures, so "structural sharing" in that context refers to its use of tree structures to implement vectors, maps, etc.

Still, though, I think you have it wrong about Clojure's design. You mentioned the talk "the value of values." Immutability and functional programming are core to Clojure's philosophy. This is pointed out on the clojure.org home page. I'm not sure why you think it's not?

Also, Clojure is meant to be a general-purpose language. Also from the home page: "It is designed to be a general-purpose language". So I'm not sure why you think otherwise? I started learning Clojure with 0 experience with the JVM, and many others are doing the same.

I'm also not sure what you mean by "education is entirely orthogonal to Clojure." I think you're commenting on the value of producing an introductory Clojure book? In any case - you mention that "there are better languages for teaching functional programming." The point of Clojure for the Brave and True is not to teach functional programming, it's to teach Clojure.


If the purpose of Clojure for the Brave is not to teach functional programming, why bother teaching it? Why go through the motions when not fully committed? Is it better than pointing the student to excellent resources despite those resources using another form of Lisp?

To put it another way, is anyone really going to know Clojure without knowing the something about SICP? Is there anything out there that more fully captures Clojure's definition of functional programming than On Lisp or Learn You a Haskell for Great Good?

OK so here's where we differ. It's in our definitions [what could be more Clojuresque] of "learning Clojure". Sure, Clojure's syntax takes an hour versus the thirty minutes Ableson takes to cover Lisp syntax including the substitution model. Then what?

Well I'm saying the two things a person needs to learn Clojure are the practices already described in four decades of Lisp's literature and the Clojure interop. Without either piece, I won't know Clojure.

http://norvig.com/21-days.html


The book covers functional programming because Clojure places a heavy emphasis on functional programming, and learning it is essential to learning Clojure. The book is not "Functional Programming for the Brave and True, with Clojure as the Medium." That doesn't mean that I'm merely "going through the motions" and am "not fully committed" to helping people understand functional programming, and it's bizarre, presumptuous, and rude that you would suggest that. At this point, it seems to me like you're merely being argumentative.


>Well I'm saying the two things a person needs to learn Clojure are the practices already described in four decades of Lisp's literature and the Clojure interop.

I agree. However, the Lisps all have subtle differences. For example, Clojure's interop is one difference, but it's not the only difference. These subtle differences still need to be explained in that Lisp's context to receive a full understanding of that Lisp. This is what I feel nonrecursive has achieved with his writing about FP in Clojure.


All 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


From 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.


Does 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.


Yes, and no. Viewed as a Lisp, it trades some simplicity for practicality, e.g. vectors rather than lists as the container for arguments to a function. Neither than `lambda` template for Common Lisp's `defun` macro or the "usage" template for Scheme's `define` are consistent with the overall language syntax because the list should be quoted or executed.

On the other hand, viewed as a DSL for the JVM implemented as a Java library or as an evolutionary step in Lisp, Clojure's syntax makes a lot more sense.

Summing over a collection is a one liner in Clojure (as in many other languages):

    => (reduce + (range 10))
    45
Clojure's dynamic typing trades simplicity of syntax for the static typing safety harness required in ML or Haskell.


No. 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.


Nobody would actually implement an accumulator that way. He gave a super simplistic example to illustrate recursion.


Pattern 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.


I 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).


It'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.


Thanks! This was awesome to visualize some abstract concepts I've read about but never implemented.




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

Search: