davidA
davidA

Reputation: 13684

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

I'm learning how to use linear and from the few tutorials I've found it looks like it is designed to work with lens. I'm a beginner with both (and Haskell too, frankly).

In my case I just want to access (and eventually modify) single elements in V4 vectors, and M44 matrices.

So far I've managed to access elements with the _x, _y, _z and _w lenses defined by 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)

However there are two related things I need to understand to proceed from this point.

Part 1

How do I translate that last "set" operation to use word-named Lens functions?

For example:

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

I also didn't have much luck with this kind of approach:

λ> (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)’

I assume I need to combine the set and view operations with the two lenses somehow, so that they form a single reference to the element concerned, but it's not clear to me how to do this and I can't find any appropriate examples.

Part 2

How do I use indexed lenses to access elements of a V4 or M44? For example, if I need to access element [2, 3] rather than _z followed by _w? I see that V4 is an instance of type-class Ixed so after some reading I thought I might be able to do the following:

λ> (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

I can't make head-nor-tail of that error message.

To bring those two parts together, what I want to be able to do is get/set individual elements of an M44 Double matrix, and if Lens is the only/best option then I'd prefer to write my Lens-based code using the descriptive names for Lens functions rather than a fruit-salad of punctuation, at least until I have more experience with the library.

EDIT:

To be clear, the indexing needs to be performed at run-time. The motivation for this is that I have an existing (pretty simple) matrix abstraction layer that has a set of existing unit tests, some of which check individual matrix elements for near equality, sometimes as part of runtime loops. The abstraction layer currently uses a naive matrix implementation which is pretty slow, so I want to integrate a performance library like Linear into the abstraction, but to do this I need to support runtime indexed addressing to have the tests pass.

Upvotes: 1

Views: 295

Answers (2)

Carl
Carl

Reputation: 27023

The design of linear looks like it's not going to support anything cleaner than defining something like

ind 0 = _x
ind 1 = _y
ind 2 = _z
ind 3 = _w

and then using it where you need it. linear just isn't designed to support numerical indexing.

Upvotes: 1

danidiaz
danidiaz

Reputation: 27766

There's no easy way to escape the fruit salad. By that I mean there's no easy way of indexing into an V4 using an Int.

Every type that is an instance of Ix has two other types associated with it: the type of indexes into the type, and the type of the returned values. Haddock obscures things here, because those types are not displayed into the documentation! But after looking in the source code, they are the following:

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

The type of returned values is easy enough, it's just the type that parameterizes the V4. But what is that E in the index?

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

Well, that's weird. It's not an Int, it's a newtype wrapper for lenses going out of the V4.

This means we have to use ix like this:

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)

Another problem with your code is that ix returns a Traversal that can't be used directly with ^. or view, as it might target 0 elements, or more than one (the compiler isn't aware of the fact that, in the particular case of V4, a single element will always be targeted).


How to index into V4 using a number? Here's a hack to do so, but it involves some techniques beyond beginner's level. The trick is to define an auxiliary typeclass IxedV4, and give instances to the type-level nats 1 to 4.

{-# 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

This lets us write the following:

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

Here that 2 following ixV4 is a type, not a term! It doesn't exist at runtime. It's being applied to ixV4 using a type application.

Upvotes: 1

Related Questions