如果索引列表遍历不匹配,则返回“Nothing”

Posted

技术标签:

【中文标题】如果索引列表遍历不匹配,则返回“Nothing”【英文标题】:Returning ‘Nothing’ if an indexed list traversal doesn’t match 【发布时间】:2021-07-18 19:18:29 【问题描述】:

我是 lens 的新手,并尝试使用它对嵌套结构进行许多小的修改,这可能会失败并可能返回其他结果:

element -> Maybe element
element -> Maybe (result, element)

我如何通过索引修改内部结构,如果索引不存在也返回Nothing?如果我使用traverseOf+ix:

type Thing = (String, [Int])

exampleThing :: Thing
exampleThing = ("example", [0, 1])

predMaybe :: Int -> Maybe Int
predMaybe x
  | x == 0 = Nothing
  | otherwise = Just (pred x)

decrementThingAt :: Int -> Thing -> Maybe Thing
decrementThingAt i = traverseOf (_2 . ix i) predMaybe
> decrementThingAt 1 exampleThing
Just ("example",[0,0])

> decrementThingAt 0 exampleThing
Nothing

如果索引不存在,这会默默地返回未修改的结构:

> decrementThingAt 2 exampleThing
Just ("example",[0,1])

而我也想在这里返回Nothing。如果可能的话,我想在镜头组合“内部”进行。我知道我可以使用 preview / ^? “outside” 来获得 Maybe 根据光学是否匹配任何目标:

> preview (_2 . ix 1) exampleThing
Just 1

> preview (_2 . ix 2) exampleThing
Nothing

但我希望能够写出类似traverseOf (_2 . ix i . <strong>previewed</strong>) predMaybe 的东西。我看到了一些“在外面”做这件事的尴尬方式,比如foldMapOf

decrementThingAt i = getFirst . foldMapOf (_2 . ix i) (First . predMaybe)

但是有没有办法让所有东西都在同一个管道中,这样我就不会重复/明确地拆卸和重新组装结构?

我也不太明白如何将它与返回额外结果结合起来。目前我正在像这样使用StateTzoom

import Control.Lens (_1, zoom)
import Control.Monad.Trans.State (StateT, runStateT)
import Data.List (uncons)

-- NB: uncons :: [a] -> Maybe (a, [a])

pop :: Thing -> Maybe (Char, Thing)
pop = runStateT $ zoom _1 $ StateT uncons
> pop exampleThing
Just ('e',("xample",[0,1]))

> pop ("", [0, 1])
Nothing

但我仍然不知道如何在缺少索引的情况下使用它,例如,使用 type ThingMaybe = (Maybe String, [Int]) 并且如果 MaybeNothing 则失败。

【问题讨论】:

【参考方案1】:

你所要求的是不可能的。要了解原因,让我们看看您的 decrementThingAt 示例。假设你想出了一些你想要的遍历。也就是说,你可以写

decrementThingAt :: Int -> Thing -> Maybe Thing
decrementThingAt i = traverseOf myTrav predMaybe

这样

> decrementThingAt 1 exampleThing
Just ("example",[0,0])

> decrementThingAt 2 exampleThing
Nothing

现在,让我们探索一下这个函数:

unknown :: Int -> Thing -> Thing
unknown i = myTrav %~ id

对于任何正常的遍历,unknown n t == t 对于nt 的所有(类型正确)选择,但对于myTrav,这不再清楚了。如果你调用unknown 2 exampleThing,想必你希望它返回...Nothing?这在类型级别甚至没有意义。

换个角度看,你不能构造一个遍历myTrav,原因和你跑多少次一样

> traverse predMaybe []
Just []

你永远不会得到Nothing。遍历ix 实质上过滤掉了您调用predMaybe 的列表元素,当您在不存在的索引上ix 时,就像您过滤掉了所有元素。最终没有调用predMaybe,因此无法返回Nothing

正如您正确识别的那样,执行您正在寻找的事情的正确方法是“在”遍历之外。这是有道理的:它与您声明使用predMaybe 的级别相同,毕竟这是Maybe 应用程序发挥作用的地方。例如,您使用FirstfoldMapOf 是有效的,因为First 是一个具有mempty ≈ Nothing 属性的幺半群,正如您所愿。


在返回额外结果时,我喜欢使用 (a,) 函子:

import Data.Functor.Compose

pop :: Thing -> Maybe (Char, Thing)
pop = getCompose . _1 (Compose . uncons)

-- Or if you prefer using all the lens operators:
-- pop t = getCompose $ t & _1 %%~ (Compose . uncons)

在这种情况下,由于需要使用Compose,它变得更加复杂,但它仍然可以按照我的意愿行事。

至于在索引丢失时返回Nothing,你会遇到我上面描述的同样的问题。


一些选项

