Haskell中不同类型的乘法

Haskell中不同类型的乘法,haskell,types,Haskell,Types,我是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 → Di

我是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)
因此,每当我试图在公式中使用错误的度量单位时,编译器都会咬我一口

问题是我不能实现这样的即席polimorphism。通过这段代码,我带来了歧义——编译器可以区分我的操作符*和前奏曲中定义的操作符。声明Num类的实例也是不可能的,因为我需要不同类型的参数

我想知道人们通常是如何解决这个问题的


提前谢谢

通常的方法是创建一个不同的运算符来乘以您的类型-普通的
*
已经被采用。您可以使用字符
!#$%&*+./的任意组合来定义自己的运算符@\^|-~。因此,您可以使用
*.
(TIE战斗机操作员)和
|/|
或类似的工具。

您可以尝试重新设计您的部队系统。试着这样做:

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
编辑: 包中实现了与此类似但完全不同的东西。阅读源代码,它是识字哈斯克尔和相当好的理解。这段代码对于大多数科学应用来说应该足够好了。

如果你想隐藏通常的(*),可以通过

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
我这样做的方式(为了避免类型级别的复杂性,即维度包)基本上是您的新类型解决方案,但有相当数量的帮助函数

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

soforth…

请注意,此实现完全是动态的。有趣的部分是让compiler做这项工作(就像dimensional一样)!对不起,有个问题。在
Unit:/:Unit
中,什么是
:/:
?@Maxym:它是Unit的类型构造函数。我从未见过这样的数据构造函数构造,在两个冒号之间使用中缀函数。我在哈斯克尔还有很多东西要学。你能给我指一下这方面的文档吗?这是一个GHC扩展,每个以冒号开头的被起诉的op都被视为数据构造函数。您也可以在类型级别使用这种名称,例如:->在Haskell中是一个有效的类型名称。我曾经看到一个Ada项目尝试这样做,结果它出了可怕的错误,因为类型系统不符合要求。我建议你要么使用Dimensional(它利用Haskell类型系统的强大功能来做正确的事情),要么坚持使用类型同义词,依靠代码审查来发现缺陷。是的,我也考虑过这一点。我想在这种情况下,使用我的模块的每个人都必须导入prelude来隐藏一些东西?它能自动化吗?可能不会,因为例如,
Data.Foldable
模块在其文档中清楚地说明了如何解决主机模块中的此问题。不幸的是,它无法实现自动化。为了给模块用户带来最小的麻烦,您需要给乘法取一个不同的名称。我理解您为什么有这个
ab->c
。也就是说,
c
a
b
决定。如果是这样,既然
(*)::a->b->c
,为什么还有
bc->a
ca->b
?请详细说明。为什么需要在
序曲
中隐藏
Num
内容<代码>(*)
来自它将干扰
Mul
?我想正确的impl将由类型来解析。