【问题标题】:Mapping while showing intermediate states映射同时显示中间状态
【发布时间】:2017-10-09 13:35:42
【问题描述】:

我需要一个这样的函数:

>>> func (+1) [1,2,3]
[[2,2,3],[2,3,3],[2,3,4]]

我的真实案例更复杂,但这个例子显示了问题的要点。主要区别在于,实际上使用索引是不可行的。 List 应该是 TraversableFoldable

EDIT:这应该是函数的签名:

func :: Traversable t => (a -> a) -> t a -> [t a]

更接近我真正想要的是与traverse 相同的签名,但无法弄清楚我必须使用什么功能才能获得所需的结果。

func :: (Traversable t, Applicative f) :: (a -> f a) -> t a -> f (t a)

【问题讨论】:

  • 这在一般情况下是不可能的,因为应用函数的输出不必与输入的类型相同。所以中间结果可能类似于["Foo", 2, 3]。您应该重新制定您的要求。
  • @EugeneSh。我认为 (+1) 函数必须是 endo 是隐含的,否则它是不可能的,正如你所解释的。
  • 这是“小丑与小丑”的工作。人们可能会选择更灵活、更准确地说明中间状态。例如,[([],(1,2),[2,3]),([2],(2,3),[3]), ([2,3],(3,4),[ ])],其中状态列表的每个元素都有相应的输入“焦点”元素,我们在左侧看到我们已经拥有的输出,在右侧看到我们尚未访问的输入,以及之前 - and-after 对焦点元素。对于这样的结构,映射函数不必是 endo。
  • @pigworker 这正是我正在寻找的。在继续之前,我还必须对焦点下的元素执行一些带有 副作用 的操作。在一些 Haskell 包中是否有一个很好的“小丑和小丑”实现?
  • 我一个都没见过。该论文的文字源代码非常适合谷歌搜索。一些乐于助人的人拿了一份,这里是github.com/mak/course-haskell/blob/master/zipppers/CJ.lhs,这和我找不到我的一样。

标签: haskell traversable


【解决方案1】:

看起来@Benjamin Hodgson 误读了您的问题,并认为您希望将f 应用于每个部分结果中的单个元素。因此,您最终认为他的方法不适用于您的问题,但我认为它适用。考虑以下变化:

import Control.Monad.State

indexed :: (Traversable t) => t a -> (t (Int, a), Int)
indexed t = runState (traverse addIndex t) 0
  where addIndex x = state (\k -> ((k, x), k+1))

scanMap :: (Traversable t) => (a -> a) -> t a -> [t a]
scanMap f t =
  let (ti, n) = indexed (fmap (\x -> (x, f x)) t)
      partial i = fmap (\(k, (x, y)) -> if k < i then y else x) ti
  in  map partial [1..n]

这里,indexed 在状态 monad 中运行,为可遍历对象的元素添加递增索引(并“免费”获取长度,无论这意味着什么):

> indexed ['a','b','c']
([(0,'a'),(1,'b'),(2,'c')],3)

同样,正如 Ben 所指出的,它也可以使用 mapAccumL 编写:

indexed = swap . mapAccumL (\k x -> (k+1, (k, x))) 0

然后,scanMap 获取可遍历对象,将其 fmap 到类似的前后对结构,使用indexed 对其进行索引,并应用一系列partial 函数,其中partial i 选择“afters” " 用于第一个 i 元素,而 "befores" 用于其余元素。

> scanMap (*2) [1,2,3]
[[2,2,3],[2,4,3],[2,4,6]]

至于将其从列表概括为其他内容,我无法弄清楚您要对第二个签名做什么:

func :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)

因为如果您将其专门用于您获得的列表:

func' :: (Traversable t) => (a -> [a]) -> t a -> [t a]

而且完全不清楚你想在这里做什么。

【讨论】:

  • 啊,你说得对,我确实误读了这个问题。这是正确的答案。不过,我认为代码比它需要的要复杂一些。您不需要 indexed 基础设施,您可以在其中标记每个元素,然后返回它们以计算结果:您可以保留我的答案的倒计时索引想法,只需将 == 0 更改为 &gt;= 0 ,将f 应用于目标索引之前的每个元素,而不仅仅是单个元素本身。
  • 我会将第二个签名专门用于:(a -&gt; IO a) -&gt; [a] -&gt; IO [a] 但没关系,我可以从这里获取。
  • 感谢scanMap 这个名字,它非常适合所讨论的功能。
