gpt4 book ai didi

haskell - 当我的 getter 和 setter 返回 `Either` 时,我可以构建类似镜头的东西吗?

转载 作者:行者123 更新时间:2023-12-04 21:30:03 25 4
gpt4 key购买 nike

简单来说

我的 getter 和 setter 都可能失败,并带有描述如何失败的消息。因此他们返回 Either String ,这意味着我无法以正常方式用它们制作镜头。

详细

考虑这些类型:

import qualified Data.Vector as V

data Tree a = Tree { label :: a
, children :: V.Vector (Tree a) }

type Path = [Int]

不是每个 PathTree导致 Tree ,所以 getter 必须有像 getSubtree :: Path -> Tree a -> Either String (Tree a) 这样的签名. setter 需要一个类似的签名(参见下面的 modSubtree)。

如果 getter 和 setter 返回类型 Tree a 的值,我会用它们来创建一个镜头,通过类似 lens 的东西函数在 Lens.Micro .但是,如果他们返回 Either,我就不能这样做。 .因此我无法将它们与其他镜头组合在一起,因此我必须进行大量的包装和展开。

什么是更好的方法?

示例代码
{-# LANGUAGE ScopedTypeVariables #-}

module I_wish_I_could_lens_this_Either where

import qualified Data.Vector as V

data Tree a = Tree { label :: a
, children :: V.Vector (Tree a) }
deriving (Show, Eq, Ord)

type Path = [Int]

-- | This is too complicated.
modSubtree :: forall a. Show a =>
Path -> (Tree a -> Tree a) -> Tree a -> Either String (Tree a)
modSubtree [] f t = Right $ f t
modSubtree (link:path) f t = do
if not $ inBounds (children t) link
then Left $ show link ++ "is out of bounds in " ++ show t
else Right ()
let (cs :: V.Vector (Tree a)) = children t
(c :: Tree a) = cs V.! link
c' <- modSubtree path f c
cs' <- let left = Left "imossible -- link inBounds already checked"
in maybe left Right $ modifyVectorAt link (const c') cs
Right $ t {children = cs'}

getSubtree :: Show a => Path -> Tree a -> Either String (Tree a)
getSubtree [] t = Right t
getSubtree (link:path) t =
if not $ inBounds (children t) link
then Left $ show link ++ "is out of bounds in " ++ show t
else getSubtree path $ children t V.! link

-- | check that an index into a vector is inbounds
inBounds :: V.Vector a -> Int -> Bool
inBounds v i = i >= 0 &&
i <= V.length v - 1

-- | Change the value at an index in a vector.
-- (Data.Vector.Mutable offers a better way.)
modifyVectorAt :: Int -> (a -> a) -> V.Vector a -> Maybe (V.Vector a)
modifyVectorAt i f v
| not $ inBounds v i = Nothing
| otherwise = Just ( before
V.++ V.singleton (f $ v V.! i)
V.++ after )
where before = V.take i v
after = V.reverse $ V.take remaining $ V.reverse v
where remaining = (V.length v - 1) - i

最佳答案

你确实可以用镜头做到这一点!或者更具体地说;遍历:)

首先进行一些设置:

{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RankNTypes #-}
module TreeTraversal where

import qualified Data.Vector as V
import Control.Lens hiding (children)

data Tree a = Tree { _label :: a
, _children :: V.Vector (Tree a) }
deriving (Show, Eq, Ord, Functor)
makeLenses ''Tree
type Path = [Int]

从现在开始,有两种方法可以进行;如果您只需要知道整个遍历是否成功(例如路径中的任何链接都无法访问),那么您可以使用 failover ;它接受一个遍历和一个函数,并将尝试在遍历上运行该函数,但它将以 Alternative 返回结果。语境;我们可以选择这个上下文为“可能”,这样我们就可以通过模式匹配检测失败并返回适当的 LeftRight .我不知道遍历索引列表的简单方法,因此我编写了一个快速助手来递归链接列表并将它们转换为使用组合的遍历。
modSubtreeWithGenericError
:: forall a. Show a
=> Path -> (Tree a -> Tree a) -> Tree a -> Either String (Tree a)
modSubtreeWithGenericError links f =
maybe (Left "out of bounds") Right . failover (pathOf links) f
where
pathOf :: [Int] -> Traversal' (Tree a) (Tree a)
pathOf [] = id
pathOf (p : ps) = children . ix p . pathOf ps

如果您只关心一般的失败,那应该可以解决问题,但很高兴知道它在哪里失败,对吗?我们可以通过编写一个自定义遍历来做到这一点,它知道它在 Either String 内部运行。 ;大多数遍历必须在 ANY applicative 上工作,但在我们的例子中,我们知道我们希望我们的结果在任何一个中;所以我们可以利用这一点:
modSubtreeWithExpressiveError
:: forall a. Show a
=> [Int] -> (Tree a -> Tree a) -> Tree a -> Either String (Tree a)
modSubtreeWithExpressiveError links f = pathOf links %%~ (pure . f)
where
pathOf :: [Int] -> LensLike' (Either String) (Tree a) (Tree a)
pathOf [] = id
pathOf (x : xs) = childOrFail x . pathOf xs
childOrFail :: Show a => Int -> LensLike' (Either String) (Tree a) (Tree a)
childOrFail link f t =
if t & has (children . ix link)
then t & children . ix link %%~ f
else buildError link t
childOrFail是有趣的一点; LensLike bit 实际上只是 (Tree a -> Either String (Tree a)) -> Tree a -> Either String (Tree a) 的别名这只是 traverse专攻 Either String ;我们不能只使用 traverse直接,因为我们只想遍历单个子树,我们的函数运行在 Tree a 上而不仅仅是 a .我手动写了遍历,首先使用 has 检查目标是否存在然后要么失败并返回 Left有一个很好的错误,或者运行 f (代表其余的遍历)使用 %%~ 在适当的 child 上. %%~组合器也有点吓人;具有讽刺意味的是,它的定义是字面意思 (%%~) = id ;通常我们会使用 %~而是在这里;但它需要一个与 Either String 不匹配的特定 Applicative我们指定的一个。 %%~愉快地运行我们的自定义遍历,尽管我们仍然需要添加一个额外的 pure到我们的函数中以使其进入Either 上下文。

这是非常先进的镜头东西,但归根结底,这只是正常的遍历(大多数镜头都是如此)。

我有一个关于编写你自己的遍历的指南,它可能会有所帮助 https://lens-by-example.chrispenner.ca/articles/traversals/writing-traversals

祝你好运!希望有帮助:)

关于haskell - 当我的 getter 和 setter 返回 `Either` 时,我可以构建类似镜头的东西吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55189779/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com