我一直对haskell及其声称可以避免像火星轨道飞行器坠毁的问题(由于两个程序中的惯例之间的差异,英里与公里数:source)的问题留下了深刻的印象。在编译时。虽然我不记得我在哪里读到这个了。
我认为原始程序的形式(用其他语言表达):
speed :: Fractional a => a -> a -> a
speed distance time = distance * 1.0 / time
我尝试编写一个可以优雅地处理haskell中此错误的变体(暂时忽略除零):
data Dist a = KM a | Miles a deriving (Show)
speed' :: Fractional a => Dist a -> a -> a
speed' (KM dist) time = dist * 1.0 / time
speed' (Miles dist) time = dist * 1.6 / time
现在,速度'的返回类型仍然不明显是KM /秒
现在我想回答这个问题;什么会使返回类型更明显的好的haskell实现看起来像:
因为我试图写一些数据类型(我可能走错了路?)。
data Distance a = Meter a | Inch a | Furlong a | LightYear a | Fathom a deriving (Show)
data Time a = Second a | Hour a | Minute a | Year a deriving (Show)
[你可以想象更多的距离和时间值]
对于这些数据类型中的每一种,我们都可以实现必要的转换,因此您可以按如下方式对每种类型执行操作:
instance Num a => Num (Time a) where
Second a + Second b = Second (a+b)
Minute a + Minute b = Minute (a+b)
Minute a + Second b = Second (a*60 + b)
Second a + Minute b = Second ( a + b*60)
Second a - Second b = Second (a-b)
Minute a - Minute b = Minute (a-b)
Minute a - Second b = Second (a*60 - b)
Second a - Minute b = Second ( a - b*60)
abs (Second a) = Second (abs a)
abs (Minute a) = Minute (abs a)
signum (Second a) = Second (signum a)
signum (Minute a) = Minute (signum a)
fromInteger a = Second (fromInteger a)
-- I'm clearly not thinking along the right 'haskelly' lines for these :/
-- These are to deal with concepts like acceleration distance/time squared
-- Second a * Second b = Second (Second (a*b)
-- Second a * Minute b = Second (Second (a*b*60))
-- Minute a * Second b = Second (Second (a*b*60))
-- Minute a * Num b = Minute (a*b)
-- Second a * Num b = Second (a*b)
[同样适用于距离的所有转换]
此时我迷失了如何使用这些新数据类型以合理的方式实现速度,以便可以对它们进行操作[比如添加]。
答案 0 :(得分:2)
在您提议的speed'
中,确实不清楚结果的单位是什么。您不能只接受Distance a
并返回a
,因为这样单位信息就会消失。相反,你需要像
data DistanceUnit = Mile | Kilometer
data TimeUnit = Second | Hour
data Dist a = Dist a DistanceUnit
data Time a = Time a TimeUnit
speed'' :: Fractional a => Dist a -> Time a -> Dist (Time a)
speed'' (Dist d dUnit) (Time t tUnit) = Dist (Time (d / t) tUnit) dUnit
inMiles :: Fractional a => Dist a -> a
inMiles (Dist d Mile) = d
inMiles (Dist d Kilometer) = d / 1.6
inHours :: Fractional a => Time a -> a
-- same pattern as above
inMph :: Fractional a => Dist (Time a) -> a
inMph (Dist (Time s tUnit) dUnit) = inMiles (Dist s dUnit) / inHours (Time s tUnit)
thrusterAmount :: Fractional a => Dist (Time a) -> Int
thrusterAmount dv = inMph dv / 2 -- rocket science goes here
goFaster :: Fractional a => Dist (Time a) -> IO ()
goFaster dv = applyThrusters . thrusterAmount $ dv
我把这个例子拼凑在一起非常原始并且拼凑在一起,但是我希望你能看到这个想法:保持单位并按照通用的方式写speed
等函数;以某种方式表达距离,划分时间以某种方式表达"。然后,当你真的需要知道一个量级以便在系统外部做一些事情时,比如点燃推进器,你可以将维度数量转换为一个裸数,隐含在任何方便你的单位。
比我更复杂的单位系统可以更好地表达复合单位,如m / s或m ^ 2s,但无论如何,每个函数的维度(但不是它的单位!)都编码为其类型:{{ 1}}需要一段距离和一个时间,并且每次返回一个距离,speed
可能需要一个起始速度,一个加速度和一个时间;并返回一段距离。没有机会误解任何这些东西,除了在系统的边界,你可以调用distanceTraveled
,然后立即将其视为kph,但接近{{1调用应该使这些错误非常明显。