【解决方案2】:

在列表中,我会使用以下内容。如果不需要,请随意丢弃第一个元素。

> let mymap f [] = [[]] ; mymap f ys@(x:xs) = ys : map (f x:) (mymap f xs)
> mymap (+1) [1,2,3]
[[1,2,3],[2,2,3],[2,3,3],[2,3,4]]

这也可以在Foldable 上工作,当然,在使用toList 将可折叠文件转换为列表之后。不过,人们可能仍然想要一个更好的实现来避免该步骤,特别是如果我们想要保留原始可折叠类型,而不仅仅是获取一个列表。

【讨论】:

  • @DannyNavarro 是的,我同意——我看到你的最后一次编辑澄清了这一点。我的解决方案太具体了。
【解决方案3】:

根据您的问题,我只是将其称为 func,因为我想不出更好的名字。

import Control.Monad.State

func f t = [evalState (traverse update t) n | n <- [0..length t - 1]]
    where update x = do
            n <- get
            let y = if n == 0 then f x else x
            put (n-1)
            return y

这个想法是updaten开始倒计时,当它达到0时我们应用f。我们将n 保留在状态单子中,以便traverse 可以在您穿过可遍历对象时检测n

ghci> func (+1) [1,1,1]
[[2,1,1],[1,2,1],[1,1,2]]

您可以使用mapAccumL 节省一些击键,这是一个捕获在状态单子中遍历模式的 HOF。

【讨论】:

  • 不幸的是,我在现实世界的问题中不能依赖长度。我不能使用 List 特定的任何东西。
  • @DannyNavarro length :: Foldable t =&gt; t a -&gt; Int
  • 我没有意识到length 现在是按照Foldable 实现的。这是非常好的。我仍然认为我无法将其应用于我的问题,但值得探索这种方法。
  • @DannyNavarro 根据我从您的 cmets 中收集到的信息,您似乎每次映射其中一个元素时都想执行副作用?将我的函数概括为f :: a -&gt; m a 非常简单:在映射时使用状态单子 transformer 而不是 mlift 您的操作。下车后我会修改答案
  • PS 在问题中指定您的实际问题是值得的。如果你问一个关于一个有点相似但不同的问题的问题,你会得到答案(就像这个!)正确回答你的问题,但仍然不能满足你的现实需求。 meta.stackexchange.com/questions/66377/what-is-the-xy-problem
【解决方案4】:

这听起来有点像没有焦点的拉链;也许是这样的:

data Zippy a b = Zippy { accum :: [b] -> [b], rest :: [a] }

mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f = go id where
  go a [] = []
  go a (x:xs) = Zippy b xs : go b xs where
    b = a . (f x :)

instance (Show a, Show b) => Show (Zippy a b) where
  show (Zippy xs ys) = show (xs [], ys)


mapZippy succ [1,2,3]
-- [([2],[2,3]),([2,3],[3]),([2,3,4],[])]

(为了提高效率,这里使用差异列表)

转换为折叠看起来有点像paramorphism

para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para f b [] = b
para f b (x:xs) = f x xs (para f b xs)

mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f xs = para g (const []) xs id where
  g e zs r d = Zippy nd zs : r nd where
    nd = d . (f e:)

对于任意遍历,有一个很酷的时间旅行状态转换器,称为 Tardis,它可以让您向前和向后传递状态:

mapZippy :: Traversable t => (a -> b) -> t a -> t (Zippy a b)
mapZippy f = flip evalTardis ([],id) . traverse g where
  g x = do
    modifyBackwards (x:)
    modifyForwards (. (f x:))
    Zippy <$> getPast <*> getFuture

【讨论】:

  • 最后我将使用与此类似的东西,因为它更符合我真正需要的东西。但是我正在标记@K。 A. Buhr 的回答被接受,因为对于我提供的示例,它运行良好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-09
  • 1970-01-01
  • 2020-12-29
  • 2015-04-01
  • 2017-12-02
  • 2020-02-22
  • 1970-01-01
相关资源
最近更新 更多