Quantcast
Channel: Planet Apache
Viewing all articles
Browse latest Browse all 9364

J Aaron Farr: Nim: Light Table Minimum Viable Snippet

$
0
0

What minimum viable snippet would be good for trying out Light Table? Short, simple, instantly understandable, but not trivial. Suited for Clojure’s functional paradigm yet slightly atypical. How about a game? Two players, perfect information, multiple turns, binary options? How about a version of single-pile Nim?

Two players take turns removing either one or two stones from a single pile. The person who takes the last stone wins. Easy.

Since I don’t know Clojure well, I gravitated toward writing a first version in Haskell.

Haskell’s rich static type system facilitates type-driven design. Write out all the types for the program, then fill in the implementations. For the Nim game, I started by writing:

-- We have two players
data Player = P1 | P2

-- and a pile.
type Pile = Int

-- The state of a game is described by the size
-- of the pile and whose turn it is.
data Game = Game Pile Player

-- Players move by taking one or two stones.
type Move = Int

-- A move updates the game state.
move :: Game -> Move -> Game

With very specific types, Haskell requires that you pay very special attention in situations where other languages let you ignore details. For example to play a game, each player needs a strategy for picking options. With a human player, we ask the person what move to make and that means IO. So I ended up with two stategy types and a function to taint pure strategies:

type PureStrategy = Game -> Options ->    Move
type     Strategy = Game -> Options -> IO Move

taint :: PureStrategy -> Strategy

Does Haskell bog us down in details or are these exactly the sort of details we want to be paying attention to? I’m still not sure, but it is always thought provoking.

I’m sure about point-free style either. Instead of defining functions by passing named arguments around, point-free uses function composition and combination in sometimes elegant, sometimes rediculous, sometimes whimsical ways:

-- The so-called 'dot' combinator.
(.:) :: (b -> c) -> (a -> a' -> b) -> a -> a' -> c
(.:) = (.) . (.)

taint :: PureStrategy -> Strategy
taint = (return .:)

I’m not sure what to make of point-free style, but it too is always thought provoking.

The Clojure version is a fairly direct translation of the Haskell version.

Besides the immediately noticeable addition of parenthesis, there are a few other natural changes:

  • There are no declared types.
  • Use keywords :P1 instead of data constants P1.
  • Use maps {:pile 14 :player :P1} instead of data constructors Game 14 P1.
  • Uncurry functions.
  • Use elimination forms instead of pattern matching:

    -- Clojure Elimination via Map Lookup
    (defn move [game move]
      {:pile   (- (:pile game) move)
          :player (next (:player game))})
    
    
    -- Haskell Pattern Matching
    move :: Game -> Move -> Game
    move (Game n p) m = Game (n - m) (next p)
    
  • Use nil directly instead of Maybe:

    -- Clojure nil Comparison
    (defn winner-is [game player]
      (= (winner game) player))
    
    
    -- Haskell Maybe Wrapping
    winnerIs :: Game -> Player -> Bool
    g `winnerIs` p = winner g == Just p
    
  • Reply on implicit effects instead of monadic threading:

    -- Clojure Implicit Effects
    (defn until [test action initial]
      (loop [v initial]
        (if (test v) v (recur (action v)))))
    
    
    -- Haskell Monadology
    untilM :: Monad m => (a -> Bool) -> (a -> m a) -> a -> m a
    untilM p f x = rec $ return x where
    rec mx = do
        x <- mx
        if p x then return x else rec $ f x
    

Clojure comes off as small and well designed, a perfectly acceptable Lisp. Two facets were a little irritating:

  • “Can only recur from tail position” and “Cannot recur from catch/finally”.
  • let still requires a little too much syntax for convenient use.

Yes, Clojure makes let easier than other Lisps:

-- Common Lisp and Scheme let
(let ((x 6) (y 9)) (* x y))

-- Clojure let
(let [x 6 y 9] (* x y))

But I would prefer a macro more like this:

-- with Macro for the Parenthetically Impared
(with x = 6 y = 9 (* x y))

To each their own.


Viewing all articles
Browse latest Browse all 9364

Trending Articles