代码之家  ›  专栏  ›  技术社区  ›  Robin Zigmond

如何在没有冗余方法的情况下在Purescript中定义typeclass实例

  •  0
  • Robin Zigmond  · 技术社区  · 5 年前

    我正在努力正确地自学Purescript。我目前正在通过 Purescript book

    我想做的具体事情是定义一个 Foldable 的实例 NonEmpty 类型,定义如下:

    data NonEmpty a = NonEmpty a (Array a)
    

    来自哈斯克尔的背景,我知道 foldMap 可折叠的 实例,所以我没有花时间写:

    instance foldableNonEmpty :: Foldable NonEmpty where
        foldMap f (NonEmpty a as) = f a <> foldMap f as
    

    这在Haskell中就足够了,因为所有其他的都有默认值 可折叠的 方法 折叠式地图 .

      The following type class members have not been implemented:
      foldr :: forall a b. (a -> b -> b) -> b -> ... -> b
      foldl :: forall a b. (b -> a -> b) -> b -> ... -> b
    
    in type class instance
    
      Data.Foldable.Foldable NonEmpty
    

    这让我大吃一惊,充其量也不太方便。但我检查了 documentation 很快发现确实有预定义的方法 foldl foldr 从…起 ,形式为 foldlDefault foldrDefault

    instance foldableNonEmpty :: Foldable NonEmpty where
        foldMap f (NonEmpty a as) = f a <> foldMap f as
        foldr = foldrDefault
        foldl = foldlDefault
    

    但这也无法编译。这次的错误是:

      The value of foldableNonEmpty is undefined here, so this reference is not allowed.
    

    文件夹默认值 折叠默认值 ,因为(根据追踪文档)它们要求类型构造函数已经具有 实例,因此不能用作定义实例的一部分。

    但所有这一切当然回避了一个问题:我如何定义 可折叠的 实例,而不必手动写出3种方法中2种方法的冗余定义?与其他定义方法的类型类类似。或者如果可能(我希望如此!),我错过了什么?

    instance foldableNonEmpty :: Foldable NonEmpty where
        foldMap f (NonEmpty a as) = f a <> foldMap f as
        foldr f = foldrDefault f
        foldl f = foldlDefault f
    

    但这引出了一个新问题:据我所知,PureScript使用咖喱的方式与Haskell完全相同,为什么这是允许的,而上面的“eta-reduced”版本是不允许的?关于未定义值的错误消息与什么有关?

    0 回复  |  直到 5 年前
        1
  •  4
  •   Fyodor Soikin    5 年前

    是的,PureScript确实以与Haskell完全相同的方式使用咖喱,但咖喱不是这里的问题。

    问题是 评估顺序 .Haskell使用正常的求值顺序,而PureScript使用Application(更明确地说,PureScript并不懒惰)。

    这意味着,在PureScript和Haskell之间,Eta减少的工作方式不同(在边缘情况下)。考虑以下示例:

    f g x = if x > 0 then g x else 0
    h x y = f (h x) y
    

    如果我打电话 h 5 0 0 h 实际上从未递归调用,因为内部 f 这个 else 分支得到评估。

    h x = f (h x)
    

    但如果我用PureScript写的话,那就意味着每次调用 h x 必须立即致电 递归地将其结果传递给 f

    h x 正在传递到 f -又名“正常评估顺序”。


    为了打电话 foldrDefault ,您必须将其传递给实例,但这意味着递归调用字典构造函数,这将导致无限递归。

    但是如果扩展实例字典,则在其自身构造过程中不需要实例字典,而仅在以下情况下需要实例字典: foldr 实际上是通过参数调用的。


    -你可能会愤怒地问。


    针对您的评论:

    我很难将其应用于实例定义,尤其是为什么“如果您扩展了实例字典,那么在其自身的构造过程中就不需要实例字典”?为了弄清楚foldrDefault实际上是什么,还不需要那个字典吗?

    var dictionary = {
        ...
        foldr: foldrDefault(dictionary)
        ...
    }
    

    对于Eta扩展的情况,JavaScript将如下所示:

    var dictionary = {
        ...
        foldr: function(y) { return foldrDefault(dictionary)(y) }
        ...
    }
    

    dictionary 传递给 文件夹默认值 初始化之前 文件夹默认值 最终会成为 undefined

    这实际上是编译器中的一个bug(很抱歉,我现在找不到GitHub问题),补丁只是简单地禁止这种模式,只允许Eta扩展的情况,如您所见,在这种情况下 词典 传递给 文件夹默认值 调用,但在初始化期间不调用

    推荐文章