Search code examples
haskellclojurehaskell-lenslensesdestructuring

What are the similarities and differences between a lens in Haskell and destructuring in Clojure?


Assumptions:

  • I want to parse a nested JSON String.
  • I know that Haskell encourages solving problems with a type system, and Clojure eschews a type system, preferring to solve the problem with data structures.
  • I'm aware that in both languages this process is referred to as destructuring - but it doesn't have another name in Clojure, whereas in Haskell this is also called using a lens, so I'm calling the Clojure one destructuring, and moving on

We can see that we can create a lens like this in Haskell:

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

initialState :: Game
initialState = Game
    { _score = 0
    , _units =
        [ Unit
            { _health = 10
            , _position = Point { _x = 3.5, _y = 7.0 }
            }
        ]
    }

health :: Lens' Unit Int
health = lens _health (\unit v -> unit { _health = v })

It's purpose is to get the health value from the game data structure.

We can do destructuring like this in Clojure:

user=> (def book {:name "SICP" :details {:pages 657 :isbn-10 "0262011530"}})
#'user/book

user=> (let [{name :name {pages :pages isbn-10 :isbn-10} :details} book]
     (println "name:" name "pages:" pages "isbn-10:" isbn-10))
name: SICP pages: 657 isbn-10: 0262011530

The purpose is to get the nested values for name, pages etc from the nested structure.

In both cases you're coming up with a mechanism to reusably retrieve values from a nested structure.

My question is: What are the similarities and differences between a lens in Haskell and destructuring in Clojure?


Solution

  • Your Clojure code would most closely be expressed like this in Haskell:

    book :: Book
    book = Book {name = "SICP", details = Details {pages = 657, isbn_10: "0262011530"}}
    
    main = do
      let Book {name = name, details = Details {pages = pages, isbn_10 = isbn_10}} = book
      putStrLn $ "name:" ++ name ++ "pages:" ++ (show pages) ++ "isbn-10:" ++ isbn-10
    

    Here we used Haskell's pattern matching, which is basically a more general form of Clojure's destructuring bind, to get at the contents of the record. This has nothing to do with lenses.

    Now let's look at your Haskell code with lenses. This code has three record types, Game, Unit and Point, and those types each have an automatically-defined set of functions to access their members without pattern matching. These functions are called _score, _health, _x and so on (just like the member names). They take a Game, Unit or Point respectively and produce the value of their respective member. If these functions would have to be defined manually (which they don't because Haskell defines such functions for all record types automatically), it'd look like this:

    _health (Unit {health = h}) = h
    

    So far this still has nothing to do with lenses.

    The lens in the code is health. It contains the function _health (which, as described, takes a Unit and produces its health) and the function (\unit v -> unit { _health = v }), which takes a unit and a value and produces a new unit whose health has been set to that value. In other words the lens contains the code to both get and set the health of a unit. The Lens library now contains various functions that you can use to compose and work with such lenses.

    So to answer your question: There are no similarities between lenses and destructuring.