简体   繁体   中英

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

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)'

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 eg

fmap ($1) (np1 + np2)

Various libraries have special operators for this, eg

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 .

I include a renaming approach in Listing 2 and a qualified import approach in Listing 3 .

Listing 2 contains code that I believe is reasonably close to the original code . It was necessary rename the operations in Number by appending (!). This avoids a clash with the operations in Prelude Num class. I believe that there were two errors in the original code . The most serious is in the instance Number (Moving Float) where the same operation symbols are used on the left and right of the equations (eg +). The compiler has no way to distinguish these operations. The other error is a syntax error instance Number v => (Point v) there is no class name after => . In sort the original code will not run, which was the motivation behind the question.

Listing 2

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

class Number a where
 (+!), (-!), (*!) :: a -> a -> a
 sqr1, sqrt1 :: a -> a

-- Define Number operations in terms of Num operations from Prelude
-- Original code does not distinguish between these operation and will not compile.
instance  Number (Moving Float) where
 (+!) a b = \t -> (a t) + (b t)
 (-!) a b = \t -> (a t) - (b t)
 (*!) a b = \t -> (a t) * (b t)
 sqrt1 a = \t -> sqrt (a t)
 sqr1 a = \t -> ((a t) * (a t))

data Point f = Point f f deriving Show

class Number s => Points p s where
 x, y :: p s -> s
 xy :: s -> s -> p s
 dist :: p s -> p s -> s
 dist a b = sqrt1 (sqr1 ((x a) -! (x b)) +! sqr1 ((y a) -! (y b)))

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

-- Syntax error in instance header in original code.
instance Number (Point (Moving Float)) 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)
 sqrt1 a =  xy (sqrt1 (x a)) (sqrt1 (y a))
 sqr1 a =  xy (sqr1 (x a)) (sqr1 (y a))


mp1, mp2 ::  Point (Moving Float)
mp1  = (xy (\t -> 4.0 + 0.5 * t) (\t -> 4.0 - 0.5 * t)) 
mp2  = xy (\t -> 0.0 + 1.0 * t) (\t -> 0.0 - 1.0 * t)
movingDist_1_2 = dist mp1 mp2
dist_at_2 = movingDist_1_2 2.0 -- gives 5.83

Listing 3 uses a qualified import as suggested by ben . Note we need an additional instance to define the operations in the Number class using the Num class.

Listing 3

module T where
import qualified Prelude as P

type Time = P.Float
type Moving v = Time -> v

class Number a where
 (+), (-), (*) :: a -> a -> a
 sqr, sqrt:: a -> a

instance Number P.Float where
 (+) a b =  a P.+ b
 (-) a b =  a P.- b
 (*) a b =  a P.* b
 sqrt a =  P.sqrt a
 sqr a = a P.* a
  
instance  Number (Moving P.Float) where
 (+) a b = \t -> (a t) + (b t)
 (-) a b = \t -> (a t) - (b t)
 (*) a b = \t -> (a t) * (b t)
 sqrt a = \t -> sqrt (a t)
 sqr a = \t -> ((a t) * (a t))

data Point f = Point f f deriving P.Show

class Number s => Points p s where
 x, y :: p s -> s
 xy :: s -> s -> p s
 dist :: p s -> p s -> s
 dist a b = sqrt (sqr ((x a) - (x b)) + sqr ((y a) - (y b)))

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

instance Number (Point (Moving P.Float)) 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)
 sqrt a =  xy (sqrt (x a)) (sqrt (y a))
 sqr a =  xy (sqr (x a)) (sqr (y a))

mp1, mp2 ::  Point (Moving P.Float)
mp1  = xy (\t -> 4.0 + (0.5 * t)) (\t -> 4.0 - (0.5 * t))
mp2  = xy (\t -> 0.0 + (1.0 * t)) (\t -> 0.0 - (1.0 * t))
movingDist_1_2 = dist mp1 mp2
dist_at_2 = movingDist_1_2 2.0

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM