【问题标题】:Functor instance for a simple algebraic data type简单代数数据类型的函子实例
【发布时间】:2015-01-07 01:20:24
【问题描述】:

我想使用异构列表。为此,我定义了一个简单的代数数据类型:

data T = LInt [Int]
       | LChar [Char]
       deriving (Eq, Ord, Show)

所以我现在可以拥有这样的东西:

x = [LInt [1, 2, 3], LChar "test"]

我的问题:这种类型可以成为 Functor 的实例吗?这会很有用;例如在 x 中选择列表的元素,如

fmap (!!2) LChar "test"  => 's'

在我看来这是不可能的。除了提出问题的动机之外,我相信这个答案会帮助我更好地理解代数数据类型。

【问题讨论】:

  • 为什么不只保留一个字符串列表和一个单独的 Int 列表列表呢?那么你可以在它们上同时使用 map 和 map.map 而无需先编写任何新代码?
  • 如果您使用[Either [[Int]] [String]],然后fmap 作用于右值,而不作用于左值:map (fmap (map length)) [Left [[2,2],[3]],Right ["Hello","Mum"],Left []] 将是[Left [[2,2],[3]],Right [5,3],Left []] :: Either [[Int]] [Int]

标签: haskell functor algebraic-data-types


【解决方案1】:

简答:

不行,不能做成Functor

长答案:

不能将其制成函数的第一个原因是函子必须具有* -> * 类型。种类就像类型的类型,您甚至可以使用:kind <type> 在 GHCi 中检查它们。例如:

> :kind Int
Int :: *
> :kind []
[] :: * -> *
> :kind Num
Num :: * -> Constraint
> :kind Maybe
Maybe :: * -> *
> :kind Either
Either :: * -> * -> *
> :kind Functor
Functor :: (* -> *) -> Constraint

* 基本上表示完全应用的类型,例如IntChar[String] 等,而类似* -> * 表示该类型采用单一类型* 返回一种新型的*。约束也有种类,即它们在完全应用时返回种类Constraint

您的类型有种类*,它与Functor 所需的参数* -> * 不匹配。为了使它成为Functor,它需要接受一个类型变量。在这里添加类型变量没有多大意义,但你可以有

data T a = LInt [a] | LChar [a]

但这不是很有用,我们现在不能强制LInt 只包含Ints 和LChar 只包含Chars。更糟糕的是,看看我们拥有的fmap 的类型

class Functor f where
    fmap :: (a -> b) -> (f a -> f b)

但是你想要做的是像

myfmap :: (a -> b) -> (f a -> b)

注意返回类型是b,而不是f bfmap 函数只转换容器内的值,不会从容器中提取值。

可以编写一个使用-XGADTs 约束的参数化类型,但是,您可以编写

data T a where
    LInt :: [Int] -> T Int
    LChar :: [Char] -> T Char

这将保证类型是健全的,但仍然不可能将其变为 Functor 的实例(即满足函子定律),并且它会阻止您制作异构列表(@987654351 @)。

所以看起来Functor 选项是正确的。你可能会觉得写一个类似

的函数很诱人
tmap :: ([a] -> b) -> T -> b
tmap f (LInt x) = f x
tmap f (LChar x) = f x

但这也行不通。类型系统看到你想说f :: [Int] -> bf :: [Char] -> b,不能统一。您可以通过启用-XRankNTypes 来做到这一点:

tmap :: (forall a. [a] -> b) -> T -> b
tmap f (LInt x) = f x
tmap f (LChar x) = f x

这确实允许你做类似的事情

> tmap length (LInt [1, 2, 3])
3
> tmap length (LChar "test")
4

但它不会让你这样做

> tmap (!! 2) (LChar "test")
Couldn't match type 'b' with 'a'
  because type variable 'a' would escape its scope
This (rigid, skolem) type variable is bound by
  a type expected by the context: [a] -> b
Expected type: [a] -> b
  Actual type: [a] -> a
...

这意味着a 类型不能出现在输出类型b 中的任何位置,因为传入的f 必须对所有 a 起作用,它不适用于任何a

总之,不必再深入到类型系统的疯狂中,你的类型就不能做你想做的事。您将不得不编写专门的函数来单独处理每种情况,这几乎是 ADT 的重点。编译器可以确保您确实处理每种情况,只要您远离返回undefined 或调用error 的函数,那么您的程序将是安全的。它可能不像您希望的那样灵活,但在安全性方面会坚如磐石。

【讨论】:

  • tmap 只是caseT :: ([Int] -> b) -> ([Char] -> b) -> T -> b 的一个特例。即tmap f = caseT f f。然后你可以做caseT (Left . (!!2)) (Right . (!!2))之类的事情。
【解决方案2】:

不,因为它没有正确的kind。 Functors 应该有 kind * -> *T 有 kind *。函子必须是具有单个类型参数的类型构造函数,例如IOMaybeEither a。这体现在fmap的类型上:

fmap :: Functor f => (a -> b) -> f a -> f b

所以f 需要一个类型参数a。你的类型T没有这样的参数。

【讨论】:

  • Either a 实际上不是类型构造函数。你可以称它为类型,或者类型函数之类的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-14
相关资源
最近更新 更多