简答:
不行,不能做成Functor。
长答案:
不能将其制成函数的第一个原因是函子必须具有* -> * 类型。种类就像类型的类型,您甚至可以使用:kind <type> 在 GHCi 中检查它们。例如:
> :kind Int
Int :: *
> :kind []
[] :: * -> *
> :kind Num
Num :: * -> Constraint
> :kind Maybe
Maybe :: * -> *
> :kind Either
Either :: * -> * -> *
> :kind Functor
Functor :: (* -> *) -> Constraint
* 基本上表示完全应用的类型,例如Int、Char、[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 b。 fmap 函数只转换容器内的值,不会从容器中提取值。
可以编写一个使用-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] -> b和f :: [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 的函数,那么您的程序将是安全的。它可能不像您希望的那样灵活,但在安全性方面会坚如磐石。