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

Jon*_*rdy 6 haskell lenses haskell-lens

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

element -> Maybe element
element -> Maybe (result, element)
Run Code Online (Sandbox Code Playgroud)

如何通过 index修改内部结构,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
Run Code Online (Sandbox Code Playgroud)
> decrementThingAt 1 exampleThing
Just ("example",[0,0])

> decrementThingAt 0 exampleThing
Nothing
Run Code Online (Sandbox Code Playgroud)

然后,如果索引不存在,则默默地返回未修改的结构:

> decrementThingAt 2 exampleThing
Just ("example",[0,1])
Run Code Online (Sandbox Code Playgroud)

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

> preview (_2 . ix 1) exampleThing
Just 1

> preview (_2 . ix 2) exampleThing
Nothing
Run Code Online (Sandbox Code Playgroud)

但我希望能够写出类似. 我看到一些在“外面”做的尴尬的方法,比如:traverseOf (_2 . ix i . previewed) predMaybefoldMapOf

decrementThingAt i = getFirst . foldMapOf (_2 . ix i) (First . predMaybe)
Run Code Online (Sandbox Code Playgroud)

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

我也不太明白如何将它与返回额外的结果结合起来。目前我正在使用StateT并且zoom喜欢这样:

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
Run Code Online (Sandbox Code Playgroud)
> pop exampleThing
Just ('e',("xample",[0,1]))

> pop ("", [0, 1])
Nothing
Run Code Online (Sandbox Code Playgroud)

但是我仍然不知道如何使用缺少的索引来实现它,例如,type ThingMaybe = (Maybe String, [Int])如果MaybeNothing.

DDu*_*Dub 1

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

\n
decrementThingAt :: Int -> Thing -> Maybe Thing\ndecrementThingAt i = traverseOf myTrav predMaybe\n
Run Code Online (Sandbox Code Playgroud)\n

这样

\n
> decrementThingAt 1 exampleThing\nJust ("example",[0,0])\n\n> decrementThingAt 2 exampleThing\nNothing\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n
unknown :: Int -> Thing -> Thing\nunknown i = myTrav %~ id\n
Run Code Online (Sandbox Code Playgroud)\n

对于任何正常的遍历,对于和unknown n t == t的所有(类型正确的)选择,但是对于,这不再清楚。如果你打电话,大概你希望它返回......ntmyTravunknown 2 exampleThingNothing?这在类型级别上甚至没有意义。

\n

从另一个角度来看,你无法构造遍历myTrav其原因与您运行多少次相同

\n
> traverse predMaybe []\nJust []\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

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

\n
\n

当涉及到返回附加结果时,我喜欢使用(a,)函子:

\n
import Data.Functor.Compose\n\npop :: Thing -> Maybe (Char, Thing)\npop = getCompose . _1 (Compose . uncons)\n\n-- Or if you prefer using all the lens operators:\n-- pop t = getCompose $ t & _1 %%~ (Compose . uncons)\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,由于需要使用,它变得有点额外复杂Compose,但它最终仍然做了我想要的事情。

\n

至于回程Nothing索引丢失时的返回,您将遇到我上面描述的相同问题。

\n
\n

一些选项

\n

无论如何,我相信实现您正在寻找的目标的最简单方法不是“内部”层面进行,而是在外部层面进行,并且没有理由这将最终“重复/明确地拆卸和重新组装结构”。考虑这样的事情:

\n
maybeToAny :: Maybe a -> Compose Maybe ((,) Any) a\nmaybeToAny = Compose . fmap (Any True,)\n\ngetIfAny :: Compose Maybe ((,) Any) a -> Maybe a\ngetIfAny (Compose Nothing) = Nothing\ngetIfAny (Compose (Just (Any False, _))) = Nothing\ngetIfAny (Compose (Just (Any True, a))) = Just a\n\ndecrementThingAt :: Int -> Thing -> Maybe Thing\ndecrementThingAt i = getIfAny . traverseOf (_2 . ix i) (maybeToAny . predMaybe)\n
Run Code Online (Sandbox Code Playgroud)\n

这里的想法是我们使用该Any值来检查是否maybeToAny . predMaybe曾经被调用过。如果是,那么该Any值将为True,我们可以生成结果值,否则,我们的遍历一定会错过,所以我们返回Nothing

\n
\n

如果你真的想在光学器件“内部”执行此操作,我们还可以进行遵循相同原理的伪遍历:

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

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

\n
finside :: Applicative f => ALens s t a b -> Lens (f s) (f t) (f a) (f b)\nfinside (cloneLens -> l) = lens (fmap $ getConst . l Const) (liftA2 $ flip (l .~))\n\ndecrementThingAt :: Int -> Thing -> Maybe Thing\ndecrementThingAt i = join . traverseOf (finside _2 . ixj i) predMaybe . Just\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n