【问题标题】:Gentle Intro to Haskell: " .... there is no single type that contains both 2 and 'b'." Can I not make such a type ?Haskell 的简单介绍:“ .... 不存在同时包含 2 和 'b' 的单一类型。”我不能做这种类型吗?
【发布时间】:2014-03-29 08:51:11
【问题描述】:

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

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

单一类型是一个特殊的 Haskell 术语吗?这里的意思是原子类型吗?

或者这是否意味着我永远无法在 Haskell 中列出我可以同时放入 1'c' 的列表?

我认为类型是一组值。

所以我不能定义包含Chars 和Ints 的类型?

代数数据类型呢?

类似:data IntOrChar = In Int | Ch Char ? (我想这应该可行,但我对作者这句话的意思感到困惑。)

顺便说一句,这是在 Haskell 中创建一个列表的唯一方法,我可以在其中同时放入 IntsChars?还是有更棘手的方法?

一个 Scala 类比:在 Scala 中,可以将隐式转换写入同时代表 Ints 和 Chars 的类型(如 IntOrChar),然后可以无缝地放置 Int s 和 Chars 到 List[IntOrChar],这对 Haskell 来说是不可能的吗?如果我想将每个IntChar 放入IntOrChar 中,是否总是必须明确地将它们放入IntOrChar 的列表中?

来自Gentle Intro to Haskell

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

【问题讨论】:

  • 您的[IntOrChar] 不是[2, 'b'],而是[In 2, Ch 'b']
  • 是的,所以我不能创建联合类型?
  • 不,single type 不是 Haskell 术语。 “Single”是一个普通的英文单词,意思是“只有一个;不是几个中的一个”。 IntOrChar联合类型。

标签: scala haskell


【解决方案1】:

简答。

在 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 类型与a 绑定在safeHead 范围和Just x 将具有IntOrChar a1 => Maybe a1 类型和a1 绑定在Cons 范围.

【讨论】:

  • 有趣!所以据我所知,这有点类似于 Scala 的隐式转换?
  • @jhegedus 好像是这样。对于隐式转换,Scala 会查找一个可以进行正确转换的函数。在这里(当创建Cons 时)Haskell 查找IntOrChar 的实例作为第一个参数的类型。这个实例(在 GHC 实现中)作为字典(包含详细信息的论文:Implementing Type Classes)传递,然后在模式匹配期间提取。不过,查找规则可能有所不同。
  • 另外,我们可以滥用符号并写成instance Num Char where fromInteger i = head $ show i,这样我们就可以写成length [1,'c'] = 2,但这是使用重载的数字文字,实际上表示`length "1c"。它实际上在带有字符的列表中没有整数。
【解决方案2】:

在 Scala 中,有一些类型同时包含IntChar,例如AnyValAny,它们都是CharInt 的超类型。在 Haskell 中没有这样的层次结构,所有的基本类型都是不相交的。

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

您可以使用existential types 模拟“任何”的概念:

data AnyBox = forall a. (Show a, Hashable a) => AB a

heteroList :: [AnyBox]
heteroList = [AB (1::Int), AB 'b']

showWithHash :: AnyBox -> String
showWithHash (AB v) = show v ++ " - " ++ (show . hash) v

let strs = map showWithHash heteroList

请注意,此模式是 discouraged

【讨论】:

  • 我不确定您所说的“所有基本类型都是不相交的”是什么意思。 undefined 在每个标准的 Haskell 类型中。
  • @dfeuer 断言undefined :: Int 不是undefined :: Char 是合理的。 Haskell 将这些视为非常不同的,因为undefined 是一个多态函数,而不是一个包含多种类型的常量。
【解决方案3】:

我认为这里所做的区别在于您的代数数据类型 IntOrChar 是一个“标记联合” - 也就是说,当您有一个 IntOrChar 类型的值时,您将知道它是否是一个 @ 987654323@ 或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 }

现在,为了说明“标记联合”和“匿名联合”之间的主要区别,请考虑如何在这些类型上定义函数。

要在 IntOrChar 值(标记的联合)上定义函数,我声称您需要提供两个函数 - 一个接受 Int(如果值为 Int),另一个接受接受Char(如果值为Char)。由于值是用它的类型标记的,它知道它应该使用两个函数中的哪一个。

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

F(IntOrChar,b) = F(Int,b) \times F(Char,b)

其中\times 表示叉积。

对于匿名联合 intorchar,由于一个值​​不会对其类型进行任何编码,因此唯一可以应用的函数是对 IntChar 值都有效的函数,即:

F(intorchar,b) = F(Int,b) \cap F(Char,b)

\cap 表示交叉点。

在 Haskell 中,只有一个函数(据我所知)可以应用于整数和字符,即恒等函数。因此,对于 Haskell 中的 [2, 'b'] 之类的列表,您无能为力。在其他语言中,这个交集可能不是空的,然后这样的结构更有意义。

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

[ I 2, C 'b', ... ]

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

【讨论】:

  • 恒等函数不是唯一的多态函数,甚至不是最多态的函数。 undefined 可以用于 any 类型,因此特别是它可以用于a -> b 类型。同样,\x -> undefined 可以用于类型a -> b。常量函数和序列函数的多态性稍差一些,但也不多:例如,\x -> (3::Int) 的类型为 a -> Int\x -> seq x (3::Int) 也是如此。
猜你喜欢
  • 1970-01-01
  • 2014-11-01
  • 2021-12-07
  • 1970-01-01
  • 2021-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多