我开始学习Haskell。在学习教程时,我发现了以下示例,它允许在算术表达式中使用函数:
module FunNat where
instance Num a => Num (t -> a) where
(+) = fun2 (+)
(*) = fun2 (*)
(-) = fun2 (-)
abs = fun1 abs
signum = fun1 signum
fromInteger = const . fromInteger
fun1 :: (a -> b) -> ((t -> a) -> (t -> b))
fun1 = (.)
fun2 :: (a -> b -> c) -> ((t -> a) -> (t -> b) -> (t -> c))
fun2 op a b = \t -> a t `op` b t
该示例有效。但我无法理解(+)
函数如何转换为两个参数的函数。据我所知,每个(+)
都替换为fun2 (+)
。而fun2相当于一个参数\t -> a t 'op' b t
的函数,但我们应该有两个参数的函数(类似(\t1 t2 -> (\x -> x ) t1 + (\x -> x) t2)
)。我认为这里应该应用Haskell类型的一些基本概念,但我不知道它们是什么。
编辑1
我知道fun2
是三个参数的函数。我无法理解内部表达转换。我的推理方式如下:(+) 3 4
= (+) (\x->3) (\x->4)
= fun2 (+) (\x->3) (\x->4)
= \t -> (\x->3) t + (\x->4) t
这是什么?或者我在推理中哪里错了?可能有必要以另一种方式思考吗?
编辑2
我认为我已经对这个问题有了一些了解(谢谢大家!)。所以:
当我们写(+) 3 4
时 - 在这种情况下,使用来自Num
的简单操作,FunNat
没有特殊功能。要使用(+)
中的FunNat
,必须撰写fun2 (+) 3 4
或fun2 (+) (\x -> 3) (\x -> 4)
。但是这些表达式期望评估任何第三个参数;
要展示FunNat
的具体功能,我们可以使用以下示例((*)-(+))
(摘自教程)或其他形式 - (-) (*) (+)
。两个表达式都有两个参数。在这种情况下,我们有:((*)-(+))
= fun2 (-) (*) (+)
= \t -> (*) t - (+) t
= \t -> (\t1 t2 -> t1 * t2) t - (\t1 t2 -> t1 + t2) t
= (\t t2 -> t * t2) - (\t t2 -> t + t2)
= \t t2 -> (t * t2) - (t + t2)
。最后的表达式只需要Num
。我希望所有这些都是正确的
正如教程中所解释的那样,但是我无法理解,使用(*)
和(+)
作为期望{{1}的fun2
参数的可能性基于以下内容(取自教程):(t->a)
= t1 -> t2 -> a
其中t1->a'
。这里使用了curring。
因此,所有必要的事实都是表面上的,但我没有考虑类型推理的非平凡机制。
答案 0 :(得分:1)
(+)
的类型为Num a => a -> a -> a
。由于您要为Num
类型创建Num a => t -> a
的实例,请考虑显式签名
(+) :: Num a => (t -> a) -> (t -> a) -> (t -> a)
(1)
或等效(通过currying)
(+) :: Num a => (t -> a) -> (t -> a) -> t -> a
(2)
这说的是:"如果你给我2个从Num a => a
产生t
的策略,我可以创建一个新策略来生成Num a => a
使用t
""
Num.(+)
个实例a
我认为你感到困惑的部分是这个特定的(+)
可以看作是2个参数的函数,它返回一个函数(1),或者一个函数的3个参数,它返回一个值(2) 。由于Haskell中的函数是咖喱的,所以这些都是一样的。
答案 1 :(得分:0)
在类型签名中,括号与右侧相关联,因此:
f :: a -> (b -> c -> d)
与:
相同f :: a -> b -> c -> d
所以下面的表示是多余的:
fun2 :: (a -> b -> c) -> ((t -> a) -> (t -> b) -> (t -> c))
^ ^
并删除它们:
fun2 :: (a -> b -> c) -> (t -> a) -> (t -> b) -> (t -> c)
arg1 arg2 arg3