这是Using lens for array indexing if both array and index are in State的更简单版本,因为我已经解决了一些问题。我不确定是否应删除原件或将其编辑到位。
鉴于
{-# Language TemplateHaskell #-}
{-# Language Rank2Types #-}
import Control.Lens
import Control.Lens.TH
import Data.Array
data M = M { _arr :: Array Int Int, _idx :: Int } deriving (Show)
$(makeLenses ''M)
我想写一个函数
combine :: Lens' M (Array Int Int) -> Lens' M Int -> Lens' M Int
这需要arr
和idx
镜头,并构建一个可用于读取和写入idx
指向的元素的组合镜头。我想要的镜头存在:
comboGet :: M -> Int
comboGet m = _arr m ! _idx m
comboSet :: M -> Int -> M
comboSet m v = m { _arr = _arr m // [(_idx m, v)] }
combo1 :: Simple Lens M Int
combo1 = lens comboGet comboSet
我了解comboGet
和comboSet
原则上可以改写为仅使用arr
和idx
镜头。
我的问题是:combo1
和arr
中构建idx
的最惯用的方式是什么?
答案 0 :(得分:3)
你可以像这样构建它:
combo :: Lens' M Int -- alias for 'Functor f => (Int -> f Int) -> (M -> f M)
combo f m = arr (\xs -> fmap (\r -> xs // [(i, r)]) $ f $ xs ! i) m
where i = m^.idx
更好的Traversal
版本如下所示:
betterCombo :: Traversal' M Int
betterCombo f m = arr (\xs -> maybe (pure xs)
((\r -> xs // [(i, r)]) <$> f)
xs^? ix i) m
where i = m^.idx
相当于:
betterCombo f m = (arr . ix i) f m
where i = m^.idx
答案 1 :(得分:1)
事实证明,由于!
是部分的,Lens'
不是可行的方式,我应该使用Traversal'
代替:
combine :: Lens' M (Array Int Int) -> Lens' M Int -> Traversal' M Int
comboGet
和comboSet
可以使用镜头组合器重写:
comboGet' :: M -> Int
comboGet' m = m ^?! almostCombo m
comboSet' :: M -> Int -> M
comboSet' m v = m & almostCombo m .~ v where
almostCombo :: M -> Traversal' M Int
almostCombo m = arr . ix (m ^. idx)
@chaosmasttter的 combo
可以写成:
combo2 :: Functor f => (Int -> f Int) -> M -> f M
combo2 f m = arr (\xs -> fmap (\r -> (ix i .~ r) xs) . f $ xs ^?! ix i) m
where i = m^.idx
它的Traversal
签名只会因Applicative
而异:
combo :: Applicative f => (Int -> f Int) -> M -> f M
Applicative
会帮助我们让getter的结果可选吗?
正如@chaosmasttter在他的betterCombo
中指出的那样,关键是要扩大镜头构成以便访问m
。