假设我已经定义了这些类型:
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' )映射到它们上。
我错过了什么吗?有没有办法抽象出这种行为,所以我不必为每个标记类型编写相同的函数?
答案 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
你甚至可以show
:Distance
:
{-# 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