A short gist about multi-argument things. Today is about Prisms.
Let's consider an example type and its autogenerated Prisms.
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 IntBut _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 = review3So 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
BazBut 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 . coercedUsing _Foo' prism, reviewN works fine too:
λ> reviewN _Foo' 42
Foo 42This 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'
Bazbut again, you never know where that could be useful.