【问题标题】:Problem with pure definition in ApplicativeApplicative 中的纯定义问题
【发布时间】:2019-11-23 03:45:59
【问题描述】:

我是 Haskell 初学者,我必须解决一个工作表问题,这一次我真的被困住了。救命!:))

我必须从函子到 monad 开始计算某些 monad 的实例,但我无法弄清楚 Applicative 实例中的纯。我不想发布程序的所有代码,但我认为以下内容应该能说明问题:

这是给定的代码,不能更改:

class (Semigroup a, Monoid a) => Log a where
    logMsg :: String -> a

newtype Logger l a = Logger (a,l)
    deriving Show

logMsgM :: Log l => String -> Logger l ()
logMsgM s = Logger ((), logMsg s)

这些是我正在处理的实例:

instance (Log l) => Functor (Logger l) where 
    fmap g (Logger (a, l)) = Logger ((g a), l)


instance (Log l) => Applicative (Logger l) where 
    pure ???????????????????
    (<*>) (Logger (g,l)) (Logger (a,_)) = Logger ((g a), l)

instance (Log l) => Monad (Logger l) where 
    return                                              = pure
    (>>=) (Logger (a,l)) g                              = (g a)
    (>>)  x y                                           = x >>= \_ -> y

我被纯粹的卡住了。 pure = Logger 产生无限类型错误 a ~ (a,l) 并且所有使用输入参数进行调整的尝试最终都会导致未知变量错误,或者带有尾随零的类型变量的奇怪类型错误等。我尝试了很多,很难以更明智的方式发布错误消息。 我不明白发生了什么事。这是基于介绍讲座,所以我知道基本概念。我只是不能用函数声明正确处理这个新类型定义(我认为......),特别是这个将两个参数包含为一个(对)。我确实理解(或者更好的措辞:“我可以遵循”)类型定义或新类型中的典型示例,例如 may 、 either 或其他在右侧具有相同数量或更多参数的示例。但是,也许,我对这个假设有误,而且我在我根本看不到的地方犯了一个谬误。

顺便说一句,写pure = return时整个代码编译,但程序产生堆栈溢出(微笑)。我认为这并不奇怪,因为 return = puer 和 pure = return 可以很好地编译但没有意义,对吧!?

感谢帮助:)

【问题讨论】:

    标签: haskell applicative


    【解决方案1】:

    Logger 是一种通常称为Writer monad 的类型。这个想法是,如果你有一个Logger l a,而l 是一个Semigroup,那么如果你运行一个Logger l a,然后运行另一个Logger l b,结果将有两个ls其他(即使用Monoid 操作连接)。那就是:

    Logger (1,"test1") >> Logger (2,"test2")   ==   Logger (2,"test1" <> "test2")
    

    鉴于此,pure a 将只是 Logger (a, something),其中 something 是一个在连接时没有任何效果的值。但现在看看类型类声明:

    class (Semigroup a, Monoid a) => Log a where ...
    instance (Log l) => Applicative (Logger l) where ...
    

    所以在Applicative (Logger l)中,l必须是Monoid,所以它必须有一个标识值mempty!这给了我们pure a = Logger (a, mempty),它只返回a而不影响日志。


    但是,仍然存在问题。如果我们有Logger (a1,l1) &gt;&gt;= \x -&gt; Logger (a2,l2)Logger (a1,l1) &lt;*&gt; Logger (a2,l2),我们希望在结果中连接l1 &lt;&gt; l2。但是您当前的实现并没有这样做!所以你需要改变它来满足这个属性。由于我不想为您解决整个工作表,因此我将其留作练习;但是,如果您遇到困难,您可能需要查阅此答案顶部的链接。

    【讨论】:

    • 非常感谢您的解释,这真的很有帮助。还要感谢您对 >>= 的提示,现在我看不到它,但会尝试自己解决。我总是尽量在没有帮助的情况下完成任务。这就是将概念与实践联系起来的方式,对吧?就像数学一样,你可能知道所有的公理、句子和任何东西,但是,如果没有练习,即使在正确和有意义地应用它们时可能会严重失败。 :)
    • @rebit “我总是尽量在没有帮助的情况下完成任务。”是的,这也是我的理念。这就是为什么我将最终任务设置为练习而​​不是直接提供答案的原因。 (这也意味着你的问题很高兴回答,因为你提供了足够多的先前工作,我可以很容易地看到这个问题。)我对数学的看法也和你一样;尤其是在看课本的时候,我常常把所有的概念都看懂了,但在应用上却一头雾水。
    • @rebit 至于&gt;&gt;=:这个想法是,如果你有一个Logger (a1,l1)(记录l1),然后在它后面加上Logger (a2,l2)(记录@987654349 @),组合值应该是记录l1 然后记录l2 的值。无论您将这些值与&lt;*&gt;&gt;&gt;= 还是&gt;&gt; 结合起来,这都应该成立(因此不仅仅是&gt;&gt;=;您实际上必须重做所有这些函数)。要组合这些值,您可以使用Semigroup 运算符&lt;&gt;(这就是Semigroup 的用途)。 (因为我的字符不多了,所以在下一条评论中继续……)
    • @rebit ... 这个操作员的身份当然是mempty(由Monoid 法律定义),所以不添加到日志中的值是Logger (a,mempty) —这是pure 的定义,因此对我在回答中以这种方式定义它的原因提供了额外的解释。
    • 非常感谢!纯适应您编写的内容并将 的定义更改为 "() (Logger (g,l1)) (Logger (x,l2)) = Logger ((gx), (l1 l2) )”。似乎工作:D。而且我认为,我明白了要在此处实现的日志记录(斐波那契递归):当 LogMsgM 输出“Logger ((),"logging string")”时,在 do 块中递归调用 LogMsgM,只是日志记录字符串与 >> 连接(do 中没有“左箭头”),其方式在 中定义。 >>= 从上下文中获取(数字)值,然后将其馈入下一个递归循环。 (下一个...)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-02
    • 2011-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多