Haskell “写作帮助”;“共线主义者莫纳德”;(习语介绍论文中的练习)

Haskell “写作帮助”;“共线主义者莫纳德”;(习语介绍论文中的练习),haskell,monads,idioms,Haskell,Monads,Idioms,我正在阅读Conor McBride和Ross Paterson的《功能性珍珠/习语:带效果的应用程序编程》(新版本,标题中有“习语”)。我在练习4中遇到了一点困难,如下所述。任何提示都将不胜感激(特别是:我是否应该开始编写fmap和join或return和>=?) 问题陈述 您想创建一个实例Monad[]其中 return x = repeat x 和ap=zapp 标准库函数 如第页所示。在本文的第2部分,ap将一元函数值应用于一元值 ap :: Monad m => m (s -&

我正在阅读Conor McBride和Ross Paterson的《功能性珍珠/习语:带效果的应用程序编程》(新版本,标题中有“习语”)。我在练习4中遇到了一点困难,如下所述。任何提示都将不胜感激(特别是:我是否应该开始编写
fmap
join
return
>=
?)

问题陈述 您想创建一个
实例Monad[]
其中

return x = repeat x
ap=zapp

标准库函数 如第页所示。在本文的第2部分,
ap
将一元函数值应用于一元值

ap :: Monad m => m (s -> t) -> m s -> m t
ap mf ms = do
    f <- mf
    s <- ms
    return (f s)
列表特定函数
zapp
(“zippy应用程序”)将一个列表中的函数应用于另一个列表中的对应值,即

zapp (f:fs) (s:ss) = f s : zapp fs ss
我的困难 请注意,在扩展的形式中,
mf::m(a->b)
是本例中的函数列表
[(a->b)]
。因此,在
>>=
的第一个应用程序中,我们有

(f:fs) >>= mu
其中
mu=(\f->(ms>=\s->return(fs))
。现在,我们可以调用
fs>=mu
作为子例程,但这不知道如何删除
ms
的第一个元素。(回想一下,我们希望得到的列表是[f1 s1,f2 s2,…)。我试图破解一些东西,但……正如预测的那样,它不起作用……任何帮助都将不胜感激

提前谢谢

编辑1 我想我成功了;首先我用
fmap
重写了
ap
,并按照用户“comonad”的建议加入
join

我的信念的飞跃是假设
fmap=map
。如果有人能解释如何到达那里,我将非常感激。在此之后,很明显,
join
在用户“comonad”建议的列表上起作用,应该是对角线,
\x->zipWith(!!).unL)x[0..
。我的完整代码是:

newtype L a = L [a] deriving (Eq, Show, Ord)
unL (L lst) = lst
liftL :: ([a] -> [b]) -> L a -> L b
liftL f = L . f . unL

joinL :: L (L a) -> L a
joinL = liftL $ \x -> zipWith ((!!) . unL) x [0..]

instance Functor L where
    fmap f = liftL (map f)

instance Monad L where
    return x = L $ repeat x
    m >>= g = joinL (fmap g m)

希望这是对的(似乎是论文第18页的“解决方案”)……谢谢大家的帮助!

Hm。我忍不住觉得这个练习有点不公平

练习4(肠绞痛患者单子) 虽然
repeat
zapp
不是通常的
Monad[]
实例的
返回
ap
, 它们仍然是替代单子的
return
ap
,更适合于
[]
的共导解释。什么是
连接:[[x]]→ [x] 
这个单子的名字

评论一下这个单子的
ap
和我们的
zapp
的相对效率

首先,我相当肯定所讨论的monad实例对
[]
一般来说是无效的。当他们说“共导解释”时,我怀疑这指的是无限列表。在某些情况下,该实例实际上对有限列表有效,但通常对任意列表无效

这是第一个非常一般的提示——为什么monad实例只对某些列表有效,特别是无限列表

这里是您的第二个提示:
fmap
return
在本文前面给出的其他定义中是微不足道的。您已经有了
return
fmap
只是稍微不那么明显

此外,
(>>=)
与任何
Monad
一样,在其他功能方面都有一个简单的实现,这使得
join
成为问题的关键。在大多数情况下,
(>=)
对于编程来说更为自然,但是
join
在概念上更为基础,在这种情况下,我认为更易于分析。因此我建议现在就开始进行这项工作,忘记
(>=)
。一旦有了实现,就可以返回并重新构建
(>>=)
并检查monad法则,确保其正常工作


最后,假设您有可用的
fmap
,但没有其他内容。给定类型为
[a->b]
[a]
的值,您可以将它们组合在一起,得到类型为
[[b]]
。这里的
连接类型是
[[a]->[a]
。您如何编写
join
,从而在这里获得与在原始值上使用
zapp
相同的结果?请注意,关于相对效率的问题以及一个问题是关于实现的线索。

单子的最小完整定义是
fmap
+
return
+
加入
返回
+
(>>=)
。您可以将一个与另一个一起实现:

(>>=) :: Monad m => m a -> (a->m b) -> m b
(>>=) ma amb = join $ fmap amb ma

fmap :: Monad m => (a->b) -> m a -> m b
fmap f ma = ma >>= (return . f)
join :: Monad m => m (m a) -> m a
join mma = mma >>= id
现在,可以根据
join
fmap
重写
ap
的实现:

ap :: Monad m => m (a->b) -> m a -> m b
ap mf ma = do
    f <- mf
    a <- ma
    return (f a)
ap mf ma = do
    f <- mf
    fmap f ma
ap mf ma = join $ fmap (flip fmap ma) mf

我只是想我应该澄清一下,标题中有练习和“习语”的版本是一篇较早的论文草稿,最终出现在JFP上。当时,我错误地认为共线性(我指的可能是无限的,可能是有限的列表)在某种程度上,我们是一个单子,与扎普相对应:有一个看似合理的候选人加入(在其他答案中提到),但杰里米·吉本斯(Jeremy Gibbons)好心地向我们指出,它不符合单子定律。反例包括“参差不齐”具有不同有限长度的列表列表。相应地,在JFP文章中,我们得到了更正。(我们对此相当满意,因为我们喜欢找到()不是单子的ap的应用程序函子。)

必要的无限列表(即流),通过排除不规则的情况,确实形成了一个单子,其ap行为类似于zapp。请注意,流x与Nat->x同构


我为这一混乱表示歉意。有时把旧的、未完成的草稿(充满错误)放在网上(哈哈)是很危险的。

@camccann:有一点我不清楚。如果
ap :: Monad m => m (a->b) -> m a -> m b
ap mf ma = do
    f <- mf
    a <- ma
    return (f a)
ap mf ma = do
    f <- mf
    fmap f ma
ap mf ma = join $ fmap (flip fmap ma) mf
ap [f1,f2,f3...] [1,2,3...] = join $ fmap (flip fmap [1,2,3...]) [f1,f2,f3...]
                            = join $ [ [(f1 1), f1 2 , f1 3 ...]
                                     , [ f2 1 ,(f2 2), f2 3 ...]
                                     , [ f3 1 , f3 2 ,(f3 3)...]
                                     ...
                                     ]
                            = [(f1 1)
                              ,     (f2 2)
                              ,          (f3 3)
                              ...
                              ]