Haskell中不同类型的乘法
我是haskell的新手,我偶尔会遇到一个问题,我会在这里用几句话来描述。想象一下,我想为不同的度量值声明不同的类型,这样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
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将由类型来解析。