Applicative(Functor)类型的简单推广; 构造函数上的模式匹配

Aky*_*Aky 6 haskell functional-programming pattern-matching functor

我一直试图通过在线书籍LYAH "向我学习一个Haskell" .

作者描述了Applicative类型的Functors的行为,它具有从一个仿函数中提取函数并将其映射到第二个仿函数的能力; 这是通过为Applicative类类声明的<*>函数:

class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b  
Run Code Online (Sandbox Code Playgroud)

举个简单的例子,在下面的实现中,Maybe类型是Applicative的一个实例:

    instance Applicative Maybe where  
    pure = Just  
    Nothing <*> _ = Nothing  
    (Just f) <*> something = fmap f something  
Run Code Online (Sandbox Code Playgroud)

前面提到的行为的一个例子:

ghci> Just (*2) <*> Just 10         -- evaluates to Just 20
Run Code Online (Sandbox Code Playgroud)

所以<*>运算符从第一个操作数"提取"(*2)函数并将其映射到第二个操作数.

现在在Applicative类型中,<*>的两个操作数都属于同一类型,所以我认为为什么不尝试实现这种行为的泛化,其中两个操作数是不同类型的Functors,所以我可以评估这样的东西:

Just (2*) <*:*> [1,2,3,4]  -- should evaluate to [2,4,6,8]
Run Code Online (Sandbox Code Playgroud)

所以这就是我提出的:

import Control.Applicative

class (Applicative f, Functor g) => DApplicative f g where
    pure1 :: a -> f a
    pure1 = pure
    (<*:*>) :: f ( a -> b )  -> g a -> g b      -- referred below as (1)

instance DApplicative Maybe [] where    -- an "instance pair" of this class
    (Just func) <*:*> g = fmap func g

main = do putStrLn(show x)
    where x = Just (2*) <*:*> [1,2,3,4]  -- it works, x equals [2,4,6,8]
Run Code Online (Sandbox Code Playgroud)

现在,虽然上述工作,但我想知道我们能否做得更好; 是否可以在DApplicative fg本身的声明中为<*:*>提供可应用于各种f&g对的默认实现?这引出了以下问题:是否有一种方法可以在不同数据类型的构造函数上进行模式匹配?

我希望我的问题有道理,而且我不只是在胡说八道(如果我是,请不要太苛刻;我只是一个FP初学者,在他的睡觉时间之后......)

Joh*_*n L 5

这确实有意义,但最终在目前的形式上并不是特别有用.问题正是你所注意到的:没有办法提供一个默认的东西,它可以用不同的类型做出明智的事情,或者通常转换fg.因此,您需要编写的实例数量会出现二次爆炸.

你没有完成DApplicative实例.这是一个完整的实现Maybe[]:

instance DApplicative Maybe [] where    -- an "instance pair" of this class
    (Just func) <*:*> g = fmap func g
    Nothing     <*:*> g = []
Run Code Online (Sandbox Code Playgroud)

这两者相结合的行为Maybe[],因为Just它做你所期望的,但Nothing它没有返回,一个空列表.

因此,而不是写DApplicative它有两个不同的类型,如果你有两个applicatives相结合的方法f,并g成一个单一的类型?如果您概括此操作,则可以使用Applicative新类型的标准.

这可以使用Applicatives的标准配方来完成

liftAp :: f (g (a -> b)) -> f (g a) -> f (g b)
liftAp l r = (<*>) <$> l <*> r
Run Code Online (Sandbox Code Playgroud)

但改为让我们改变Maybe:

import Control.Applicative

newtype MaybeT f a = MaybeT { runMaybeT :: f (Maybe a) }

instance (Functor f) => Functor (MaybeT f) where
    fmap f (MaybeT m) = MaybeT ((fmap . fmap) f m)

instance (Applicative f) => Applicative (MaybeT f) where
    pure a = MaybeT (pure (pure a))
    (MaybeT f) <*> (MaybeT m) = MaybeT ( (<*>) <$> f <*> m)
Run Code Online (Sandbox Code Playgroud)

现在,您只需要一种方法将内部应用程序中的某些内容f转换为组合应用程序MaybeT f:

lift :: (Functor f) => f a -> MaybeT f a
lift = MaybeT . fmap Just
Run Code Online (Sandbox Code Playgroud)

这看起来像很多样板,但ghc可以自动导出几乎所有的样板.

现在您可以轻松使用组合功能:

*Main Control.Applicative> runMaybeT $ pure (*2) <*> lift [1,2,3,4]
[Just 2,Just 4,Just 6,Just 8]

*Main Control.Applicative> runMaybeT $ MaybeT (pure Nothing) <*> lift [1,2,3,4]
[Nothing,Nothing,Nothing,Nothing]
Run Code Online (Sandbox Code Playgroud)

Nothing的这种行为可能会令人惊讶,但如果你认为列表代表了不确定性,你可能会看到它如何有用.如果你想要么返回的双重行为,Just [a]或者Nothing,你只需要一个转化列表ListT Maybe a.

这与我写的实例并不完全相同DApplicative.原因是因为类型. DApplicative将一个转换f成一个g.只有当你知道具体的fg.为了概括它,结果需要结合两者的行为,f并且正g如此实现那样.

所有这些都适用于Monads.转换类型例如MaybeT由monad变换器库(例如mtl)提供.