Haskell 中的参数化类型
Parameterized Types in Haskell
为什么 Haskell 中的类型必须在类型构造函数参数中显式参数化?
例如:
data Maybe a = Nothing | Just a
这里a
必须指定类型。为什么不能只在构造函数中指定?
data Maybe = Nothing | Just a
他们为什么从设计的角度做出这样的选择?一个比另一个好吗?
我知道第一个比第二个强类型,但第二个甚至没有选项。
编辑:
示例函数
data Maybe = Just a | Nothing
div :: (Int -> Int -> Maybe)
div a b
| b == 0 = Nothing
| otherwise = Just (a / b)
使用 GADT 表示法可能会解决问题,因为标准表示法将类型级和值级语言混为一谈。
标准 Maybe
类型看起来像一个 GADT:
{-# LANGUAGE GADTs #-}
data Maybe a where
Nothing :: Maybe a
Just :: a -> Maybe a
“未参数化”版本也是可能的:
data EMaybe where
ENothing :: EMaybe
EJust :: a -> EMaybe
(正如 Joseph Sible 评论的那样,这被称为 存在类型 )。现在你可以定义
foo :: Maybe Int
foo = Just 37
foo' :: EMaybe
foo' = EJust 37
很好,为什么我们不总是使用 EMaybe
?
嗯,问题是当你想要使用这样的值时。使用 Maybe
很好,您可以完全控制包含的类型:
bhrar :: Maybe Int -> String
bhrar Nothing = "No number "
bhrar (Just i)
| i<0 = "Negative "
| otherwise = replicate i ''
但是您可以用 EMaybe
类型的值做什么?结果并不多,因为 EJust
包含一个 某种未知类型的值 。因此,无论您尝试将值用于什么,都将是类型错误,因为编译器无法确认它实际上是正确的类型。
bhrar :: EMaybe -> String
bhrar' (EJust i) = replicate i ''
=====> Error couldn't match expected type Int with a
如果变量未反映在 return 类型中,则认为它存在。这可以定义 data ExMaybe = ExNothing | forall a. ExJust a
但 ExJust
的参数完全没用。 ExJust True
和 ExJust ()
都有类型 ExMaybe
并且从类型系统的角度看是没有区别的。
这是原始 Maybe
和现有 ExMaybe
的 GADT 语法
{-# Language GADTs #-}
{-# Language LambdaCase #-}
{-# Language PolyKinds #-}
{-# Language ScopedTypeVariables #-}
{-# Language StandaloneKindSignatures #-}
{-# Language TypeApplications #-}
import Data.Kind (Type)
import Prelude hiding (Maybe(..))
type Maybe :: Type -> Type
data Maybe a where
Nothing :: Maybe a
Just :: a -> Maybe a
type ExMaybe :: Type
data ExMaybe where
ExNothing :: ExMaybe
ExJust :: a -> ExMaybe
你的问题就像问为什么函数 f x = ..
需要指定它的参数,可以选择使类型参数不可见,但这很奇怪,但即使不可见,参数仍然存在.
-- >> :t JUST
-- JUST :: a -> MAYBE
-- >> :t JUST 'a'
-- JUST 'a' :: MAYBE
type MAYBE :: forall (a :: Type). Type
data MAYBE where
NOTHING :: MAYBE @a
JUST :: a -> MAYBE @a
mAYBE :: b -> (a -> b) -> MAYBE @a -> b
mAYBE nOTHING jUST = \case
NOTHING -> nOTHING
JUST a -> jUST a
具有明确的类型参数使其更具表现力。没有它你会失去很多信息。比如map
的类型怎么写?还是一般的函子?
map :: (a -> b) -> [a] -> [b]
这个版本几乎没有说明正在发生的事情
map :: (a -> b) -> [] -> []
甚至更糟,head
:
head :: [] -> a
现在我们突然可以访问不安全强制和零类型安全。
unsafeCoerce :: a -> b
unsafeCoerce x = head [x]
但我们不仅失去了安全感,我们也失去了做一些事情的能力。例如,如果我们想 read
一些东西到一个列表或 Maybe
,我们就不能再指定我们想要什么样的列表。
read :: Read a => a
example :: [Int] -> String
main = do
xs <- getLine
putStringLine (example xs)
如果没有具有显式类型参数的列表,则无法编写此程序。 (或者更确切地说,read
将无法为不同的列表类型提供不同的实现,因为内容类型现在是不透明的)
然而,正如其他人所提到的,仍然可以通过使用 ExistentialQuantification
扩展来定义类似的类型。但在这些情况下,您在如何使用这些数据类型方面非常有限,因为您不知道它们包含什么。
为什么 Haskell 中的类型必须在类型构造函数参数中显式参数化?
例如:
data Maybe a = Nothing | Just a
这里a
必须指定类型。为什么不能只在构造函数中指定?
data Maybe = Nothing | Just a
他们为什么从设计的角度做出这样的选择?一个比另一个好吗?
我知道第一个比第二个强类型,但第二个甚至没有选项。
编辑:
示例函数
data Maybe = Just a | Nothing
div :: (Int -> Int -> Maybe)
div a b
| b == 0 = Nothing
| otherwise = Just (a / b)
使用 GADT 表示法可能会解决问题,因为标准表示法将类型级和值级语言混为一谈。
标准 Maybe
类型看起来像一个 GADT:
{-# LANGUAGE GADTs #-}
data Maybe a where
Nothing :: Maybe a
Just :: a -> Maybe a
“未参数化”版本也是可能的:
data EMaybe where
ENothing :: EMaybe
EJust :: a -> EMaybe
(正如 Joseph Sible 评论的那样,这被称为 存在类型 )。现在你可以定义
foo :: Maybe Int
foo = Just 37
foo' :: EMaybe
foo' = EJust 37
很好,为什么我们不总是使用 EMaybe
?
嗯,问题是当你想要使用这样的值时。使用 Maybe
很好,您可以完全控制包含的类型:
bhrar :: Maybe Int -> String
bhrar Nothing = "No number "
bhrar (Just i)
| i<0 = "Negative "
| otherwise = replicate i ''
但是您可以用 EMaybe
类型的值做什么?结果并不多,因为 EJust
包含一个 某种未知类型的值 。因此,无论您尝试将值用于什么,都将是类型错误,因为编译器无法确认它实际上是正确的类型。
bhrar :: EMaybe -> String
bhrar' (EJust i) = replicate i ''
=====> Error couldn't match expected type Int with a
如果变量未反映在 return 类型中,则认为它存在。这可以定义 data ExMaybe = ExNothing | forall a. ExJust a
但 ExJust
的参数完全没用。 ExJust True
和 ExJust ()
都有类型 ExMaybe
并且从类型系统的角度看是没有区别的。
这是原始 Maybe
和现有 ExMaybe
{-# Language GADTs #-}
{-# Language LambdaCase #-}
{-# Language PolyKinds #-}
{-# Language ScopedTypeVariables #-}
{-# Language StandaloneKindSignatures #-}
{-# Language TypeApplications #-}
import Data.Kind (Type)
import Prelude hiding (Maybe(..))
type Maybe :: Type -> Type
data Maybe a where
Nothing :: Maybe a
Just :: a -> Maybe a
type ExMaybe :: Type
data ExMaybe where
ExNothing :: ExMaybe
ExJust :: a -> ExMaybe
你的问题就像问为什么函数 f x = ..
需要指定它的参数,可以选择使类型参数不可见,但这很奇怪,但即使不可见,参数仍然存在.
-- >> :t JUST
-- JUST :: a -> MAYBE
-- >> :t JUST 'a'
-- JUST 'a' :: MAYBE
type MAYBE :: forall (a :: Type). Type
data MAYBE where
NOTHING :: MAYBE @a
JUST :: a -> MAYBE @a
mAYBE :: b -> (a -> b) -> MAYBE @a -> b
mAYBE nOTHING jUST = \case
NOTHING -> nOTHING
JUST a -> jUST a
具有明确的类型参数使其更具表现力。没有它你会失去很多信息。比如map
的类型怎么写?还是一般的函子?
map :: (a -> b) -> [a] -> [b]
这个版本几乎没有说明正在发生的事情
map :: (a -> b) -> [] -> []
甚至更糟,head
:
head :: [] -> a
现在我们突然可以访问不安全强制和零类型安全。
unsafeCoerce :: a -> b
unsafeCoerce x = head [x]
但我们不仅失去了安全感,我们也失去了做一些事情的能力。例如,如果我们想 read
一些东西到一个列表或 Maybe
,我们就不能再指定我们想要什么样的列表。
read :: Read a => a
example :: [Int] -> String
main = do
xs <- getLine
putStringLine (example xs)
如果没有具有显式类型参数的列表,则无法编写此程序。 (或者更确切地说,read
将无法为不同的列表类型提供不同的实现,因为内容类型现在是不透明的)
然而,正如其他人所提到的,仍然可以通过使用 ExistentialQuantification
扩展来定义类似的类型。但在这些情况下,您在如何使用这些数据类型方面非常有限,因为您不知道它们包含什么。