应用(函子)类型类的简单概括;构造函数上的模式匹配

发布于 2024-12-03 15:03:07 字数 1686 浏览 1 评论 0原文

我一直在尝试通过在线书籍“学习Haskell”LYAH

作者将应用类型函子的行为描述为能够从一个函子中提取函数并将其映射到第二个函子上;这是通过<*>为 Applicative 类型类声明的函数:

class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b  

作为一个简单的示例,Maybe 类型是 Applicative 的实例,其实现如下:

    instance Applicative Maybe where  
    pure = Just  
    Nothing <*> _ = Nothing  
    (Just f) <*> something = fmap f something  

前面提到的行为示例:

ghci> Just (*2) <*> Just 10         -- evaluates to Just 20

因此 <*>运算符从第一个操作数“提取”(*2) 函数并将其映射到第二个操作数。

现在,在应用类型中,<*> 的两个操作数都为是相同类型的,所以我想作为一个练习,为什么不尝试实现这种行为的泛化,其中两个操作数是不同类型的函子,所以我可以评估这样的东西:

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

所以这就是我想出的:

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]

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

我希望我的问题有一定道理,我不只是在胡言乱语(如果我是这样,请不要太严厉;我只是一个 FP 初学者,已经过了就寝时间了……)

I've been trying to "learn me a Haskell" through the online book LYAH.

The author describes the behaviour of Functors of the Applicative type as sort of having the ability to extract a function from one functor and mapping it over a second functor; this is through the <*> function declared for the Applicative type class:

class (Functor f) => Applicative f where  
    pure :: a -> f a  
    (<*>) :: f (a -> b) -> f a -> f b  

As a simple example, the Maybe type is an instance of Applicative under the following implementation:

    instance Applicative Maybe where  
    pure = Just  
    Nothing <*> _ = Nothing  
    (Just f) <*> something = fmap f something  

An example of the behaviour mentioned previously:

ghci> Just (*2) <*> Just 10         -- evaluates to Just 20

so the <*> operator "extracts" the (*2) function from the first operand and maps it over the second operand.

Now in Applicative types, both operands of <*> are of the same type, so I thought as an exercise why not try implementing a generalisation of this behaviour, where the two operands are Functors of different types, so I could evaluate something like this:

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

So this is what I came up with:

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]

Now, although the above works, I'm wondering if we can do better; is it possible to give a default implementation for <*:*> that can be applied to a variety of f & g pairs, in the declaration for DApplicative f g itself? And this leads me to the following question: Is there a way to pattern match on constructors across different data types?

I hope my questions make some sense and I'm not just spewing nonsense (if I am, please don't be too harsh; I'm just an FP beginner up way past his bedtime...)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

看海 2024-12-10 15:03:07

这确实有道理,但以目前的形式来看,它最终并不是特别有用。问题正是您所注意到的:没有办法提供默认值来对不同类型执行明智的操作,或者通常从 f 转换为 g。因此,您需要编写的实例数量会呈二次爆炸。

您尚未完成 DApplicative 实例。这是 Maybe[] 的完整实现:

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

​​它结合了 Maybe[] 的行为,因为使用 Just 它会执行您所期望的操作,但是使用 Nothing 它不会返回任何内容,而是一个空列表。

因此,如果您有办法将两个应用程序 fg 组合成一个类型,而不是编写采用两种不同类型的 DApplicative ,该怎么办?如果您概括此操作,则可以将标准 Applicative 与新类型结合使用。

这可以通过 Applicatives 的标准表述来完成,

liftAp :: f (g (a -> b)) -> f (g a) -> f (g b)
liftAp l r = (<*>) <
gt; l <*> r

但是让我们更改 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 ( (<*>) <
gt; f <*> m)

现在您只需要一种方法将内部 applicatives f 中的内容转换为组合的applicative MaybeT f

lift :: (Functor f) => f a -> MaybeT f a
lift = MaybeT . fmap Just

这看起来像很多样板文件,但 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]

Nothing 的这种行为可能会令人惊讶,但如果您将列表视为代表不确定性,您可能会看到它有什么用处。如果您想要返回 Just [a]Nothing 的双重行为,您只需要一个转换后的 List ListT Maybe a

这与我为 DApplicative 编写的实例不太一样。原因在于类型。 DApplicativef 转换为 g。只有当您知道特定的 fg 时,这才有可能。为了概括它,结果需要像此实现一样结合 fg 的行为。

所有这些也适用于 Monad。诸如 MaybeT 之类的转换类型由 monad 转换器库提供,例如 mtl

This does make sense, but it ends up being not particularly useful in its current form. The problem is exactly what you've noticed: there is no way to provide a default which does sensible things with different types, or to generally convert from f to g. So you'd have a quadratic explosion in the number of instances you'd need to write.

You didn't finish the DApplicative instance. Here's a full implementation for Maybe and []:

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

This combines the behaviors of both Maybe and [], because with Just it does what you expect, but with Nothing it returns nothing, an empty list.

So instead of writing DApplicative which takes two different types, what if you had a way of combining two applicatives f and g into a single type? If you generalize this action, you could then use a standard Applicative with the new type.

This could be done with the standard formulation of Applicatives as

liftAp :: f (g (a -> b)) -> f (g a) -> f (g b)
liftAp l r = (<*>) <
gt; l <*> r

but instead let's change 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 ( (<*>) <
gt; f <*> m)

Now you just need a way to convert something in the inner applicative, f, into the combined applicative MaybeT f:

lift :: (Functor f) => f a -> MaybeT f a
lift = MaybeT . fmap Just

This looks like a lot of boilerplate, but ghc can automatically derive nearly all of it.

Now you can easily use the combined functions:

*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]

This behavior at Nothing may be surprising, but if you think of a list as representing indeterminism you can probably see how it could be useful. If you wanted the dual behavior of returning either Just [a] or Nothing, you just need a transformed List ListT Maybe a.

This isn't quite the same as the instance I wrote for DApplicative. The reason is because of the types. DApplicative converts an f into a g. That's only possible when you know the specific f and g. To generalize it, the result needs to combine the behaviors of both f and g as this implementation does.

All of this works with Monads too. Transformed types such as MaybeT are provided by monad transformer libraries such as mtl.

稚然 2024-12-10 15:03:07

您的 MaybeDApplicative 实例不完整:如果 <*:*> 的第一个参数是 Nothing 会发生什么?

每个组合要做什么的选择尚不清楚。对于两个列表 <*> 将生成所有组合:[(+2),(+10)] <*> [3,4] 给出 [5,6,13,14]。对于两个 ZipList ,您有类似 zip 的行为: (ZipList [(+2),(+10)]) <*> (ZipList [3,4]) 给出 [5,14]。因此,您必须为列表的 DApplicative 和 ZipList 的两种可能行为选择一种,没有“正确”的版本。

Your DApplicative instance for Maybe is not complete: What should happen if the first argument of <*:*> is Nothing?

The choice what to do for every combination isn't clear. For two lists <*> would generate all combinations: [(+2),(+10)] <*> [3,4] gives [5,6,13,14]. For two ZipLists you have a zip-like behaviour: (ZipList [(+2),(+10)]) <*> (ZipList [3,4]) gives [5,14]. So you have to choose one of both possible behaviours for a DApplicative of a list and a ZipList, there is no "correct" version.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文