类型变量
类型变量是类型签名中的非大写字母名称。与他们的大写对手不同,例如 Int
和 String
,它们不代表任何类型,而是任何类型。它们用于编写可以在任何类型或类型上运行的泛型函数,对于通过 List
或 Dict
等容器编写操作特别有用。例如,List.reverse
函数具有以下签名:
reverse : List a -> List a
这意味着它可以在任何类型值的列表上工作,所以 List Int
,List (List String)
,这些和其他任何一个都可以完全相同。因此,a
是一个可以代表任何类型的类型变量。
reverse
函数可以在其类型签名中使用任何非大写的变量名,除了少数特殊类型的变量名,例如 number
(有关更多信息,请参阅相应的示例):
reverse : List lol -> List lol
reverse : List wakaFlaka -> List wakaFlaka
只有当单个签名中有不同的类型变量时,类型变量的名称才有意义,例如列表中的 map
函数:
map : (a -> b) -> List a -> List b
map
从任何类型的 a
到任何类型的 b
,以及包含某些类型 a
的元素的列表,并返回一些类型 b
的元素列表,它通过将给定的函数应用于列表的每个元素来获取。
让我们使签名具体化,以便更好地看到这一点:
plusOne : Int -> Int
plusOne x =
x + 1
> List.map plusOne
<function> : List Int -> List Int
我们可以看到,在这种情况下,a = Int
和 b = Int
。但是,如果 map
有一个像 map : (a -> a) -> List a -> List a
这样的类型签名,那么它只适用于在单一类型上运行的函数,并且你永远无法通过使用 map
函数来改变列表的类型。但由于 map
的类型签名有多个不同的类型变量 a
和 b
,我们可以使用 map
来改变列表的类型:
isOdd : Int -> Bool
isOdd x =
x % 2 /= 0
> List.map isOdd
<function> : List Int -> List Bool
在这种情况下,a = Int
和 b = Bool
。因此,为了能够使用可以获取和返回不同类型的函数,必须使用不同的类型变量。