给出了Haskell函数:
head . filter fst
现在的问题是如何手动“手动”找到类型。如果我让Haskell告诉我我得到的类型:
head . filter fst :: [(Bool, b)] -> (Bool, b)
但是我想了解它是如何工作的,只使用已定义如下的已使用函数的签名:
head :: [a] -> a
(.) :: (b -> c) -> (a -> b) -> a -> c
filter :: (a -> Bool) -> [a] -> [a]
fst :: (a, b) -> a
编辑:这么多很好的解释......选择最好的一个并不容易!
答案 0 :(得分:15)
使用通常称为unification的过程来推断类型。 Haskell属于Hindley-Milner家族,这是统一的 它用于确定表达式类型的算法。
如果统一失败,则表达式是类型错误。
表达式
head . filter fst
通过。让我们手动进行统一,看看我们为什么会这样做 我们得到了什么。
让我们从filter fst
开始:
filter :: (a -> Bool) -> [a] -> [a]
fst :: (a' , b') -> a' -- using a', b' to prevent confusion
filter
获取(a -> Bool)
,然后[a]
获得另一个[a]
。在表达中
filter fst
,我们传递filter
参数fst
,其类型为(a', b') -> a'
。
为此,类型fst
必须使用filter
的第一个参数类型统一:
(a -> Bool) UNIFY? ((a', b') -> a')
算法统一两个类型表达式并尝试绑定尽可能多的变量(例如a
或{{ 1}})到实际类型(例如a'
)。
只有这样Bool
才能生成有效的类型化表达式:
filter fst
filter fst :: [a] -> [a]
显然是a'
。因此,变量 Bool
类型解析为a'
。
Bool
可以统一到(a', b')
。因此,如果a
为a
且(a', b')
为a'
,
然后Bool
只是a
。
如果我们向(Bool, b')
传递了一个不兼容的参数,例如filter
(a 42
),
Num
与Num a => a
的统一将作为两个表达式失败
永远不能统一到正确的类型表达。
回到
a -> Bool
这与我们正在谈论的filter fst :: [a] -> [a]
相同,所以我们替换它的位置
以前统一的结果:
a
下一位,
filter fst :: [(Bool, b')] -> [(Bool, b')]
可以写成
head . (filter fst)
所以请(.) head (filter fst)
(.)
因此,要使统一成功,
(.) :: (b -> c) -> (a -> b) -> a -> c
必须统一head :: [a] -> a
(b -> c)
必须统一filter fst :: [(Bool, b')] -> [(Bool, b')]
从(2)我们在表达式中得到(a -> b)
IS a
b
)`
所以类型变量(.) :: (b -> c) -> (a -> b) -> a -> c
和a
的值在
表达式c
很容易辨别
(1)为我们提供(.) head (filter fst) :: a -> c
和b
之间的关系:c
是b
的列表。
我们知道c
为a
,[(Bool, b')]
只能统一到c
所以(Bool, b')
成功进行了类型检查:
head . filter fst
<强>更新强>
有趣的是,您可以从各个方面统一启动流程。
我首先选择了head . filter fst :: [(Bool, b')] -> (Bool, b')
,然后选择了filter fst
和(.)
,但是作为其他示例
显示,统一可以通过几种方式进行,与数学方式不同
证据或定理推导可以多种方式完成!
答案 1 :(得分:9)
filter :: (a -> Bool) -> [a] -> [a]
使用函数(a -> Bool)
,相同类型的列表a
,并返回该类型a
的列表。
在您的定义中,您使用filter fst
和fst :: (a,b) -> a
,因此类型
filter (fst :: (Bool,b) -> Bool) :: [(Bool,b)] -> [(Bool,b)]
推断是。
接下来,您将结果[(Bool,b)]
与head :: [a] -> a
一起撰写。
(.) :: (b -> c) -> (a -> b) -> a -> c
是两个函数func2 :: (b -> c)
和func1 :: (a -> b)
的组合。在你的情况下,你有
func2 = head :: [ a ] -> a
和
func1 = filter fst :: [(Bool,b)] -> [(Bool,b)]
所以head
这里以[(Bool,b)]
为参数,每个定义返回(Bool,b)
。最后你有:
head . filter fst :: [(Bool,b)] -> (Bool,b)
答案 2 :(得分:8)
让我们从(.)
开始吧。它的类型签名是
(.) :: (b -> c) -> (a -> b) -> a -> c
说
“给定从b
到c
的函数,以及从a
到b
的函数,
还有a
,我可以给你一个b
“。我们希望将其用于head
和
filter fst
,所以`:
(.) :: (b -> c) -> (a -> b) -> a -> c
^^^^^^^^ ^^^^^^^^
head filter fst
现在head
,这是一个从一个东西到一个数组的函数
单身。所以现在我们知道b
将是一个数组,
并且c
将成为该数组的一个元素。所以出于目的
我们的表达式,我们可以认为(.)
具有签名:
(.) :: ([d] -> d) -> (a -> [d]) -> a -> d -- Equation (1)
^^^^^^^^^^
filter fst
filter
的签名是:
filter :: (e -> Bool) -> [e] -> [e] -- Equation (2)
^^^^^^^^^^^
fst
(请注意,我已更改了类型变量的名称以避免混淆
使用a
s
我们已经拥有!)这说“给定e
到Bool的函数,
以及e
的列表,我可以为您提供e
s的列表。函数fst
有签名:
fst :: (f, g) -> f
说,“如果一对包含f
和g
,我可以给你一个f
”。
与公式2相比,我们知道这一点
e
将是一对值,是第一个元素
必须是Bool
。所以在我们的表达中,我们可以想到filter
有签名:
filter :: ((Bool, g) -> Bool) -> [(Bool, g)] -> [(Bool, g)]
(我在这里所做的就是用公式2中的e
替换(Bool, g)
。)
表达式filter fst
的类型为:
filter fst :: [(Bool, g)] -> [(Bool, g)]
回到等式1,我们可以看到(a -> [d])
现在必须
[(Bool, g)] -> [(Bool, g)]
,a
必须为[(Bool, g)]
和d
必须是(Bool, g)
。所以在我们的表达中,我们可以将(.)
视为
有签名:
(.) :: ([(Bool, g)] -> (Bool, g)) -> ([(Bool, g)] -> [(Bool, g)]) -> [(Bool, g)] -> (Bool, g)
总结:
(.) :: ([(Bool, g)] -> (Bool, g)) -> ([(Bool, g)] -> [(Bool, g)]) -> [(Bool, g)] -> (Bool, g)
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
head filter fst
head :: [(Bool, g)] -> (Bool, g)
filter fst :: [(Bool, g)] -> [(Bool, g)]
全部放在一起:
head . filter fst :: [(Bool, g)] -> (Bool, g)
除了我使用g
作为类型变量而不是b
之外,这相当于你所拥有的。
这可能听起来很复杂,因为我用血淋淋的细节描述了它。然而,这种推理很快成为第二天性,你可以在脑海中做到这一点。
答案 3 :(得分:3)
(向下翻页进行手动推导) 查找head . filter fst
== ((.) head) (filter fst)
的类型,给定 < / p>
head :: [a] -> a
(.) :: (b -> c) -> ((a -> b) -> (a -> c))
filter :: (a -> Bool) -> ([a] -> [a])
fst :: (a, b) -> a
这是通过一个小的Prolog程序以纯机械的方式实现:
type(head, arrow(list(A) , A)). %% -- known facts
type(compose, arrow(arrow(B, C) , arrow(arrow(A, B), arrow(A, C)))).
type(filter, arrow(arrow(A, bool), arrow(list(A) , list(A)))).
type(fst, arrow(pair(A, B) , A)).
type([F, X], T):- type(F, arrow(A, T)), type(X, A). %% -- application rule
在Prolog解释器中运行时自动生成
3 ?- type([[compose, head], [filter, fst]], T).
T = arrow(list(pair(bool, A)), pair(bool, A)) %% -- [(Bool,a)] -> (Bool,a)
其中类型以纯粹的语法方式表示为复合数据术语。例如。 [a] -> a
类型由arrow(list(A), A)
表示,可能的Haskell等效Arrow (List (Logvar "a")) (Logvar "a")
,给定相应的data
定义。
只使用了一个推理规则,即应用程序的推理规则,以及Prolog的结构统一,其中复合词匹配如果它们具有相同的形状且它们的成分匹配: f(a 1 , 2 ,... a n )和 g(b 1 ,b 2 ,... b m )匹配iff f 与 g , n == m 和 a i 匹配 b i ,逻辑变量可以根据需要采用任何值,但只能一次(无法更改)。
4 ?- type([compose, head], T1). %% -- (.) head :: (a -> [b]) -> (a -> b)
T1 = arrow(arrow(A, list(B)), arrow(A, B))
5 ?- type([filter, fst], T2). %% -- filter fst :: [(Bool,a)] -> [(Bool,a)]
T2 = arrow(list(pair(bool, A)), list(pair(bool, A)))
以机械方式执行类型推断 手动 ,涉及将事物一个接一个地写下来,注意到一边的等价物并执行替换,从而模仿Prolog的操作。我们可以将任何->, (_,_), []
等纯粹视为句法标记,而不理解它们的含义,并使用结构统一机械地执行该过程,此处只有一个rule of type inference,即。 应用程序的规则: (a -> b) c ⊢ b {a ~ c}
(在等价下用(a -> b)
替换c
和b
的并置a
和c
)。一致地重命名逻辑变量非常重要,以避免名称冲突:
(.) :: (b -> c ) -> ((a -> b ) -> (a -> c )) b ~ [a1],
head :: [a1] -> a1 c ~ a1
(.) head :: (a ->[a1]) -> (a -> c )
(a ->[c] ) -> (a -> c )
---------------------------------------------------------
filter :: ( a -> Bool) -> ([a] -> [a]) a ~ (a1,b),
fst :: (a1, b) -> a1 Bool ~ a1
filter fst :: [(a1,b)] -> [(a1,b)]
[(Bool,b)] -> [(Bool,b)]
---------------------------------------------------------
(.) head :: ( a -> [ c ]) -> (a -> c) a ~ [(Bool,b)]
filter fst :: [(Bool,b)] -> [(Bool,b)] c ~ (Bool,b)
((.) head) (filter fst) :: a -> c
[(Bool,b)] -> (Bool,b)
答案 4 :(得分:1)
您可以采用“技术”方式,通过大量复杂的统一步骤。或者你可以用“直观”的方式做到这一点,只看着事情并思考“好吧,我有什么在这里?这是什么期待?”等等。
好吧,filter
需要一个函数和一个列表,并返回一个列表。 filter fst
指定一个函数,但没有给出列表 - 所以我们仍在等待列表输入。所以filter fst
正在获取一个列表并返回另一个列表。 (顺便说一下,这是一个非常常见的Haskell短语。)
接下来,.
运算符将输出“管道”到head
,它需要一个列表并返回该列表中的一个元素。 (第一个,当它发生时。)因此无论filter
出现什么,head
都会给你第一个元素。在这一点上,我们可以得出结论
head . filter foobar :: [x] -> x
但是x
是什么?好吧,filter fst
将fst
应用于列表的每个元素(以决定是保留还是抛出它)。因此fst
必须适用于列表元素。并且fst
期望一个2元素元组,并返回该元组的第一个元素。现在filter
期待fst
返回Bool
,这意味着元组的第一个元素必须是Bool
。
把所有这些放在一起,我们得出结论
头。 filter fst :: [(Bool,y)] - &gt; (Bool,y)
什么是y
?我们不知道。我们实际上并不关心!无论它是什么,上述功能都将起作用。这就是我们的类型签名。
在更复杂的例子中,弄清楚发生了什么可能更难。 (特别是当涉及奇怪的类实例时!)但是对于像这样的小型实例,涉及常见功能,你通常可以只想“好了,这里发生了什么?那里有什么?这个函数有什么期望?”并且在没有太多手动算法追逐的情况下直接回答答案。