Type-level lenses using singletons https://github.com/mstksg/lens-typelevel#readme

Version on this page:
Stackage Nightly 2018-12-15:
Latest on Hackage:

See all snapshots lens-typelevel appears in

BSD3 licensed by Justin Le
Maintained by justin@jle.im

Module documentation for

There are no documented modules for this package.


lens-typelevel on Hackage Build Status

(Rendered off-hackage documentation)

van Laarhoven lenses at the type level using singletons defunctionalization.

ghci> :kind! '("hello", 6 ) & L1_ .~ 'True
'( 'True, 6 )

ghci> :kind! '("hello", 6 ) ^. L2_

ghci> :kind! '("hello", 6 ) ^. To_ SndSym0

ghci> :kind! '("hello", 'True ) & L2_ %~ NotSym0
'("hello", 'False )

ghci> :kind! '[ 'True, 'False, 'False ] & Traverse_ %~ NotSym0
'[ 'False, 'True, 'True ]

ghci> :kind! '("hello", '(6, 'False ) ) ^. L2_ .@ L1_

ghci> type TestList = '[ '("hello", 'True), '("world", 'False), '("curry", 'False)]
ghci> :kind! TestLst ^.. Traverse_ .@ L1_
'["hello", "world", "curry"]

ghci> :kind! '[] ^?! Traverse_
Error "Failed indexing into empty traversal"

ghci> :kind! '["hello", "world", "curry"] & IxList_ ('S 'Z) .~ "haskell"
'["hello", "haskell", "curry"]

It’s pretty much the exact same representation as the lens library; it’s essentially an API-faithful port with the same representation and essentially the same implementation. We even have CloneLens_ and CloneTraversal_ implemented using type-level versions of Context and Bazaar:

ghci> type CloneExample l   = ('( 'True, 'False ) & CloneLens_ l %~ NotSym0)
                                                  ^. CloneLens_ l
ghci> :kind! CloneExample L1_
ghci> :kind! CloneExample L2_

Using prefix function names:

ghci> :kind! Set  L1_       'True        '("hello", 6     )
'( 'True, 6 )

ghci> :kind! View L2_                    '("hello", 6     )

ghci> :kind! View (To_ SndSym0)          '("hello", 6     )

ghci> :kind! Over L2_       NotSym0      '("hello", 'True )
'("hello", 'False )

ghci> :kind! Over Traverse_ NotSym0      '[ 'True, 'False, 'False ]
'[ 'False, 'True, 'True ]

ghci> :kind! View (L2_ .@ L1_)           '("hello", '(6, 'False ) )

ghci> type TestList = '[ '("hello", 'True), '("world", 'False), '("curry", 'False)]
ghci> :kind! ToListOf (Traverse_ .@ L1_) TestList
'["hello", "world", "curry"]

ghci> :kind! UnsafePreview Traverse_     '[]
Error "Failed indexing into empty traversal"

ghci> :kind! Set (IxList_ ('S 'Z)) "haskell" '["hello", "world", "curry"]
'["hello", "haskell", "curry"]

Defining lenses

There are two main ways to define optics.

First, you can write them by hand using singletonsOnly:

$(singletonsOnly [d|
  l1 :: Functor f => LensLike (a, c) (b, c) a b
  l1 f (x, y) = (\x' -> (x', y)) <$> f x

  l1Alt :: Functor f => LensLike (a, c) (b, c) a b
  l1Alt = mkLens fst (\(_, y) x -> (x', y))

  getFirst :: Getting a (a, b) a
  getFirst = to fst

This creates the type families L1, L1Alt, and GetFirst; however, these aren’t lenses, because they aren’t partially applied. The lactual lenses are L1Sym0, L1AltSym0, and GetFirstSym0. As a convention, I recommend aliasing the actual lenses with an underscore suffix:

-- L1_       :: Functor f => LensLike f (a, c) (b, c) a b
type L1_       = L1Sym0

-- L1Alt_    :: Functor f => LensLike f (a, c) (b, c) a b
type L1Alt     = L1AltSym0

-- GetFirst_ :: Getting a (a, b) a
type GetFirst_ = GetFirstSym0

The number after the Sym is determined by how many arguments you need to apply to your function before you get to the actual lens. For example, IxList requires one argument (the index) to get to the actual traversal, so the definition in the library is:

type IxList_ i = IxListSym1 i

Second, you can write them directly at the type level using combinators like MkLens_ and To_:

type GetFirst_ = To_ FstSym0

(FstSym0 is the promotion of fst from the singletons library)




October 29, 2018


  • Infix singletons mirror aliases of infix type aliases (%^., etc.)


October 28, 2018


  • Instances for N.
  • Export value-level mkLens.
  • Extensive documentation additions.


October 26, 2018


  • Initial release.
Depends on 2 packages:
Used by 1 package:
comments powered byDisqus