正确处理类型信息

时间:2015-08-20 04:27:28

标签: haskell

我一直对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)

[同样适用于距离的所有转换]

此时我迷失了如何使用这些新数据类型以合理的方式实现速度,以便可以对它们进行操作[比如添加]。

1 个答案:

答案 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调用应该使这些错误非常明显。