幻影类型背后的动机? [英] Motivation behind Phantom Types?

查看:163
本文介绍了幻影类型背后的动机?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Don Stewart's Haskell在Large 的演示中提到了 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
  

$ 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