幻影类型背后的动机? [英] Motivation behind Phantom Types?
问题描述
数据比率n =比率Double
1.234 ::比率D3
data询问ccy =询问Double
询问1.5123 ::询问GBP
我阅读了他的关于他们的重点,但我不明白他们。另外,我还阅读了有关该主题的 Haskell Wiki 。然而,我仍然错过了他们的观点。
使用幻影类型的动机是什么?
回答使用幻像类型的动机是什么。有两点:$ b
$ b
- 使无效状态不可表示,这在 Aadit的回答
- 提供类型级别的一些信息
例如,您可以使用长度单位标记距离:
{ - #LANGUAGE GeneralizedNewtypeDeriving# - }
newtype距离a =距离Double
派生(Num,Show)
数据公里
数据Mile
marathonDistance ::距离公里
marathonDistance =距离42.195
distanceKmToMiles ::距离公里数 - >距离Mile
distanceKmToMiles(Distance km)=距离(0.621371 * km)
marathonDistanceInMiles ::距离Mile
marathonDistanceInMiles = distanceKmToMiles marathonDistance
您可以避开火星气候轨道器灾难:
>>> marathonDistanceInMiles
距离26.218749345
>>> marathonDistanceInMiles + marathonDistance
< interactive>:10:27:
无法将类型'Kilometer'与'Mile'匹配
预期类型:距离Mile
Actual类型:Distance Kilometer
在'(+)'的第二个参数中,即'marathonDistance'
在表达式中:marathonDistanceInMiles + marathonDistance
这个模式有轻微的变化。您可以使用 DataKinds
来关闭一组单元:
{ - #LANGUAGE GeneralizedNewtypeDiving#
$ b { - #LANGUAGE KindSignatures# - }
{ - #LANGUAGE DataKinds# - }
数据长度单位=公里数| Mile
$ b $ newtype距离(a :: LengthUnit)=距离Double
派生(Num,Show)
marathonDistance ::距离'公里
marathonDistance =距离42.195
距离KmToMiles ::距离'公里数 - >距离'Mile
distanceKmToMiles(Distance km)=距离(0.621371 * km)
marathonDistanceInMiles :: Distance'Mile
marathonDistanceInMiles = distanceKmToMiles marathonDistance
$ c $ $ b> >>> marathonDistanceInMiles
距离26.218749345
>>> marathonDistance + marathonDistance
距离84.39
>>> marathonDistanceInMiles + marathonDistance
< interactive>:28:27:
无法将类型''公里数'与''Mile'匹配
预期类型:Distance'Mile
实际类型:Distance'公里
在'(+)'的第二个参数中,即'marathonDistance'
在表达式中:marathonDistanceInMiles + marathonDistance
但现在距离
只能以公里或英里计算,因此我们不能再增加更多单位。这可能在某些用例中很有用。
我们也可以这样做:
data距离=距离{distanceUnit :: LengthUnit,distanceValue :: Double}
派生(显示)
在距离情况下,我们可以计算出加法,例如,如果涉及不同的单位,则转换为公里。但是这对于货币并不适用,该比率在一段时间内不是恒定的。
< { - #LANGUAGE GeneralizedNewtypeDeriving # - }
{ - #LANGUAGE KindSignatures# - }
{ - #LANGUAGE DataKinds# - }
{ - #LANGUAGE GADTs# - }
{ - #LANGUAGE StandaloneDeriving# - }
data公里
数据Mile
data距离a where
KilometerDistance :: Double - >距离公里
MileDistance :: Double - >距离Mile
导出实例Show(Distance a)
marathonDistance :: Distance Kilometer
marathonDistance = KilometerDistance 42.195
distanceKmToMiles :: Distance公里 - >距离Mile
distanceKmToMiles(KilometerDistance km)= MileDistance(0.621371 * km)
marathonDistanceInMiles :: Distance Mile
marathonDistanceInMiles = distanceKmToMiles marathonDistance
现在我们知道单位也是在价值层面上:
>>> marathonDistanceInMiles
MileDistance 26.218749345
特别是这种方法从 Aadit的回答中简化 Expr a
示例:
{ - #LANGUAGE GADTs# - }
data Expr a where
Number :: Int - > ; Expr Int
Boolean :: Bool - > Expr Bool
Increment :: Expr Int - > Expr Int
Not :: Expr Bool - > Expr Bool
值得指出的是后者的变化需要非平凡的语言扩展( GADTs
, DataKinds
, KindSignatures
),这在编译器中可能不支持。这可能是 Mu编译器 Don提到的情况。
Don Stewart's Haskell in the Large's presentation mentioned Phantom Types:
data Ratio n = Ratio Double
1.234 :: Ratio D3
data Ask ccy = Ask Double
Ask 1.5123 :: Ask GBP
I read over his bullet points about them, but I did not understand them. In addition, I read the Haskell Wiki on the topic. Yet I still am missing their point.
What's the motivation to use a phantom type?
解决方案 To answer the "what's the motivation to use a phantom type". There is two points:
- to make invalid states inrepresentable, which is well explained in Aadit's answer
- Carry some of the information on the type level
For example you could have distances tagged by the length unit:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
newtype Distance a = Distance Double
deriving (Num, Show)
data Kilometer
data Mile
marathonDistance :: Distance Kilometer
marathonDistance = Distance 42.195
distanceKmToMiles :: Distance Kilometer -> Distance Mile
distanceKmToMiles (Distance km) = Distance (0.621371 * km)
marathonDistanceInMiles :: Distance Mile
marathonDistanceInMiles = distanceKmToMiles marathonDistance
And you can avoid Mars Climate Orbiter disaster:
>>> marathonDistanceInMiles
Distance 26.218749345
>>> marathonDistanceInMiles + marathonDistance
<interactive>:10:27:
Couldn't match type ‘Kilometer’ with ‘Mile’
Expected type: Distance Mile
Actual type: Distance Kilometer
In the second argument of ‘(+)’, namely ‘marathonDistance’
In the expression: marathonDistanceInMiles + marathonDistance
There are slight varitions to this "pattern". You can use DataKinds
to have closed set of units:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
data LengthUnit = Kilometer | Mile
newtype Distance (a :: LengthUnit) = Distance Double
deriving (Num, Show)
marathonDistance :: Distance 'Kilometer
marathonDistance = Distance 42.195
distanceKmToMiles :: Distance 'Kilometer -> Distance 'Mile
distanceKmToMiles (Distance km) = Distance (0.621371 * km)
marathonDistanceInMiles :: Distance 'Mile
marathonDistanceInMiles = distanceKmToMiles marathonDistance
And it will work similarly:
>>> marathonDistanceInMiles
Distance 26.218749345
>>> marathonDistance + marathonDistance
Distance 84.39
>>> marathonDistanceInMiles + marathonDistance
<interactive>:28:27:
Couldn't match type ‘'Kilometer’ with ‘'Mile’
Expected type: Distance 'Mile
Actual type: Distance 'Kilometer
In the second argument of ‘(+)’, namely ‘marathonDistance’
In the expression: marathonDistanceInMiles + marathonDistance
But now the Distance
can be only in kilometers or miles, we can't add more units later. That might be useful in some use cases.
We could also do:
data Distance = Distance { distanceUnit :: LengthUnit, distanceValue :: Double }
deriving (Show)
In the distance case we can work out the addition, for example translate to kilometers if different units are involved. But this doesn't work well for currencies which ratio isn't constant over time etc.
And it's possible to use GADTs for that instead, which may be simpler approach in some situations:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE StandaloneDeriving #-}
data Kilometer
data Mile
data Distance a where
KilometerDistance :: Double -> Distance Kilometer
MileDistance :: Double -> Distance Mile
deriving instance Show (Distance a)
marathonDistance :: Distance Kilometer
marathonDistance = KilometerDistance 42.195
distanceKmToMiles :: Distance Kilometer -> Distance Mile
distanceKmToMiles (KilometerDistance km) = MileDistance (0.621371 * km)
marathonDistanceInMiles :: Distance Mile
marathonDistanceInMiles = distanceKmToMiles marathonDistance
Now we know the unit also on the value level:
>>> marathonDistanceInMiles
MileDistance 26.218749345
This approach especially greately simplifies Expr a
example from Aadit's answer:
{-# LANGUAGE GADTs #-}
data Expr a where
Number :: Int -> Expr Int
Boolean :: Bool -> Expr Bool
Increment :: Expr Int -> Expr Int
Not :: Expr Bool -> Expr Bool
It's worth pointing out that the latter variations require non-trivial language extensions (GADTs
, DataKinds
, KindSignatures
), which might not be supported in your compiler. That's might be the case with Mu compiler Don mentions.
这篇关于幻影类型背后的动机?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!