应用(函子)类型类的简单概括;构造函数上的模式匹配
我一直在尝试通过在线书籍“学习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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这确实有道理,但以目前的形式来看,它最终并不是特别有用。问题正是您所注意到的:没有办法提供默认值来对不同类型执行明智的操作,或者通常从
f
转换为g
。因此,您需要编写的实例数量会呈二次爆炸。您尚未完成
DApplicative
实例。这是Maybe
和[]
的完整实现:它结合了
Maybe
和[]
的行为,因为使用Just
它会执行您所期望的操作,但是使用Nothing
它不会返回任何内容,而是一个空列表。因此,如果您有办法将两个应用程序
f
和g
组合成一个类型,而不是编写采用两种不同类型的DApplicative
,该怎么办?如果您概括此操作,则可以将标准Applicative
与新类型结合使用。这可以通过 Applicatives 的标准表述来完成,
但是让我们更改
Maybe
:现在您只需要一种方法将内部 applicatives
f
中的内容转换为组合的applicativeMaybeT f
:这看起来像很多样板文件,但 ghc 可以自动导出几乎所有内容。
现在您可以轻松地使用组合函数:
Nothing 的这种行为可能会令人惊讶,但如果您将列表视为代表不确定性,您可能会看到它有什么用处。如果您想要返回
Just [a]
或Nothing
的双重行为,您只需要一个转换后的 ListListT Maybe a
。这与我为
DApplicative
编写的实例不太一样。原因在于类型。DApplicative
将f
转换为g
。只有当您知道特定的f
和g
时,这才有可能。为了概括它,结果需要像此实现一样结合f
和g
的行为。所有这些也适用于 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
tog
. 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 forMaybe
and[]
:This combines the behaviors of both
Maybe
and[]
, because withJust
it does what you expect, but withNothing
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 applicativesf
andg
into a single type? If you generalize this action, you could then use a standardApplicative
with the new type.This could be done with the standard formulation of Applicatives as
but instead let's change
Maybe
:Now you just need a way to convert something in the inner applicative,
f
, into the combined applicativeMaybeT f
:This looks like a lot of boilerplate, but ghc can automatically derive nearly all of it.
Now you can easily use the combined functions:
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]
orNothing
, you just need a transformed ListListT Maybe a
.This isn't quite the same as the instance I wrote for
DApplicative
. The reason is because of the types.DApplicative
converts anf
into ag
. That's only possible when you know the specificf
andg
. To generalize it, the result needs to combine the behaviors of bothf
andg
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.您的
Maybe
的DApplicative
实例不完整:如果<*:*>
的第一个参数是Nothing 会发生什么?
每个组合要做什么的选择尚不清楚。对于两个列表
<*>
将生成所有组合:[(+2),(+10)] <*> [3,4]
给出[5,6,13,14]
。对于两个ZipList
,您有类似 zip 的行为:(ZipList [(+2),(+10)]) <*> (ZipList [3,4])
给出[5,14]
。因此,您必须为列表的 DApplicative 和 ZipList 的两种可能行为选择一种,没有“正确”的版本。Your
DApplicative
instance forMaybe
is not complete: What should happen if the first argument of<*:*>
isNothing
?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 twoZipList
s 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 aDApplicative
of a list and aZipList
, there is no "correct" version.