唐·斯图尔特(Don Stewart)在他的《大规模Haskell》演讲中提到了虚类型:
data Ratio n = Ratio Double
1.234 :: Ratio D3
data Ask ccy = Ask Double
Ask 1.5123 :: Ask GBP
我阅读了他关于它们的要点,但我并不理解它们。此外,我还在该主题上阅读了Haskell Wiki。但我仍然没有明白它们的重点。
使用幻影类型的动机是什么?
唐·斯图尔特(Don Stewart)在他的《大规模Haskell》演讲中提到了虚类型:
data Ratio n = Ratio Double
1.234 :: Ratio D3
data Ask ccy = Ask Double
Ask 1.5123 :: Ask GBP
我阅读了他关于它们的要点,但我并不理解它们。此外,我还在该主题上阅读了Haskell Wiki。但我仍然没有明白它们的重点。
使用幻影类型的动机是什么?
{-# 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
这个“模式”有一些细微的变化。你可以使用 DataKinds
来获得一个封闭的单位集合:
{-# 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
它的工作方式类似:
>>> 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
但是现在Distance
只能以公里或英里为单位,之后我们无法再增加更多的单位。这在某些情况下可能会很有用。
我们还可以做:
data Distance = Distance { distanceUnit :: LengthUnit, distanceValue :: Double }
deriving (Show)
{-# 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
>>> 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编译器的情况。
data List a = Nil | Cons a (List a)
Nil
和Cons
的返回类型默认为List a
(适用于所有类型为a
的列表)。请注意,保留了HTML标签。Nil :: List a
Cons :: a -> List a -> List a
|____|
|
-- return type is generalized
Nil
是一个幻构造器(即其返回类型不取决于其参数,在这种情况下虽然无意义但仍然相同)。Nil
是一个幻构造器,我们可以将Nil
专门用于任何类型(例如Nil :: List Int
或Nil :: List Char
)。
Cons
的参数类型(a
和List a
)。data Expr a = Number Int
| Boolean Bool
| Increment (Expr Int)
| Not (Expr Bool)
Number :: Int -> Expr a
Boolean :: Bool -> Expr a
Increment :: Expr Int -> Expr a
Not :: Expr Bool -> Expr a
Number
和Increment
必须始终返回Expr Int
,而Boolean
和Not
必须始终返回Expr Bool
。Number
不可能返回Expr a
,但实际上它确实返回了。这会让您编写错误的表达式,而类型检查器无法捕获。例如:Increment (Boolean False) -- you shouldn't be able to increment a boolean
Not (Number 0) -- you shouldn't be able to negate a number
Expr
的所有数据构造函数都是幻影构造函数(即它们的返回类型不依赖于它们的参数)。所有构造函数都是幻影构造函数的数据类型称为幻影类型。Nil
这样的幻影构造函数的返回类型可以专门针对我们想要的任何类型进行特化。因此,我们可以如下创建 Expr
的智能构造函数:number :: Int -> Expr Int
boolean :: Bool -> Expr Bool
increment :: Expr Int -> Expr Int
not :: Expr Bool -> Expr Bool
number = Number
boolean = Boolean
increment = Increment
not = Not
increment (boolean False) -- error
not (number 0) -- error
当您想要专门化数据构造函数的返回类型时,幽灵构造函数非常有用。而幽灵类型是指其构造函数均为幽灵构造函数的数据类型。
请注意,像 Left
和 Right
这样的数据构造函数也是幽灵构造函数:
data Either a b = Left a | Right b
Left :: a -> Either a b
Right :: b -> Either a b
data Expr a = Expr Int | ...
,但不会公开它们的构造函数?有什么方法可以防止某人仍然使用“非智能”构造函数吗? - Kevin Meredithmodule MyModule (Expr()、number、boolean、increment、not) where
。这样可以防止人们直接使用实际的构造函数,强制他们使用智能构造函数。请注意,这也意味着人们将无法进行模式匹配。因此,您需要提供一些方法来允许他们对数据进行解构。 - Aadit M ShahRatio D3
,我们使用类似的丰富类型来驱动类型导向的代码。例如,如果您在某个地方有一个类型为Ratio D3
的字段,则其编辑器将被分派到一个仅接受数字输入并显示3位精度的文本字段中。相比之下,例如newtype Amount = Amount Double
,我们不显示小数位,但使用千位逗号,并将输入解析为'10m',如'10,000,000'。Double
。
fn.bind(this)
,我也遇到类似的问题——等等,bind
是什么? - phadej