Typeclassopedia 提供了以下练习:
这是 Monoidal
和 MyApplicative
:
class Functor f => Monoidal f where
u :: f () -- using `u` rather than `unit`
dotdot :: f a -> f b -> f (a,b) -- using instead of `(**)`
class Functor f => MyApplicative f where
p :: a -> f a -- using instead of `pure`
apply :: f (a -> b) -> f a -> f b -- using instead of `(<**>)`
首先,让我展示类似
Maybe
的数据类型:data Option a = Some a
| None deriving Show
然后,我定义了
instance MyApplicative Option
:instance MyApplicative Option where
p = Some
apply None _ = None
apply _ None = None
apply (Some g) f = fmap g f
最后,我尝试根据
Monoidal Option
的 p
和 apply
来实现 MyApplicative
:instance Monoidal Option where
u = p ()
dotdot None _ = None
dotdot _ None = None
dotdot (Some x) (Some y) = Some id <*> Some (x, y)
这是正确的吗?我用
dotdot
实现的 apply
似乎没有instance Monoidal Option where
u = p ()
dotdot None _ = None
dotdot _ None = None
dotdot (Some x) (Some y) = apply (Some id) (Some (x, y))
特别是,我很好奇如何使用 Applicative 的
dotdot :: f a -> f b -> f (a, b)
正确实现 (<*>)
- 在我的例子中它是 apply
。 最佳答案
Applicative
是 Monoidal
的一个简洁的替代表示。两个类型类是等效的,您可以在两者之间进行转换,而无需考虑特定的数据类型,例如 Option
。 Applicative
的“整洁的替代表示”基于以下两个等效项
pure a = fmap (const a) unit
unit = pure ()
ff <*> fa = fmap (\(f,a) -> f a) $ ff ** fa
fa ** fb = pure (,) <*> fa <*> fb
获得
Applicative
的这种“简洁的替代表示”的技巧与 zipWith
的技巧相同 - 将接口(interface)中的显式类型和构造函数替换为可以传递类型或构造函数以恢复原始接口(interface)的内容。unit :: f ()
替换为
pure
,我们可以将类型 ()
和构造函数 () :: ()
替换为 unit
以恢复 (a,b)
。pure :: a -> f a
pure () :: f ()
同样(虽然不那么简单)将类型
(,) :: a -> b -> (a,b)
和构造函数 liftA2
代入 **
以恢复 Applicative
。liftA2 :: (a -> b -> c) -> f a -> f b -> f c
liftA2 (,) :: f a -> f b -> f (a,b)
<*>
然后通过将函数应用 ($) :: (a -> b) -> a -> b
提升到仿函数中来获得漂亮的 <*>
运算符。(<*>) :: f (a -> b) -> f a -> f b
(<*>) = liftA2 ($)
从
liftA2
返回到 liftA2
很常见,以至于 Control.Applicative
is included in <$>
。 fmap
是中缀 ojit_code 。liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
liftA2 f a b = f <$> a <*> b
关于haskell - 在 Applicative 方面实现 Monoidal,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27262232/