Haskell、多元函数与类型推断
Haskell, polyvariadic function and type inference
在寻找多元函数示例时,我找到了这个资源:
Whosebug: How to create a polyvariadic haskell function?,并且有一个这样的答案片段:
class SumRes r where
sumOf :: Integer -> r
instance SumRes Integer where
sumOf = id
instance (Integral a, SumRes r) => SumRes (a -> r) where
sumOf x = sumOf . (x +) . toInteger
然后我们可以使用:
*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0 :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59
出于好奇,我试着稍微改了一下,因为我乍一看觉得它很棘手,所以我进入了这个:
class SumRes r where
sumOf :: Int -> r
instance SumRes Int where
sumOf = id
instance (SumRes r) => SumRes (Int -> r) where
sumOf x = sumOf . (x +)
我刚刚将 Integer
更改为 Int
,并将 instance (Integral a, SumRes r) => SumRes (a -> r) where
的多态性降低为 instance (SumRes r) => SumRes (Int -> r) where
为了编译它,我必须设置 XFlexibleInstances
标志。当我尝试测试 sumOf
函数时遇到问题:
*Main> sumOf 1 :: Int
1
*Main> sumOf 1 1 :: Int
<interactive>:9:9
No instance for (Num a0) arising from the literal `1'
The type variable `a0' is ambiguous...
然后我尝试了:
*Main> sumOf (1 :: Int) (1 :: Int) :: Int
2
考虑到我们在 SumRes
类型类中使用 Int
,为什么不能 Haskell 推断我们在这种情况下需要 Int
?
数字文字本身是多态的,而不是 Int
类型
*Main> :t 1
1 :: Num a => a
看看当我们得到类型签名时会发生什么:
*Main> :t sumOf 1 2 3
sumOf 1 2 3 :: (Num a, Num a1, SumRes (a -> a1 -> t)) => t
请注意,该类型根本没有提及 Int
。类型检查器无法弄清楚如何实际计算总和,因为定义的 Int
个实例中的 none 个足够通用,可以在此处应用。
如果您将类型固定为 Int
,那么您最终会得到
*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int)
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: SumRes t => t
*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
注意 SumRes t => t
与 Int
兼容,因为我们有一个 SumRes Int
实例,但是如果我们没有明确指定 Int
,那么我们就没有实例general 够用到这里,因为没有general SumRes t
instance.
实例
instance (...) => SumRes (Int -> r) where
大致意思是 "here's how to define SumRes
on Int -> r
for any r
(under certain conditions)"。与
比较
instance (...) => SumRes (a -> r) where
表示"here's how to define SumRes
on a -> r
for any a,r
(under certain conditions)".
主要区别在于第二个声明这是 相关实例,无论 a,r
可能是哪种类型。除了一些(非常棘手且有潜在危险的)Haskell 扩展,以后不能在涉及函数时添加更多实例。相反,第一个为新实例留出空间,例如
instance (...) => SumRes (Double -> r) where ...
instance (...) => SumRes (Integer -> r) where ...
instance (...) => SumRes (Float -> r) where ...
instance (...) => SumRes (String -> r) where ... -- nonsense, but allowed
这与诸如 5
之类的数字文字是多态的事实相匹配:它们的类型必须从上下文中推断出来。因为稍后编译器可能会发现例如Double -> r
实例并选择 Double
作为文字类型,编译器不会提交 Int -> r
实例,并在类型错误中报告歧义。
请注意,使用一些(安全的)Haskell 扩展(例如 TypeFamilies
),可以 "promise" 编译器认为您的 Int -> r
是只有一个将在整个程序中声明。这是这样做的:
instance (..., a ~ Int) => SumRes (a -> r) where ...
这承诺处理所有 "functional type" 情况,但要求 a
实际上与 Int
.
是同一类型
在寻找多元函数示例时,我找到了这个资源: Whosebug: How to create a polyvariadic haskell function?,并且有一个这样的答案片段:
class SumRes r where
sumOf :: Integer -> r
instance SumRes Integer where
sumOf = id
instance (Integral a, SumRes r) => SumRes (a -> r) where
sumOf x = sumOf . (x +) . toInteger
然后我们可以使用:
*Main> sumOf 1 :: Integer
1
*Main> sumOf 1 4 7 10 :: Integer
22
*Main> sumOf 1 4 7 10 0 0 :: Integer
22
*Main> sumOf 1 4 7 10 2 5 8 22 :: Integer
59
出于好奇,我试着稍微改了一下,因为我乍一看觉得它很棘手,所以我进入了这个:
class SumRes r where
sumOf :: Int -> r
instance SumRes Int where
sumOf = id
instance (SumRes r) => SumRes (Int -> r) where
sumOf x = sumOf . (x +)
我刚刚将 Integer
更改为 Int
,并将 instance (Integral a, SumRes r) => SumRes (a -> r) where
的多态性降低为 instance (SumRes r) => SumRes (Int -> r) where
为了编译它,我必须设置 XFlexibleInstances
标志。当我尝试测试 sumOf
函数时遇到问题:
*Main> sumOf 1 :: Int
1
*Main> sumOf 1 1 :: Int
<interactive>:9:9
No instance for (Num a0) arising from the literal `1'
The type variable `a0' is ambiguous...
然后我尝试了:
*Main> sumOf (1 :: Int) (1 :: Int) :: Int
2
考虑到我们在 SumRes
类型类中使用 Int
,为什么不能 Haskell 推断我们在这种情况下需要 Int
?
数字文字本身是多态的,而不是 Int
*Main> :t 1
1 :: Num a => a
看看当我们得到类型签名时会发生什么:
*Main> :t sumOf 1 2 3
sumOf 1 2 3 :: (Num a, Num a1, SumRes (a -> a1 -> t)) => t
请注意,该类型根本没有提及 Int
。类型检查器无法弄清楚如何实际计算总和,因为定义的 Int
个实例中的 none 个足够通用,可以在此处应用。
如果您将类型固定为 Int
,那么您最终会得到
*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int)
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: SumRes t => t
*Main> :t sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
sumOf (1 :: Int) (2 :: Int) (3 :: Int) :: Int
注意 SumRes t => t
与 Int
兼容,因为我们有一个 SumRes Int
实例,但是如果我们没有明确指定 Int
,那么我们就没有实例general 够用到这里,因为没有general SumRes t
instance.
实例
instance (...) => SumRes (Int -> r) where
大致意思是 "here's how to define SumRes
on Int -> r
for any r
(under certain conditions)"。与
instance (...) => SumRes (a -> r) where
表示"here's how to define SumRes
on a -> r
for any a,r
(under certain conditions)".
主要区别在于第二个声明这是 相关实例,无论 a,r
可能是哪种类型。除了一些(非常棘手且有潜在危险的)Haskell 扩展,以后不能在涉及函数时添加更多实例。相反,第一个为新实例留出空间,例如
instance (...) => SumRes (Double -> r) where ...
instance (...) => SumRes (Integer -> r) where ...
instance (...) => SumRes (Float -> r) where ...
instance (...) => SumRes (String -> r) where ... -- nonsense, but allowed
这与诸如 5
之类的数字文字是多态的事实相匹配:它们的类型必须从上下文中推断出来。因为稍后编译器可能会发现例如Double -> r
实例并选择 Double
作为文字类型,编译器不会提交 Int -> r
实例,并在类型错误中报告歧义。
请注意,使用一些(安全的)Haskell 扩展(例如 TypeFamilies
),可以 "promise" 编译器认为您的 Int -> r
是只有一个将在整个程序中声明。这是这样做的:
instance (..., a ~ Int) => SumRes (a -> r) where ...
这承诺处理所有 "functional type" 情况,但要求 a
实际上与 Int
.