Умножение разных типов в 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
и так далее...