首页 > 解决方案 > 在 Haskell 中为可变参数模式使用类型类

问题描述

假设我在 Haskell 中有一个约定,我在其中定义了一系列这样的函数:


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)

如果可能的话,我很想定义一个sn带有可变数量参数的函数,总是使用这种模式。在使用类型类之前,我已经看到过这种事情,但我不太清楚在这种情况下如何去做。例如,我可以想象:

class NAble elt where
    sn :: elt -> state -> Node

instance NAble Node where
    sn elt _ = elt

但后来我被困住了。我不确定递归定义是什么。也许是这样的:

instance (NAble b) => NAble (a -> b) where
    sn eltMaker state = ss (eltMaker state) state

但这显然不太对。不确定这是否可能,但如果是的话,那就太酷了。当然,如果这有助于使其正确,参数的顺序可以改变,但让它工作真的很好。任何帮助,将不胜感激!

标签: haskellrecursiontypeclass

解决方案


如果您将参数以稍微不同的顺序排列——s参数首先出现,然后是Node-constructing 函数——它会变得容易得多。然后一个类型系列将立即解决您的问题:

{-# 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))

但让我也推荐一个替代方案。查看标准库使用的模式:

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

f~(->) st~Node,我们得到:

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

如果使用标准库的人需要liftA4或更高怎么办?通常,他们会转而使用一系列(<*>)用途:

(<*>) :: (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

推荐阅读