Home > OS >  Lifting of the addition operation from Haskell's Num class to dynamic values
Lifting of the addition operation from Haskell's Num class to dynamic values

Time:07-08

I am trying to implement my code based almost directly on a paper (pages 34-35). I am using Haskell's Num class instead of the user-defined Number class suggested in the paper.

I want to focus on implementing addition over dynamic time-varying Float values, and subsequently addition over time-varying Points.

Listing 1 is my attempt. How do I get addition of points with time-varying coordinates to work properly? My research requires a review of the code in that particular paper. As far as it is practical, I need to stick to the structure of the original code in the paper. In other words, what do I need to add to Listing 1 to overload ( ) from Num to perform addition on time varying points?

module T where
type Time = Float
type Moving v = Time -> v

instance Num v => Num (Moving v) where
 ( ) a b = \t -> (a t)   (b t)
 (-) a b = \t -> (a t) - (b t)
 (*) a b = \t -> (a t) * (b t)
 
-- tests for time varying Float values, seems OK
a,b::(Moving Float)
a = (\t -> 4.0)
b = (\t -> 5.0)
testA =  a 1.0
testAddMV1 = (a   b ) 1.0
testAddMV2 = (a   b ) 2.0

-- Point Class
class Num s => Points p s where
 x, y :: p s -> s
 xy :: s -> s -> p s


data  Point f = Point f f deriving Show


instance Num v => Points Point v where
 x (Point x1 y1) = x1
 y (Point x1 y1) = y1
 xy x1 y1 = Point x1 y1


instance Num v => Num (Point (Moving v)) where
 ( ) a b = xy (x a   x b) (y a   y b)
 (-) a b = xy (x a - x b) (y a - y b)
 (*) a b = xy (x a * x b) (y a * y b)

-- Cannot get this to work as suggested in paper.
np1, np2 :: Point (Moving Float)
np1 = xy (\t -> 4.0   0.5 * t) (\t -> 4.0 - 0.5 * t)
np2 = xy (\t -> 0.0   1.0 * t) (\t -> 0.0 - 1.0 * t)

-- Error
-- testAddMP1 = (np1   np2 ) 1.0
-- * Couldn't match expected type `Double -> t'
--   with actual type `Point (Moving Float)'

CodePudding user response:

The error isn't really about the addition operation. You also can't write np1 1.0 because this is a vector (I don't particularly like calling it that) whose components are functions. Whereas you try to use it as a function whose values are vectors.

What you're trying to express here is, "evaluate both the component-functions at this time-slice, and give me back the point corresponding to both coordinates". The standard solution (which I don't recommend, though) is to give Point a Functor instance. This is something the compiler can do for you:

{-# LANGUAGE DeriveFunctor #-}
data  Point f = Point f f
 deriving (Show, Functor)

And then you can write e.g.

fmap ($1) (np1   np2)

Various libraries have special operators for this, e.g.

import Control.Lens ((??))

np1   np2 ?? 1

Why is a functor instance a bad idea? For the same reason it's a bad idea to implement multiplication on points as component-wise multiplication: it does not make sense physically. Namely, it depends on a particular choice of coordinate system, but the choice of coordinate frame is in principle arbitrary and should not affect the results. For addition it indeed does not affect the result (disregarding float inaccuracy), but for multiplication or arbitrary function-mapping it can massively affect the result.

A better solution is to just not use "function-valued points" in the first place, but instead point-valued functions.

np1, np2 :: Moving (Point Float)
np1 = \t -> xy (4.0   0.5 * t) (4.0 - 0.5 * t)
np2 t = xy (0.0   1.0 * t) (0.0 - 1.0 * t)

Actually a functor instance is a less bad idea than a Num instance. The particular operation fmap ($1) is in fact equivariant under coordinate transformation. That's because point-evaluation of functions is a linear mapping. To properly express this, you could make Point an endofunctor in the category of linear maps.

  • Related