镜头库:这个有光学元件吗?

Lens lib: Is there an optic for this?

我有一组固定长度的向量:

data family Vector (n :: Nat) a
data instance Vector 2 a = Vector2 a a
data instance Vector 3 a = Vector3 a a a
-- and so on

以及两个用于获取和设置向量切片作为列表的函数:

getSlice :: Proxy i -> Proxy l -> Vector n a -> [a]
setSlice :: Proxy i -> Proxy l -> Vector n a -> [a] -> Maybe (Vector n a)
-- ^ setSlice is partial because list parameter may not have enough elements.

我想我可以将这些 getter 和 setter 组合成这样的镜头:

slice :: Proxy i -> Proxy l -> Lens (Vector n a) (Maybe (Vector n a)) [a] [a]
slice i l = lens (getSlice i l) (setSlice i l)

但这违反了镜头法(http://hackage.haskell.org/package/lens-4.15.2/docs/Control-Lens-Lens.html#t:Lens

所以我想知道是否有这样的结构?

我不认为你能得到你正在寻找的东西,但你可以得到一些相关的东西。这个答案将采取一个相当迂回的路径来达到我认为你最可能想要的东西;这是我的想法接近结论的路径,我认为证明了我最终的结论。总的主题是,有几种不同的合法光学器件可以适用于您的情况,并且可以以不同的方式发挥作用。

首先,让我们看看您可以得到什么样的LensilNatVector n中标出一个"window"。如果 window 不完全位于向量内,您还没有指出要发生什么。一种选择是简单地要求它适合。另一种选择是将 window 裁剪为向量的大小:

-- Minimum
type Min m n = Min' (m <=? n) m n
type family Min' m_le_n (m :: Nat) (n :: Nat) where
  Min' 'True m _ = m
  Min' 'False _ n = n

-- Saturated subtraction
type SatMinus m n = SatMinus' (n <=? m) m n
type family SatMinus' n_le_m m n where
  SatMinus' 'True m n = m - n
  SatMinus' 'False _ _ = 0

-- Window clipping
type ClippedLength i l n = Min l (SatMinus n i)

现在您当然可以定义(对于每个 n,使用 class;我将在 post 的其余部分忽略此细节)一个合法的

vlCut :: (KnownNat i, KnownNat l)
   => Proxy i -> Proxy l
   -> Lens' (Vector n a) (Vector (ClippedLength i l n) a)

或者,如果您只想让 windows 适合,

vl :: (KnownNat i, KnownNat j, i + l <= n)
   => Proxy i -> Proxy l
   -> Lens' (Vector n a) (Vector l a)

我们现在可以在不失去任何普遍性的情况下通过这些镜头中的一个来工作(尽管我们会失去效率;稍后会详细介绍)。这样做意味着我们可以完全忽略 window 之外的所有内容,因此我们无需再提及代理。如果我们有从 Vector w at 的任何光学器件,那么我们可以生产从 Vector n at.

的光学器件

将你的切片操作函数减少到 window,你得到

getSliceW :: Vector w a -> [a]
setSliceWpartial :: Vector w a -> [a] -> Maybe (Vector w a)

正如您发现的那样,这些并不构成 Lens。但是,如果您再减少一点,将 setSliceWpartial 替换为

fromList :: [a] -> Maybe (Vector w a)

你可以合法Prism:

slicep :: Prism' [a] (Vector w a)

给定一个 Vector w a,你总是可以产生一个 [a],但另一种方式只是有时可能。你当然可以将它与 vlvlCut 一起使用(如果这是你需要解决的问题,那是一个很好的解决方案),但你不能 compose 它与他们,因为类型不匹配。您可以使用 re 反转棱镜,但最后只会给您一个 Getter


由于您的类型似乎不太合适,让我们尝试更改它们:

getSliceW :: Vector w a -> [a]
setSliceW :: Vector w a -> [a] -> Vector w a

现在我们用鲈鱼做饭!它具有 类型 Lens' (Vector w a) [a] 碎片,尽管它实际上不是合法的镜头。然而,这是一个很好的线索。 Control.Lens.Traversal 优惠

partsOf' :: ATraversal s t a a -> Lens s t [a] [a]

在这种情况下,您可以阅读为

partsOf' :: Traversal' (Vector w a) a -> Lens' (Vector w a) [a]

所以(通过window),我们真正想要的是

traverseVMono :: Traversal' (Vector w a) a

当然,这会立即概括;只需为 Vector n 编写一个 Traversable 实例并使用它的 traverse.


我之前提到过 window Lens 的工作效率很低。那你怎么处理呢?好吧,只是不要费心实际构建 window!你想去"from end to end"只是为了遍历window。这样做:

traverseWindow :: (KnownNat i, KnownNat l, Applicative f)
               -- optionally require i + l <= n
               => proxy1 i
               -> proxy2 l
               -> (a -> f a)
               -> Vector n a
               -> f (Vector n a)

如果你愿意,你可以恢复原来的部分setSlice;你只需要使用 traverseWindowMaybeT (State [a]):

foldMapWindow :: (KnownNat i, KnownNat l, Monoid m)
  => proxy1 i
  -> proxy2 l
  -> (a -> m)
  -> Vector n a
  -> m
foldMapWindow p1 p2 f = getConst . traverseWindow p1 p2 (Const . f)

windowToList :: (KnownNat i, KnownNat l)
  => proxy1 i
  -> proxy2 l
  -> Vector n a
  -> [a]
windowToList p1 p2 = foldMapWindow p1 p2 (:[])

setSlice :: (KnownNat i, KnownNat l)
         => proxy1 i -> proxy2 l
         -> Vector n a -> [a] -> Maybe (Vector n a)
setSlice p1 p2 v xs = flip evalState (windowToList p1 p2 v) . runMaybeT $ flip (traverseWindow p1 p2) v $ \_ -> do
  y : ys <- get
  put ys
  pure y