🎓 A1Learner
🔍
0 of 47 lessons completed

Haskell Van Laarhoven Lens

A Van Laarhoven lens is a function that works for every functor. It takes a function on the focused part and returns a function on the whole value.

Viewing uses Const

viewL runs firstL with Const as the functor. Const keeps the focused value, so viewing returns 3.

{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
  fmap _ (Const x) = Const x
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
  fmap f (Identity x) = Identity (f x)
type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s
data Pair = Pair Int Int deriving Show
firstL :: Lens Pair Int
firstL f (Pair x y) = fmap (\x2 -> Pair x2 y) (f x)
viewL :: Lens s a -> s -> a
viewL l s = unConst (l Const s)
main :: IO ()
main = print (viewL firstL (Pair 3 4))

Setting uses Identity

setL runs firstL with Identity as the functor. Identity carries the new value back into the pair.

{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
  fmap _ (Const x) = Const x
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
  fmap f (Identity x) = Identity (f x)
type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s
data Pair = Pair Int Int deriving Show
firstL :: Lens Pair Int
firstL f (Pair x y) = fmap (\x2 -> Pair x2 y) (f x)
setL :: Lens s a -> a -> s -> s
setL l v s = runIdentity (l (\_ -> Identity v) s)
main :: IO ()
main = print (setL firstL 9 (Pair 3 4))

The same lens can update a part

overL runs firstL with Identity and a function that adds ten. The second element stays 4.

{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
  fmap _ (Const x) = Const x
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
  fmap f (Identity x) = Identity (f x)
type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s
data Pair = Pair Int Int deriving Show
firstL :: Lens Pair Int
firstL f (Pair x y) = fmap (\x2 -> Pair x2 y) (f x)
overL :: Lens s a -> (a -> a) -> s -> s
overL l g s = runIdentity (l (Identity . g) s)
main :: IO ()
main = print (overL firstL (+10) (Pair 3 4))

Example

{-# LANGUAGE RankNTypes #-}
newtype Const a b = Const { unConst :: a }
instance Functor (Const a) where
  fmap _ (Const x) = Const x
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
  fmap f (Identity x) = Identity (f x)
type Lens s a = forall f. Functor f => (a -> f a) -> s -> f s
data Pair = Pair Int Int deriving Show
firstL :: Lens Pair Int
firstL f (Pair x y) = fmap (\x2 -> Pair x2 y) (f x)
secondL :: Lens Pair Int
secondL f (Pair x y) = fmap (\y2 -> Pair x y2) (f y)
viewL :: Lens s a -> s -> a
viewL l s = unConst (l Const s)
setL :: Lens s a -> a -> s -> s
setL l v s = runIdentity (l (\_ -> Identity v) s)
overL :: Lens s a -> (a -> a) -> s -> s
overL l g s = runIdentity (l (Identity . g) s)
main :: IO ()
main = do
  print (viewL secondL (Pair 3 4))
  print (setL firstL 10 (Pair 3 4))
  print (overL secondL (*10) (Pair 3 4))

Key points

  • Van Laarhoven lens: a function that takes a part function and returns a whole function for every functor f.
  • Functor: a type with fmap that applies a function inside its wrapper.
  • Const: a tiny wrapper that keeps a value and ignores the function given to fmap.
  • Identity: a tiny wrapper that holds exactly one value and applies fmap normally.
  • View: viewing uses Const so the focused value is kept and returned.
  • Set: setting uses Identity so the new value is placed back into the whole value.

In this example, viewL reads the second element, setL replaces the first element with 10, and overL multiplies the second element by ten, all through the same Van Laarhoven shape.

Note

the same lens serves both viewing and setting because the caller picks the functor.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Haskell Playground

Exercise

Which functor does viewing through a Van Laarhoven lens use?