【问题标题】:Why is this implementation a bad instance of the Foldable Typeclass?为什么这个实现是可折叠类型类的错误实例?
【发布时间】:2021-01-01 19:56:37
【问题描述】:

我正在处理精彩的Haskell Book。在 Traversable 章节(21)的最后,我需要为下面的 Tree 写一个实例:

data Tree a =
    Empty
  | Leaf a
  | Node (Tree a) a (Tree a)

这是我的解决方案的a link to the full code。练习建议尝试同时实现foldMapfoldr。这就是我实现foldr 的方式(没有过多考虑调用顺序):

foldr _ z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) = 
  f x $ foldr f (foldr f z left) right

然后我实现foldMap如下:

foldMap f Empty = mempty
foldMap f (Leaf x) = f x
foldMap f (Node left x right) = 
  foldMap f left <> f x <> foldMap f right

当我运行 QuickCheck 的 foldable 测试批次时,我遇到了一些失败。将我的 foldr 实现更改为以下内容可使所有测试通过:

foldr _ z Empty = z
foldr f z (Leaf x) = f x z
foldr f z (Node left x right) = 
  foldr f (f x (foldr f z right)) left

我尝试自己运行失败的测试用例,但无法重新创建失败:

*Ch21_12_ExercisesTree Data.Monoid> tree = Node (Node (Leaf (-5)) 3 (Node (Leaf 3) 5 Empty)) (-2) Empty
*Ch21_12_ExercisesTree Data.Monoid> foldr (<>) (mempty :: Sum Int) t
Sum {getSum = 4}
*Ch21_12_ExercisesTree Data.Monoid> foldMap Sum t
Sum {getSum = 4}

我怀疑 QuickCheck 正在使用的 folding 函数有什么我不明白的地方。

问题:

  1. 为什么会出现故障?
  2. 有没有办法通过 QuickCheck 获取测试中使用的函数?

【问题讨论】:

    标签: haskell typeclass quickcheck foldable typeclass-laws


    【解决方案1】:

    foldr 可以从foldMap by using the Endo monoid 获得,a -&gt; b -&gt; b 函数将a 值转换为可以(单向)组合的b -&gt; b 函数。既然如此,如果你的foldMap 是……

    foldMap f Empty = mempty
    foldMap f (Leaf x) = f x
    foldMap f (Node left x right) = 
      foldMap f left <> f x <> foldMap f right
    

    ...对应的foldr必须是:

    foldr f z Empty = id z  -- mempty amounts to id
    foldr f z (Leaf x) = (f x) z
    foldr f z (Node left x right) = 
      ((\e -> foldr f e left) . f x . (\e -> foldr f e right)) z  -- (<>) amounts to (.)
    

    如果我们把它整理一下......

    foldr f z Empty = z
    foldr f z (Leaf x) = f x z
    foldr f z (Node left x right) = 
      foldr f (f x (foldr f z right)) left)
    

    ...我们得到了您问题中所写foldr 的正确定义。由于实现之间的差异与组合顺序有关,因此尝试非交换幺半群很容易导致失败案例,as you have found out

    关于 QuickCheck 子问题,我遵从DDub's answer.

    【讨论】:

      【解决方案2】:

      正如您已经推断的那样,您遇到失败的原因是因为这两种实现是可区分的,您可以通过使用非交换幺半群来观察。


      获取quickcheck使用的函数并不是那么简单。例如,请参阅 this question/answer 关于由 quickcheck 生成的 Showing 函数以获取更多信息。

      从 QuickCheck 中获取 Showable 函数的方法是将函数包装在 the Fun type 中。也就是说,您调用的代码 (found here) 只是直接使用函数,因此它们永远无法显示。您可以尝试的一种选择是创建您自己版本的 foldable 函数,您可以根据需要使用类型 Fun a b 代替 a -&gt; bapplyFun 来应用函数。

      【讨论】:

        【解决方案3】:

        我刚刚意识到我使用了可交换 Monoid ...我能够使用非可交换 Monoid 重新创建故障:

        > ftree = fmap (First . Just) tree
        > foldr (<>) mempty ft
        First {getFirst = Just (-2)}
        > foldMap (First . Just) ft
        First {getFirst = Just (First {getFirst = Just (-5)})}
        

        这可能是一个简单的案例。我想在具有真实数据类型的生产代码中,这可能要复杂得多。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-13
          • 1970-01-01
          • 2020-01-29
          • 2017-08-16
          • 2015-04-22
          • 1970-01-01
          相关资源
          最近更新 更多