Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have an operator which adds/subtracts both absolute and relative values, in Haskell

(Apologies for the weird title, but I could not think of a better one.)

For a personal Haskell project I want to have the concepts of 'absolute values' (like a frequency) and relative values (like the ratio between two frequencies). In my context, it makes no sense to add two absolute values: one can add relative values to produce new relative values, and add a relative value to an absolute one to produce a new absolute value (and likewise for subtraction).

I've defined type classes for these: see below. However, note that the operators ##+ and #+ have a similar structure (and likewise for ##- and #-). Therefore I would prefer to merge these operators, so that I have a single addition operator, which adds a relative value (and likewise a single subtraction operator, which results in a relative value). UPDATE: To clarify, my goal is to unify my ##+ and #+ into a single operator. My goal is not to unify this with the existing (Num) + operator.

However, I don't see how to do this with type classes.

Question: Can this be done, and if so, how? Or should I not be trying?

The following is what I currently have:

{-# LANGUAGE MultiParamTypeClasses #-}

class Abs a where
  nullPoint :: a

class Rel r where
  zero :: r
  (##+) :: r -> r -> r
  neg :: r -> r

  (##-) :: Rel r => r -> r -> r
  r ##- s = r ##+ neg s

class (Abs a, Rel r) => AbsRel a r where
  (#+) :: a -> r -> a
  (#-) :: a -> a -> r
like image 977
MarnixKlooster ReinstateMonica Avatar asked Apr 02 '13 06:04

MarnixKlooster ReinstateMonica


3 Answers

I think you're looking for a concept called a Torsor. A torsor consists of set of values, set of differences, and operator which adds a difference to a value. Additionally, the set of differences must form an additive group, so differences also can be added together.

Interestingly, torsors are everywhere. Common examples include

  • Points and Vectors
  • Dates and date-differences
  • Files and diffs

etc.

One possible Haskell definition is:

 class Torsor a where
    type TorsorOf a :: *
    (.-) :: a -> a -> TorsorOf a
    (.+) :: a -> TorsorOf a -> a

Here are few example instances:

 instance Torsor UTCTime where
    type TorsorOf UTCTime = NominalDiffTime
    a .- b = diffUTCTime a b 
    a .+ b = addUTCTime b a

 instance Torsor Double where
    type TorsorOf Double = Double
    a .- b = a - b
    a .+ b = a + b

instance Torsor Int where
    type TorsorOf Int = Int
    a .- b = a - b
    a .+ b = a + b

In the last case, notice that the two sets of the torsors don't need to be a different set, which makes adding your relative values together simple.

For more information, see a much nicer description in Roman Cheplyakas blog

like image 112
aleator Avatar answered Nov 10 '22 00:11

aleator


I don't think you should be trying to unify these operators. Subtracting two vectors and subtracting two points are fundamentally different operations. The fact that it's difficult to represent them as the same thing in the type system is not the type system being awkward - it's because these two concepts really are different things!

The mathematical framework behind what you're working with is the affine space.

These are already available in Haskell in the vector-space package (do cabal install vector-space at the command prompt). Rather than using multi parameter type classes, they use type families to associate a vector (relative) type with each point (absolute) type.

Here's a minimal example showing how to define your own absolute and relative data types, and their interaction:

{-# LANGUAGE TypeFamilies #-}

import Data.VectorSpace
import Data.AffineSpace

data Point = Point { px :: Float, py :: Float }

data Vec = Vec { vx :: Float, vy :: Float }

instance AdditiveGroup Vec where
    zeroV = Vec 0 0
    negateV (Vec x y) = Vec (-x) (-y)
    Vec x y ^+^ Vec x' y' = Vec (x+x') (y+y')

instance AffineSpace Point where
  type Diff Point = Vec
  Point x y .-. Point x' y' = Vec (x-x') (y-y')
  Point x y .+^ Vec x' y' = Point (x+x') (y+y')
like image 37
Chris Taylor Avatar answered Nov 10 '22 01:11

Chris Taylor


You have two answers telling you what you should do, here's another answer telling you how to do what you asked for (which might not be a good idea). :)

class Add a b c | a b -> c where
    (#+) :: a -> b -> c

instance Add AbsTime RelTime AbsTime where
    (#+) = ...
instance Add RelTime RelTime RelTime where
    (#+) = ...

The overloading for (#+) makes it very flexible. Too flexible, IMO. The only restraint is that the result type is determined by the argument types (without this FD the operator becomes almost unusable because it constrains nothing).

like image 22
augustss Avatar answered Nov 09 '22 23:11

augustss