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 aExJust 的参数完全没用。 ExJust TrueExJust () 都有类型 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 扩展来定义类似的类型。但在这些情况下,您在如何使用这些数据类型方面非常有限,因为您不知道它们包含什么。