温和介绍Haskell:“......没有一种类型包含2和'b'。”我可以不做这样的类型吗?

时间:2014-03-29 08:51:11

标签: scala haskell

我目前正在学习Haskell,所以这里有一个初学者的问题:

下面的文字中单一类型是什么意思?

单一类型是一个特殊的Haskell术语吗?这是否意味着原子类型?

或者这是否意味着我永远不能在Haskell中创建一个列表,我可以将1'c'放在一起?

我在想一个类型是一组值。

所以我无法定义包含CharInt s的类型?

代数数据类型怎么样?

类似于:data IntOrChar = In Int | Ch Char? (我想这应该有用,但我很困惑作者对那句话的意思。)

顺便说一句,这是在Haskell中制作列表的唯一方法,我可以将IntsChars放在一起吗?还是有更棘手的方法?

Scala类比:在Scala中,可以将隐式转换写入表示IntChar s(如IntOrChar)的类型,然后就可以了将IntChar放入List[IntOrChar]中,是不是Haskell无法做到的?如果我想将它们放入Int列表中,我是否始终必须将每个CharIntOrChar明确包装到IntOrChar

来自Gentle Intro to Haskell

  

Haskell还包含多态类型---类型   通过某种方式对所有类型进行普遍量化。多态型   表达式实质上描述了类型的族。例如,   (forall a)[a]是由每种类型a组成的类型的家族,   a的列表类型。整数列表(例如[1,2,3]),列表   字符(['a','b','c']),甚至是整数列表等等   这个家庭的所有成员。 (但请注意,[2,'b']不是a   有效的例子,因为没有包含2和2的单一类型   'B')

3 个答案:

答案 0 :(得分:5)

简短回答。

在Haskell中没有隐式转换。也没有联合类型 - 只有不相交的联合(代数数据类型)。所以你只能写:

someList :: [IntOrChar]
someList = [In 1, Ch 'c']

更长,肯定不是温和的答案。

注意:这是一种很少使用的技术。如果您需要它,可能会使您的API过于复杂。

但存在各种类型。

{-# LANGUAGE ExistentialQuantification, RankNTypes #-}
class IntOrChar a where
   intOrChar :: a -> Either Int Char

instance IntOrChar Int where
   intOrChar = Left

instance IntOrChar Char where
   intOrChar = Right 

data List = Nil
          | forall a. (IntOrChar a) => Cons a List

someList :: List
someList = (1 :: Int) `Cons` ('c' `Cons` Nil)

在这里,我创建了一个只有函数IntOrChar的类型intOrChar。这样,您就可以将forall a. (IntOrChar a) => a类型的任何内容转换为Either Int Char

还有一种在其第二个构造函数中使用存在类型的特殊列表。 这里类型变量a在构造函数范围内绑定(带forall)。所以每一次 使用Cons,您可以传递forall a. (IntOrChar a) => a类型的任何内容作为第一个参数。因此,在破坏(即模式匹配)期间,第一个参数将会出现 仍然是forall a. (IntOrChar a) => a。您唯一能做的就是将其传递或致电intOrChar并将其转换为Either Int Char

withHead :: (forall a. (IntOrChar a) => a -> b) -> List -> Maybe b
withHead f Nil = Nothing
withHead f (Cons x _) = Just (f x)

intOrCharToString :: (IntOrChar a) => a -> String
intOrCharToString x = 
   case intOrChar of
    Left i -> show i
    Right c -> show c

someListHeadString :: Maybe String
someListHeadString = withHead intOrCharToString someList

再次注意,你不能写

{- Wont compile
safeHead :: IntOrChar a => List -> Maybe a
safeHead Nil = Nothing
safeHead (Cons x _) = Just x
-}

-- This will
safeHead2 :: List -> Maybe (Either Int Char)
safeHead2 Nil = Nothing
safeHead2 (Cons x _) = Just (intOrChar x)

safeHead无效,因为您希望IntOrChar a => Maybe a的{​​{1}}类型绑定在a范围,而safeHead的类型为{{1} } Just x绑定在IntOrChar a1 => Maybe a1范围内。

答案 1 :(得分:3)

在Scala中,有些类型包括IntChar,例如AnyValAny,它们都是Char和{{1}的超类型}}。在Haskell中没有这样的层次结构,所有基本类型都是不相交的。

您可以创建自己的联合类型来描述'IntInt的概念(或者您可以使用内置的Char类型),但有Haskell中没有隐式转换可以将Either透明地转换为Int

您可以使用existential types模仿“任意”的概念:

IntOrChar

请注意,此模式为discouraged

答案 2 :(得分:2)

我认为这里的区别在于你的代数数据类型IntOrChar是一个带标记的联合" - 也就是说,当您的值为IntOrChar时,您会知道它是Int还是Char

通过比较考虑这个匿名联合定义(在C中):

typedef union { char c; int i; } intorchar;

如果您获得intorchar类型的值,则您不知道(先验)哪个选择器有效。这就是为什么大多数情况下union构造函数与struct一起使用以形成标记联合构造的原因:

typedef struct {
          int tag;
          union { char c; int i; } intorchar_u
} IntOrChar;

此处tag字段编码联合的哪个选择器有效。

union构造函数的另一个主要用途是覆盖两个结构,以获得子结构之间的有效映射。例如,这种联合是有效访问int的各个字节的一种方法(假设8位字符和32位整数):

union { char b[4]; int i }

现在,来说明"标记的工会与#34;之间的主要区别。和#34;匿名工会"考虑如何在这些类型上定义函数。

要在IntOrChar值(标记的联合)上定义一个函数,我声称你需要提供两个函数 - 一个取Int的函数(如果值为{{1} }}和一个带Int的值(如果值为Char)。由于该值是用其类型标记的,因此它知道它应该使用哪两个函数。

如果我们让Char表示从F(a,b)类型到a类型的函数集,我们就有:

b

其中F(IntOrChar,b) = F(Int,b) \times F(Char,b) 表示叉积。

对于匿名联合\times,由于值不对其类型的任何内容进行编码,因此可以应用的唯一函数是对intorchar和{{1}都有效的函数。值,即:

Int

其中Char表示交集。

在Haskell中,只有一个函数(据我所知)可以应用于整数和字符,即身份函数。因此,在Haskell中使用F(intorchar,b) = F(Int,b) \cap F(Char,b) 之类的列表并不多。在其他语言中,这个交集可能不是空的,然后像这样的结构更有意义。

总而言之,如果您创建了tagged-union,则可以在同一列表中包含整数和字符,在这种情况下,您必须标记每个值,这些值将使您的列表看起来像:

\cap

如果你没有标记你的值,那么你正在创建一个类似于匿名联合的东西,但是由于没有任何(有用的)函数可以应用于整数和字符。对于那种联盟,你真的不能做任何事情。