镜头库:这有光学器件吗?

时间:2017-06-07 01:12:40

标签: haskell lens

我有一系列固定长度的载体:

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.

我以为我可以将这些吸气剂和定位器组合成这样的镜头:

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

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

1 个答案:

答案 0 :(得分:1)

我不认为你可以得到你正在寻找的东西,但你可以得到一些相关的东西。这个答案将采取相当迂回的方式来达到我认为你最想要的东西;这是我的思路接近结论的道路,我认为证明了我最终的结果。一般主题是您可以根据自己的情况使用一些不同的合法光学元件,这些光学元件可以以不同的方式使用。

首先,让我们来看看你可以获得的Lens种类。 il Nat标出Vector n中的“窗口”。如果窗口不完全位于矢量中,则您没有表明您想要发生什么。一种选择是简单地要求它适合。另一种选择是将窗口剪切为矢量的大小:

-- 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,使用一个类;我会在帖子的其余部分忽略这个细节)合法的

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

或者,如果您只想允许适合的窗口,

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

我们现在可以通过其中一个镜头工作而不会失去任何一般性(虽然我们会失去效率;更多的是在以后解决这个问题)。这样做意味着我们完全忽略了窗口之外的所有内容,因此我们不再需要提及代理。如果我们有Vector w at的任何光学元件,那么我们就可以生成从Vector n at的光学元件。

将切片操作函数减少到窗口,得到

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

正如您所发现的,这些不会成为Lens。但是如果你进一步减少,用{/ 1>替换setSliceWpartial

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

你可以制定合法的Prism

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

给定Vector w a,您始终可以生成[a],但另一种方式有时是可能的。您当然可以将其与vlvlCut一起使用(如果这是您需要解决的问题,这是一个很好的解决方案),但您不能用它们组成它,因为类型不匹配正确。您可以使用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]

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

traverseVMono :: Traversal' (Vector w a) a

当然,这会立即概括;只需为Traversable编写Vector n个实例,然后使用其traverse

我之前提到过,通过窗口Lens工作效率很低。那你怎么能处理呢?好吧,只是不打扰实际构建窗口!你想要“从头到尾”只是为了遍历窗口。那样做:

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;你必须使用traverseWindow之类的MaybeT (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