In brief
My getter and setter could both fail, with messages describing how. Therefore they return Either String
, which means I can't make lenses out of them in the normal way.
In detail
Consider these types:
import qualified Data.Vector as V
data Tree a = Tree { label :: a
, children :: V.Vector (Tree a) }
type Path = [Int]
Not every Path
into a Tree
leads to a Tree
, so a getter has to have a signature like getSubtree :: Path -> Tree a -> Either String (Tree a)
. A setter needs a similar signature (see modSubtree
below).
If the getter and setter returned values of type Tree a
, I would use them to create a lens, via something like the lens
function in Lens.Micro. I can't do that, though, if they return Either
. Therefore I can't compose them with other lenses, so I have to do lots of wrapping and unwrapping.
What would be a better way?
Example code
{-# 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