Reputation: 796
Let's say I have a convention in Haskell where I define a series of functions like this:
data Node = MkNode
s0 :: Node -> s -> Node
s0 a _ = a
s1 :: (s -> a) -> (a -> Node) -> s -> Node
s1 a b c = b (a c)
s2 :: (s -> a) -> (s -> b) -> (a -> b -> Node) -> s -> Node
s2 a b c d = c (a d) (b d)
s3 :: (s -> a) -> (s -> b) -> (s -> c) -> (a -> b -> c -> Node) -> s -> Node
s3 a b c d e = d (a e) (b e) (c e)
If possible, I would love to define a function sn
that takes a variable number of arguments, always with this pattern. I've seen this sort of thing done before using typeclasses, but I can't quite figure out how to do it in this case. For example, I can imagine:
class NAble elt where
sn :: elt -> state -> Node
instance NAble Node where
sn elt _ = elt
But then I'm stuck. I'm not sure what the recursive definition would be. Perhaps something like:
instance (NAble b) => NAble (a -> b) where
sn eltMaker state = ss (eltMaker state) state
But that's obviously not quite right. Not sure if this is possible, but it would be cool if it were. Of course the order of the arguments can change if that helps get it right, but it'd be really nice to get this to work. Any help would be appreciated!
Upvotes: 2
Views: 86
Reputation: 152707
If you put the arguments in a slightly different order -- with the s
argument coming first, and the Node
-constructing function second -- it gets a lot easier. Then a type family will fix you right up:
{-# LANGUAGE TypeFamilies #-}
data Node = MkNode
class NAble t where
type Ret t s
sn :: s -> t -> Ret t s
instance NAble Node where
type Ret Node s = Node
sn s mkNode = mkNode
instance NAble t => NAble (a -> t) where
type Ret (a -> t) s = (s -> a) -> Ret t s
sn s mkNode fa = sn s (mkNode (fa s))
But let me also recommend an alternative. Look to the pattern the standard library uses:
pure :: Applicative f => ( t) -> f t
fmap :: Applicative f => (a -> t) -> f a -> f t
liftA2 :: Applicative f => (a -> b -> t) -> f a -> f b -> f t
liftA3 :: Applicative f => (a -> b -> c -> t) -> f a -> f b -> f c -> f t
Taking f~(->) s
and t~Node
, we get:
pure :: ( Node) -> s -> Node
fmap :: (a -> Node) -> (s -> a) -> s -> Node
liftA2 :: (a -> b -> Node) -> (s -> a) -> (s -> b) -> s -> Node
liftA3 :: (a -> b -> c -> Node) -> (s -> a) -> (s -> b) -> (s -> c) -> s -> Node
What if people who use the standard library need liftA4
or higher? Typically they will then switch to a chain of (<*>)
uses instead:
(<*>) :: (s -> a -> Node) -> (s -> a) -> s -> Node
(f <*> g) s = f s (g s)
{-# MAKE_THE_PROGRAMMER_INLINE liftAn #-}
liftAn mkNode f1 f2 ... fn = pure mkNode
<*> f1
<*> f2
...
<*> fn
Upvotes: 3