Poker in Clojure

September 7, 2012 at 11:22 pm
filed under Coding
Tagged , ,

This is another experiential bit, but it’s a bit of a contrast between my experience writing poker-related routines in Haskell vs. Clojure. Of course the one caveat is that if you’ve done it before, you have some sense of what you need to do. There are a few problems I needed to solve functionally in Haskell first, and some of those translate quite easily.

For instance, the most interesting solution was dealing M hands of N cards. You could write a recursive function to do this, possibly with a fold to accumulate the hands you dealt. A much more interesting answer starts with this:

splitAt m . iterate (drop n)

iterate is the magic in this case. You create a lazy list of decks of cards, which iterates drop n over it. splitAt m splits off enough of that infinite list to create m decks, each of which has had n cards removed 0..m times. If you weren’t interested in returning the remaining cards, you could instead just map (take n) . take m.

This translates pretty well to Clojure, but I couldn’t get the syntax to look right. The lack of currying by default does have its price. On the other hand, this may be indicative that I’m doing something wrong. I can’t really tell if three let bindings before doing the “real” work is idiomatic, or whether I should’ve nested more, or what.

(defn deal [nhands ncards]
  (let [deck' (shuffle deck)
        decks (iterate #(drop ncards %) deck')
        draws (take nhands decks)]
    (map #(take ncards %) draws)))

Of course, since Clojure doesn’t care as much about shuffling, it is quite straightforward to do that.

On the other hand, check it:

(defn nkinds? [n ranks]
  (get (group-by count (partition-by identity ranks)) n))

Yes, it will return nil. Somewhere inside, I am sad. But meanwhile this is pretty slick. partition-by with identity turns [4 4 4 3 2] into ((4 4 4) (3) (3) and group-by with count transforms that into a map from number of items to those items. So (nkinds? 3 [4 4 4 3 2]) yields (4 4 4). Otherwise it returns nil. When I need to return more useful information, I can resort to when-let or some such.

Return types and dynamic languages

It really does feel like cheating, but the fact that I don’t have to declare return types is a pretty big point of flexibility. Now, I think part of why Lisps can get away with this is that there just aren’t that many datatypes. Contrast this with a comparable language like Python, where it is idiomatic to create and return arbitrary types.

That said, I can see running into the same problem I did with Python in a decent-sized codebase, which is that by the time you have more than a few modules, each with a few layers of functions, it can become very difficult to know what you’re actually dealing with. You end up tracing the calls up the chain to find the provenance of a datatype and it’s nuts.

Ultimately I’m not sure. I want to say that if all you’re doing is combining lists and maps and sets, you’re worried less about the specific type or representation of the data than you are about what data you have. Furthermore, the whole point of this exercise — immutability, no inheritance, pure functions — is such that you can reason about this far more easily. Setting up a repro case ought not involve setting up one out of, what, n! states your system can be in.

Perhaps the proposition, then, is something like this: Clojure is a dynamic language which is orders of magnitude simpler due to an emphasis on purity, immutability, and simplicity. You might worry about types but types will not in and of themselves make your programs simpler. I don’t know if I buy it, but if you want to take a construct on its own terms, it’s important to understand what those terms are.

Nil punning

Incidentally, I need to get my reading comprehension better, vis a vis my previous post about Clojure. The Joy of Clojure covers the topic of the empty list being truthy explicitly, under 3.2, Nil pun with care. seq is apparently the idiomatic way to detect this condition.

The idiom is something like this:

(when (seq s)
  (bar)
  (recur (rest s))

Apparently rest is lazy, so that’s one reason to prefer it, all else being equal. Regardless this is enough of a distinction I can sink my teeth into, given that Haskell has what it calls lazy vs. strict.

%d bloggers like this: