【问题标题】:Is there an equivalent to head/tail for Foldable functors in general?一般来说,可折叠函子是否有等效于头/尾的方法?
【发布时间】:2018-06-10 15:27:18
【问题描述】:

我想表达以下 Haskell 代码,仅使用 函子代数(即 - 依赖于任何特定容器类型,例如 List):

ys = zipWith (+) (head xs : repeat 0)
                 (tail xs ++ [y])

在我看来应该有办法做到这一点,只依赖Foldable(或者,也许是Traversable),但我看不到。

我想知道:

  1. 对于可折叠/可遍历函子,是否存在 firstrest 的一般概念?
  2. 是否有一种公认的惯用方式,仅使用函子代数来移动可折叠/可遍历函子的内容? (请注意,上面的计算可能用英文描述为“从右侧移入一个值,并将落在左侧的值加回新的第一个值。”)

【问题讨论】:

  • 不使用Foldable 类,但更方便且非常通用:来自镜头库的_head_tail

标签: haskell functor shift traversable foldable


【解决方案1】:

您可以使用来自Data.MonoidFirstLast 幺半群来查找Foldable 的第一个或最后一个元素。

foldMap (Last . Just)  :: Foldable t => t a -> Last a
foldMap (First . Just) :: Foldable t => t a -> First a

所有Foldable 都可以转换为列表,因此由于您可以找到列表的头部和尾部,因此您可以为任何Foldable 这样做。

toList = foldr (:) [] :: Foldable t => t a -> [a]

也就是说,尾部将有一个列表类型,而不是Foldable 的类型(除非它也是一个列表)。这最终是因为并非所有 Foldable 都可以实现 uncons。例如:

data Pair a = Pair a a

这是Foldable,但您不能使用Pair 表示Pair 的尾部。

【讨论】:

  • 虽然在给定的Foldable 类型内获取头部和尾部可能没有意义,但在保持脊柱完全相同的同时“旋转”任何Foldable 似乎是一个定义明确的操作.只是没有一个可用的,我不认为。例如对于Pairrotate (Pair x y) = Pair y x 是一个完美的操作。
  • @DanielWagner 很有趣。好吧,也许递归方案中有些东西可以提供帮助。 foldr 不会剪掉它。
  • @DanielWagner 我希望你需要Traversable
  • @BenjaminHodgson 谢谢,您的评论虽然回想起来应该很明显,但告诉我如何实现所要求的。我已经根据您的见解更新了我的答案。 =)
【解决方案2】:

您的问题的第一部分(将结构的第一个值与一件事结合起来,其余部分保持不变)可以使用Traversable 以简单的方式完成。我们将使用State,从我们要应用的功能开始,然后立即将其修改为id

onlyOnHead :: Traversable t => (a -> a) -> t a -> t a
onlyOnHead f xs = evalState (traverse go xs) f where
    go x = do
        fCurrent <- get
        put id
        return (fCurrent x)

您可以用类似的想法旋转元素:我们将旋转一个列表,然后将 State 中的内容作为绘制元素的对象。

rotate :: Traversable t => t a -> t a
rotate xs = evalState (traverse go xs) (rotateList (toList xs)) where
    rotateList [] = []
    rotateList vs = tail vs ++ [head vs]

    go _ = do
        v:vs <- get
        put vs
        return v

【讨论】:

  • Traversable 的另一种方式是rotate xs = ys where (x0, ys) = mapAccumR (\x y -&gt; (y, x)) x0 xs。 (或者可能是 runState 的类似技巧。)
  • @DavidFletcher 非常漂亮!
  • 有趣。这也表明,如果 T a 类型是不透明的并且满足某些不变量(例如,红黑树),那么它可以被制成 Foldable 但不应该是 Traversable
  • @chi 为什么?红黑树上的不变量在书脊上,而不是在内容上,traverse 准确地保留了书脊。所以那里没问题。
  • @chi 哦,当然。但也可以考虑 SetMap - 每个都有一个不变量,因此 Set 不能支持 Functor 或任何其他有趣的类,但 Map 可以支持 Functor 和 @987654339 @ 因为不变量在它的键上,这些实例不会触及。
【解决方案3】:

要旋转,您不需要任何丑陋的偏函数。这个奇怪的Applicative 可以解决问题。

data Foo a t where
  Cons :: (a -> q -> t) -> a -> Foo a q -> Foo a t
  Nil :: t -> Foo a t

instance Functor (Foo a) where
  fmap f (Cons g x xs) = Cons (\p q -> f (g p q)) x xs
  fmap f (Nil q) = Nil (f q)

instance Applicative (Foo a) where
  pure = Nil
  liftA2 f (Nil t) ys = f t <$> ys
  liftA2 f (Cons g x xs) ys = Cons (\a (q,b) -> f (g a q) b) x (liftA2 (,) xs ys)

您可以旋转Foo

rot :: Foo a t -> Foo a t
rot n@(Nil _) = n
rot (Cons g0 a0 as0) = go g0 a0 as0
  where
    go :: (a -> q -> t) -> a -> Foo a q -> Foo a t
    go g a n@(Nil _) = Cons g a n
    go g a (Cons h y ys) = Cons g y (go h a ys)

运行一个得到结果:

runFoo :: Foo a t -> t
runFoo (Nil t) = t
runFoo (Cons g x xs) = g x (runFoo xs)

把它们放在一起,

rotate :: Traversable t => t a -> t a
rotate = runFoo . rot . traverse (\a -> Cons const a (Nil ()))

然后rotate [1..10] = [2..10] ++ [1]

【讨论】:

    【解决方案4】:

    感谢所有回复的人!

    请注意,Conal Elliott's shaped-types library 在这方面也有一些有用的机制。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      • 2015-07-19
      • 2010-12-08
      • 1970-01-01
      • 2019-09-12
      • 2011-03-20
      • 1970-01-01
      相关资源
      最近更新 更多