一般函数应用于Haskell中的标记类型

时间:2014-04-23 12:38:24

标签: haskell

假设我已经定义了这些类型:

data Km     = Km Float deriving (Show, Eq)
data Mile   = Mile Float deriving (Show, Eq)
data Meter  = Meter Float deriving (Show, Eq)
data Feet   = Feet Float deriving (Show, Eq)

我有一些功能,如:

kms_to_miles    :: Km   -> Meter
kms_to_feets    :: Km   -> Feet
miles_to_meters :: Mile -> Meter

这很完美,因为我无法混合单位。

现在,如果我有一些简单的功能:

doubleTheDistance' :: Float -> Float
doubleTheDistance' x = 2 * x

我想把它应用到我可以的类型中:

class Dbl a where
    doubleTheDistance :: a -> a
instance Dbl Km where
    doubleTheDistance (Km x) = Km (2 * x)
instance Dbl Mile where
    doubleTheDistance (Mile x) = Mile (2 * x)
--  an instance with the same for all my types here...  

由于我的类型不是参数化的,我无法使用Functor并将一般函数( doubleTheDistance' )映射到它们上。

我错过了什么吗?有没有办法抽象出这种行为,所以我不必为每个标记类型编写相同的函数?

3 个答案:

答案 0 :(得分:4)

像往常一样,lens可以简化这一过程。它提供了通过Iso s。转换为新类型的工具。

将新类型的实例添加到Wrapped类:

newtype Km = Km { getFloatKm :: Float }
makeWrapped ''Km

现在,您可以使用普通的Iso工具来处理newtype包装器。您可以通过Iso_Unwrapped函数访问_Wrapped

let x = Km 5 in x & _Wrapped %~ (*2)
> Km {getFloatKm = 10.0}

这当然比这个简单的例子更强大。

答案 1 :(得分:4)

另一种方法是使用phantom type来编码“单位”。这仍然为您提供类型安全的特定类型,例如Distance Km,但也允许您在需要时应用更多通用函数,例如doubleTheDistance

data Km
data Mile
data Meter
data Feet

data Distance m = Distance Float  deriving (Show, Eq)

kms_to_miles :: Distance Km -> Distance Mile
kms_to_miles (Distance km) = Distance (km*0.621371192)

doubleTheDistance :: Distance m -> Distance m
doubleTheDistance (Distance x) = Distance (2 * x)

-- Tests
km = Distance 5 :: Distance Km

doubled = doubleTheDistance km

你甚至可以showDistance

{-# LANGUAGE ScopedTypeVariables  #-}

data Km
data Mile
data Meter
data Feet

data Distance m = Distance Float deriving Eq

instance Show Km where
   show _ = "km"
instance Show Mile where
   show _ = "ml"
instance Show Feet where
   show _ = "ft"

instance Show m => Show (Distance m) where
   show (Distance a) = show a ++ " " ++ show (undefined :: m)

km = Distance 5 :: Distance Km -- Is shown as "0.5 km"

答案 2 :(得分:1)

没有扩展的一种方法:

class Distance a where
  toFloat :: a -> Float
  fromFloat :: Float -> a

instance Distance Miles where
  toFloat (Miles x) = x
  fromFloat x = Miles x

-- same for other units

mapDistance :: Distance a => (Float -> Float) -> a -> a
mapDistance f = fromFloat . f . toFloat

doubleTheDistance = mapDistance (* 2.0)

你仍然需要一些重复,但每个单元只需要一次,而不是每个功能一次。在实践中,我很可能更喜欢西蒙的方法。当然,它们可以结合起来:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

instance Distance Float where
  toFloat x = x
  fromFloat x = x

newType Km = Km Float deriving (Show, Eq, Distance)

-- definitions of mapDistance and doubleTheDistance as above