Haskell:如何使用索引镜头访问 Linear.V 或 Linear.Matrix 的各个元素?

Haskell: How to access individual elements of Linear.V or Linear.Matrix using indexed lenses?

我正在学习如何使用 linear and from the few tutorials I've found it looks like it is designed to work with lens。我是两者的初学者(坦率地说 Haskell 也是)。

在我的例子中,我只想访问(并最终修改)V4 vectors, and M44 矩阵中的单个元素。

到目前为止,我已经成功访问​​了 _x_y_z_w 镜头的元素,这些镜头由 Linear.V4:

λ> import Linear.V4
λ> import Control.Lens
λ> view _x (V4 1 2 3 4)  -- equivalent to V4 1 2 3 4 ^. _x
1

λ> m = identity :: M44 Double
λ> view _x $ view _y m    -- access element [row=1, col=0]
λ> m & _y . _w .~ (2.0)
V4 (V4 1.0 0.0 0.0 0.0) (V4 0.0 1.0 0.0 2.0) (V4 0.0 0.0 1.0 0.0) (V4 0.0 0.0 0.0 1.0)

然而,从这一点出发,我需要了解两件相关的事情。

第 1 部分

如何将最后一个 "set" 操作转换为使用单词命名的 Lens 函数?

例如:

λ> set _w (2.0) $ view _y m
V4 0.0 1.0 0.0 2.0   -- returns a V4 not a V4 (V4 Double)

我也不太喜欢这种方法:

λ> (view _w $ view _y m) .~ 2.0
• Couldn't match type ‘Double’
                 with ‘(a0 -> Identity Double) -> s -> Identity t’
    arising from a functional dependency between:
      constraint ‘mtl-2.2.2:Control.Monad.Reader.Class.MonadReader
                    (V4 (V4 (ASetter s t a0 Double))) ((->) (M44 Double))’
        arising from a use of ‘view’
      instance ‘mtl-2.2.2:Control.Monad.Reader.Class.MonadReader
                  r ((->) r)’

我假设我需要以某种方式将设置和查看操作与两个镜头结合起来,以便它们形成对相关元素的单一引用,但我不清楚如何做到这一点,而且我找不到任何适当的例子。

第 2 部分

如何使用索引镜头访问 V4M44 的元素?例如,如果我需要访问元素 [2, 3] 而不是 _z 后跟 _w?我看到 V4 是 type-class Ixed 的一个实例,所以在阅读之后我认为我可以执行以下操作:

λ> (V4 1 2 3 4) ^. (ix 2)
• Could not deduce (Num (Linear.Vector.E V4))
    arising from the literal ‘2’
  from the context: (Num a, Monoid a)
    bound by the inferred type of it :: (Num a, Monoid a) => a

我无法完全理解该错误消息。

为了将这两个部分放在一起,我希望能够做的是 get/set 个 M44 Double 矩阵的单个元素,如果 Lens 是 only/best 选项,那么我更愿意使用 Lens 函数的描述性名称来编写基于 Lens 的代码,而不是标点符号的水果沙拉,至少在我对库有更多经验之前是这样。

编辑:

明确地说,索引需要在 运行 时执行。这样做的动机是我有一个现有的(非常简单的)矩阵抽象层,它有一组现有的单元测试,其中一些检查各个矩阵元素是否接近相等,有时作为 运行 时间循环的一部分。抽象层目前使用的是非常慢的朴素矩阵实现,所以我想将像 Linear 这样的性能库集成到抽象中,但要做到这一点,我需要支持 运行 时间索引寻址才能拥有测试通过。

没有简单的方法可以逃脱水果沙拉。我的意思是没有简单的方法可以使用 Int.

索引到 V4

作为 Ix 实例的每个类型都有两个与之关联的其他类型:indexes 的类型,以及返回值的类型. Haddock 在这里隐藏了一些东西,因为这些类型没有显示在文档中!但是在查看源代码后,它们如下:

type instance Index (V4 a) = E V4
type instance IxValue (V4 a) = a

返回值的类型很简单,就是参数化V4的类型。但是索引中的 E 是什么?

 newtype E t = E { el :: forall x. Lens' (t x) x }

嗯,这很奇怪。它不是 Int,它是 一种用于 V4.

镜头的新型包装纸

这意味着我们必须像这样使用 ix

ghci> import Control.Lens
ghci> import Linear.V4
ghci> import Linear.Vector
gchi> over (ix (E _y)) (+ 1.0) (V4 1 2 3 4 :: V4 Float)

您的代码的另一个问题是 ix returns 不能直接与 ^.view 一起使用的 Traversal,因为它可能以 0 个或多个元素为目标(编译器不知道这样一个事实,即在 V4 的特定情况下,将始终以单个元素为目标)。


如何使用数字索引到 V4?这是一个 hack 方法,但它涉及一些超出初学者水平的技术。诀窍是定义一个辅助类型类 IxedV4,并为类型级 nats 14 提供实例。

{-# LANGUAGE DataKinds, KindSignatures, AllowAmbiguousTypes, TypeApplications #-}
import Control.Lens
import Linear.V4
import Linear.Vector (E(..))
import GHC.TypeLits (Nat)

class IxedV4 (n::Nat) where
    -- Produce a wrapped lens from a type-level Nat
    -- The Nat will be specified using TypeApplications
    ixV4 :: Traversal' (V4 x) x

instance IxedV4 1 where
    ixV4 = ix $ E _x

instance IxedV4 2 where
    ixV4 = ix $ E _y

instance IxedV4 3 where
    ixV4 = ix $ E _z

instance IxedV4 4 where
    ixV4 = ix $ E _w

这让我们写下:

bar :: V4 Float
bar = over (ixV4 @2) (+ 1.0) (V4 1 2 3 4 :: V4 Float)

这里ixV4后面的2是类型,不是术语!它在运行时不存在。它被应用到 ixV4 使用 type application.

linear 的设计看起来不会支持任何比定义

这样的东西更干净的东西
ind 0 = _x
ind 1 = _y
ind 2 = _z
ind 3 = _w

然后在需要的地方使用它。 linear 只是不支持数字索引。