对于它的价值,我相信实现您正在寻找的目标的最简单方法是不是在“内部”级别进行,而是在外部级别进行,并且没有理由最终会“反复/明确地拆卸和重新组装结构”。考虑这样的事情:

maybeToAny :: Maybe a -> Compose Maybe ((,) Any) a
maybeToAny = Compose . fmap (Any True,)

getIfAny :: Compose Maybe ((,) Any) a -> Maybe a
getIfAny (Compose Nothing) = Nothing
getIfAny (Compose (Just (Any False, _))) = Nothing
getIfAny (Compose (Just (Any True, a))) = Just a

decrementThingAt :: Int -> Thing -> Maybe Thing
decrementThingAt i = getIfAny . traverseOf (_2 . ix i) (maybeToAny . predMaybe)

这里的想法是我们使用Any 值来检查是否曾经调用过maybeToAny . predMaybe。如果是,那么Any的值就是True,我们可以产生结果值,否则我们的遍历肯定是错过了,所以我们返回Nothing


如果你真的想在光学“内部”执行此操作,我们也可以按照相同的原则进行伪遍历:

ixj :: Ixed m => Index m -> Traversal' (Maybe m) (IxValue m)
ixj i _ Nothing = pure Nothing
ixj i afb (Just s) =
  let t' = getCompose $ s & ix i %%~ Compose . fmap (Any True,) . afb
      go (Any True, t)  = Just t
      go (Any False, _) = Nothing
  in go <$> t'

请注意,我们需要 s 类型为 Maybe m 而不仅仅是 m,这样我们才能失败。不幸的是,这不是真正的遍历,因为它不遵守遍历定律(整个失败的想法与光学的工作原理相反)。我仍然设法想出一些东西,但它有点难看:

finside :: Applicative f => ALens s t a b -> Lens (f s) (f t) (f a) (f b)
finside (cloneLens -> l) = lens (fmap $ getConst . l Const) (liftA2 $ flip (l .~))

decrementThingAt :: Int -> Thing -> Maybe Thing
decrementThingAt i = join . traverseOf (finside _2 . ixj i) predMaybe . Just

(我不确定镜头库中是否已经存在finside 或类似的东西。我也不确定它产生的内容是否始终遵守镜头法则。)

【讨论】:

谢谢,这更清楚了。我有点惊讶——“遍历映射中的值,要求存在键,无需额外的preview 来检查”似乎是一个足够常见的用例。检查我的理解——您是否将traverseOf 解释为此处的要求?需要明确的是,我不介意使用不同的“驱动程序”功能,只要光学/组合器以某种方式适合管道。 (比如说,像required (field . at i) . anotherField . … 一样,假设的required 充当我同样假设的previewed 的角色,但可能有更多可用的上下文。) 我有点困惑。当您说您不想使用foldMapOf 时,听起来您确实 认为traverseOf 是一项要求。如果不是,那么使用 foldMapOf 技巧有什么问题?至于您假设的requiredpreviewed 光学器件,您有针对它们的建议类型吗?我想我试图在我的答案中得到它是我不知道在遍历中“需要”某些东西意味着什么。遍历需要任意 Applicative,但您似乎希望将该 Applicative 限制为 Maybe 对不起,很遗憾我不知道我在这里不知道什么。我看到failover(使用Alternative)、has/hasn't 用于折叠,isn't 用于棱镜,我认为索引会有这样的事情。至于类型,我最近的尝试是这样的:rix :: Ixed a =&gt; Index a -&gt; LensLike' Maybe a (IxValue a); rix i f x = preview (ix i) x *&gt; traverseOf (ix i) f x。有了这个,traverseOf (_2 . rix i) predMaybe 似乎 可以按预期工作,但当我要遍历时,多余的preview 似乎也很奇怪。另外,不确定这是否合法。 啊!因此,rix 可能适合您的需求,但从技术上讲,它不是镜头或遍历,因为您已将f 的类型固定为Maybe。它适用于某些组合器(例如,traverseOf),但不是所有组合器(例如,toListOf),即使它有效,它也有局限性(\i -&gt; traverseOf (_2 . rix i) id 不进行类型检查)。 我已经更新了我的答案,包括了几种替代方法来做你想做的事情。也许它们会对您有所帮助或激发出一个好主意!

以上是关于如果索引列表遍历不匹配,则返回“Nothing”的主要内容,如果未能解决你的问题,请参考以下文章

相交列表

Excel - 需要从数组中搜索列表单元格的子字符串,无法获得索引/匹配工作吗?

遍历字典列表并 1) 将流值与流列元素进行比较 2) 如果匹配,则附加一个带有数据的新列表

递归遍历带有列表的嵌套字典,并替换匹配的值

python 如果它是python中另一个列表的子列表,则返回列表的索引

在列表中查找对