Умножение разных типов в Haskell

Я новичок в haskell, и время от времени я сталкиваюсь с проблемой, которую я попытаюсь описать здесь в нескольких словах. Представьте себе, я хочу объявить разные типы для разных мер, поэтому система типа Haskell найдет ошибки в моих формулах:

newtype Dist = Km Float
newtype Time = H Float
newtype Velocity = KmH Float

(/) :: Dist → Time → Velocity
(Km d) / (H t) = KmH (d / v)

(*) :: Velocity → Time → Dist
(KmH v) * (H t) = Km (v * t)

поэтому в любое время, когда я пытаюсь использовать неправильные единицы измерения в моих формулах, компилятор кусает.

Проблема заключается в том, что я не могу реализовать ad-hoc-полиморфизм следующим образом. С помощью этого кода я привожу двусмысленность - компилятор может отличить мой оператор * и тот, который определен в Prelude. Объявление экземпляра класса Num также невозможно, так как мне нужны разные типы параметров.

Мне интересно, как люди обычно решают проблему.

Спасибо заранее!

Ответ 1

Вы можете попытаться немного перестроить систему. Попробуйте что-то вроде этого:

data Unit = Unit String
          | Unit :/: Unit
          | Unit :*: Unit
          | Unit :^: Int
          deriving (Show,Eq)

instance Num Unit where
  -- Insert reasonable definition here
  x * y = ...

data UnitNum n = UN n Unit

instance Num (Num n) => UnitNum n where
  UN n u + Un k u' | u == u' = UN (n+k) u
                   | otherwise = error ...
  -- insert other definitions here.

km,h,kmh :: Unit

km = Unit "km"
h = Unit "h"
kmh = km / h

Edit:

Что-то похожее на это, но совсем другое было реализовано в пакете dimensional. Прочтите исходный код, он грамотно Haskell и неплохо понять. Этот код должен быть достаточно хорош для большинства научных приложений.

Ответ 2

Вы можете скрыть обычный (*), если хотите,

import Prelude hiding((*))

Или скрыть все Num

import Prelude hiding(Num(..))

Затем вы можете определить свое собственное умножение, возможно, по линиям

class Mul a b c | a b -> c, b c -> a, c a -> b where
    (*) :: a -> b -> c

Ответ 3

Обычный способ заключается в создании другого оператора для умножения вашего типа - обычная * уже выполнена. Вы можете определить своих операторов, используя любую комбинацию символов !#$%&*+./<=>[email protected]\^|-~. Таким образом, вы можете использовать |*| (оператор TIE Fighter) и |/| или что-то в этом роде.

Ответ 4

То, как я это сделал (чтобы избежать сложностей на уровне типов, которые являются пакетом размеров), является в основном вашим решением нового типа, но с множеством вспомогательных функций.

class Units a where
    wrap :: Double -> a
    unWrap :: a -> Double

instance Units Double where
    wrap = id
    unWrap = id

inWrap :: (Units a) => (Double -> Double) -> a -> a
inWrap f = wrap . f . unWrap

newtype Years = Years Double deriving (Typeable, Show, Eq, Ord)
instance Units Years where
    wrap = Years
    unWrap (Years x) = x

newtype a :/: b = Per a deriving (Typeable, Show, Eq, Ord)
instance Units a => Units (a :/: b) where
    wrap = Per . wrap
    unWrap (Per x) = unWrap x

perYears :: a -> a :/: Years
perYears = Per

class Additive a where
    (+%) :: a -> a -> a
    negU :: a -> a
    (-%) :: a -> a -> a
    x -% y = x +% negU y

instance Units a => Additive a
    where x +% y = wrap $ unWrap x + unWrap y
          negU = inWrap negate

class ScalarMult f where
    (.*) :: f -> Double -> f
class ScalarAdd f where
    (.+) :: f -> Double -> f

instance Units a => ScalarAdd a where
    f .+ v = inWrap (+ v) f
instance Units a => ScalarMult a where
    f .* v = inWrap (* v) f

и так далее...