我昨天正在尝试使用类型系列,并遇到以下代码遇到障碍:
{-# LANGUAGE TypeFamilies #-}
class C a where
type A a
myLength :: A a -> Int
instance C String where
type A String = [String]
myLength = length
instance C Int where
type A Int = [Int]
myLength = length
main = let a1 = [1,2,3]
a2 = ["hello","world"]
in print (myLength a1)
>> print (myLength a2)
这里我有一个与C类相关的类型和一个计算相关类型长度的函数。但是上面的代码给了我这个错误:
/tmp/type-families.hs:18:30:
Couldn't match type `A a1' with `[a]'
In the first argument of `myLength', namely `a1'
In the first argument of `print', namely `(myLength a1)'
In the first argument of `(>>)', namely `print (myLength a1)'
/tmp/type-families.hs:19:30:
Couldn't match type `A a2' with `[[Char]]'
In the first argument of `myLength', namely `a2'
In the first argument of `print', namely `(myLength a2)'
In the second argument of `(>>)', namely `print (myLength a2)'
Failed, modules loaded: none.
但是,如果我将“type”更改为“data”,则代码会编译并运行:
{-# LANGUAGE TypeFamilies #-}
class C a where
data A a
myLength :: A a -> Int
instance C String where
data A String = S [String]
myLength (S a) = length a
instance C Int where
data A Int = I [Int]
myLength (I a) = length a
main = let a1 = I [1,2,3]
a2 = S ["hello","world"]
in
print (myLength a1) >>
print (myLength a2)
为什么“长度”在第一种情况下不能按预期工作?行“type A String ...”和“type A Int ...”指定类型“A a”是一个列表,因此myLength应分别具有以下类型:“myLength :: [String] - > Int “或”myLength :: [Int] - > Int“。
答案 0 :(得分:14)
嗯。我们暂时忘记类型。
假设您有两个功能:
import qualified Data.IntMap as IM
a :: Int -> Float
a x = fromInteger (x * x) / 2
l :: Int -> String
l x = fromMaybe "" $ IM.lookup x im
where im = IM.fromList -- etc...
假设存在一些您关心的值n :: Int
。仅给出a n
的值,您如何找到l n
的值?当然,你没有。
这有什么关系?好吧,myLength
的类型是A a -> Int
,其中A a
是将“类型函数”A
应用于某种类型a
的结果。但是,myLength
是类型类的一部分,类参数a
用于选择要使用的myLength
实现。因此,给定某些特定类型B
的值,将myLength
应用于它会提供B -> Int
类型,其中B ~ A a
并且您需要知道a
以查找myLength
的实施情况。仅给出A a
的值,您如何找到a
的值?当然,你没有。
您可以合理地反对在此处的代码中,函数A
是可逆的,与我之前示例中的a
函数不同。这是事实,但由于 open world 假设涉及类型类,编译器无法对此做任何事情;理论上,你的模块可以由另一个定义自己实例的模块导入,例如:
instance C Bool where
type A Bool = [String]
傻?是。有效代码?也是的。
在许多情况下,在Haskell中使用构造函数用于创建平凡的内射函数:构造函数引入了一个新实体,该实体仅由它给出的参数唯一定义,使得恢复变得简单原始价值观。这正是您的代码的两个版本之间的差异;数据系列通过为每个参数定义一个新的,不同的类型,使类型函数可逆。