A short gist about multi-argument things. Today is about Prisms.
Let's consider an example type and its autogenerated Prism
s.
data FooBarBaz = Foo Int | Bar Int Char | Baz
deriving Show
$(makePrisms ''FooBarBaz)
The _Foo
is a nice Prism' FooBarBaz Int
. For example, we can use it to construct FooBarBaz
λ> _Foo # 42
Foo Int
But _Bar
is unpleasant Prism' FooBarBaz (Int, Char)
. We have to tuple
λ> _Bar # (42, 'f')
Bar 42 'f'
Can we do better?
We can write helpers
review2 :: AReview t (b1, b2) -> b1 -> b2 -> t
review2 l b1 b2 = review l (b1, b2)
review3 :: AReview t (b1, b2, b3) -> b1 -> b2 -> b3 -> t
review3 l b1 b2 b3 = review l (b1, b2, b3)
but those aren't satisfying.
Luckily, Prism
carries "amount of arguments", so we can write a a quite simple type class to abstract over review2
, review3
...
class ReviewN tuple where
type ReviewNArgs tuple t :: *
reviewN :: AReview t tuple -> ReviewNArgs tuple t
instance ReviewN (b1, b2) where
type ReviewNArgs (b1, b2) t = b1 -> b2 -> t
reviewN = review2
instance ReviewN (b1, b2, b3) where
type ReviewNArgs (b1, b2, b3) t = b1 -> b2 -> b3 -> t
reviewN = review3
So now we can write nicer auto-uncurried code:
λ> reviewN _Bar 42 'f'
Bar 42 'f'
That's an improvement
Note that if we try to define an infix operator:
(##) :: ReviewN tuple => AReview t tuple -> ReviewNArgs tuple t
(##) = reviewN
infixr 8 ##
it won't work as we want. We'd need a fixity tighter than juxtaposition. :(
λ> (_Bar ## 42) 'f'
Bar 42 'f'
Let's not forget the nullary constructor Baz
. It generates _Baz :: Prism' FooBarBaz ()
.
Our ReviewN
can easily handle this too: ()
is an empty tuple:
instance ReviewN () where
type ReviewNArgs () t = t
reviewN = flip review ()
λ> reviewN _Baz
Baz
But how about the very nice _Foo
. There are No instance for (ReviewN Int) arising from a use of ‘reviewN’
. Do we have to switch between review
and reviewN
?
One solution is to use Only
(or Identity
or ...)
instance ReviewN (Only b) where
type ReviewNArgs (Only b) t = b -> t
reviewN l = review l. Only
_Foo' :: Prism' FooBarBaz (Only Int)
_Foo' = _Foo . coerced
Using _Foo'
prism, reviewN
works fine too:
λ> reviewN _Foo' 42
Foo 42
This is a very small example of somewhat generic programming. I think this (obvious?) trick might be useful in DSLs where "functions" of the DSL can have multiple inputs (or outputs!) - there we can choose to wrap single parameters in Only
, which would allow to abstracti over arity.
Prism
(and optics) happen to be quite general DSL ;)
Also I'm pretty sure that this can be made work with generic-lens
machinery, so in a single argument case (Only b)
is generated, so we could write:
reviewNCtor @"Foo" 42
reviewNCtor @"Bar" 42 'f'
reviewNCtor @"Baz"
Which is silly as we can just
Foo 42
Bar 42 'f'
Baz
but again, you never know where that could be useful.