myt*_*hbu 13 haskell types type-inference unification
给出了Haskell函数:
head . filter fst
Run Code Online (Sandbox Code Playgroud)
现在的问题是如何手动"手动"找到类型.如果我让Haskell告诉我我得到的类型:
head . filter fst :: [(Bool, b)] -> (Bool, b)
Run Code Online (Sandbox Code Playgroud)
但是我想了解它是如何工作的,只使用已定义如下的已使用函数的签名:
head :: [a] -> a
(.) :: (b -> c) -> (a -> b) -> a -> c
filter :: (a -> Bool) -> [a] -> [a]
fst :: (a, b) -> a
Run Code Online (Sandbox Code Playgroud)
编辑:这么多很好的解释......选择最好的一个并不容易!
Fai*_*aiz 17
使用通常称为统一的过程来推断类型.Haskell属于Hindley-Milner族,它是用于确定表达式类型的统一算法.
如果统一失败,则表达式是类型错误.
表达方式
head . filter fst
Run Code Online (Sandbox Code Playgroud)
经过.让我们手动统一,看看为什么我们得到了什么.
让我们从filter fst
:
filter :: (a -> Bool) -> [a] -> [a]
fst :: (a' , b') -> a' -- using a', b' to prevent confusion
Run Code Online (Sandbox Code Playgroud)
filter
拿一个(a -> Bool)
,然后一个[a]
给另一个[a]
.在表达式中
filter fst
,我们传递给filter
参数fst
,其类型是(a', b') -> a'
.为此,类型fst
必须与filter
第一个参数的类型统一:
(a -> Bool) UNIFY? ((a', b') -> a')
Run Code Online (Sandbox Code Playgroud)
该算法统一了两个类型表达式,并尝试将尽可能多的类型变量(例如a
或a'
)绑定到实际类型(例如Bool
).
只有这样才会filter fst
导致有效的类型化表达式:
filter fst :: [a] -> [a]
Run Code Online (Sandbox Code Playgroud)
a'
显然Bool
.所以类型变量 a'
解析为a Bool
.并且(a', b')
可以统一到a
.所以,如果a
是(a', b')
和a'
是Bool
,然后a
就是(Bool, b')
.
如果我们传递了一个不兼容的参数filter
,例如42
(a Num
),那么Num a => a
with的统一a -> Bool
就会失败,因为这两个表达式永远不能统一到一个正确的类型表达式.
回来
filter fst :: [a] -> [a]
Run Code Online (Sandbox Code Playgroud)
这与a
我们所讨论的相同,因此我们将其替换为先前统一的结果:
filter fst :: [(Bool, b')] -> [(Bool, b')]
Run Code Online (Sandbox Code Playgroud)
下一点,
head . (filter fst)
Run Code Online (Sandbox Code Playgroud)
可以写成
(.) head (filter fst)
Run Code Online (Sandbox Code Playgroud)
所以拿 (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
Run Code Online (Sandbox Code Playgroud)
因此,要使统一成功,
head :: [a] -> a
必须统一 (b -> c)
filter fst :: [(Bool, b')] -> [(Bool, b')]
必须统一 (a -> b)
从(2)我们得到表达式
中的a
IS )b
(.) :: (b -> c) -> (a -> b) -> a -> c
因此,类型变量的值a
,并c
在表达(.) head (filter fst) :: a -> c
很容易地告诉,因为(1)给了我们之间的关系b
和c
,即:b
是的列表c
.当我们知道a
是[(Bool, b')]
,c
只能统一到(Bool, b')
因此head . filter fst
成功进行类型检查:
head . filter fst :: [(Bool, b')] -> (Bool, b')
Run Code Online (Sandbox Code Playgroud)
UPDATE
有趣的是,您可以从各个方面统一启动流程.我filter fst
先选择,然后继续(.)
,head
但正如其他例子所示,统一可以通过多种方式进行,与数学证明或定理推导可以多种方式完成的方式不同!
ich*_*ame 10
filter :: (a -> Bool) -> [a] -> [a]
获取一个函数(a -> Bool)
,一个相同类型a
的列表,并返回该类型的列表a
.
在确定指标的使用filter fst
与fst :: (a,b) -> a
这样的类型
filter (fst :: (Bool,b) -> Bool) :: [(Bool,b)] -> [(Bool,b)]
Run Code Online (Sandbox Code Playgroud)
是推断.接下来,您撰写你的结果[(Bool,b)]
与head :: [a] -> a
.
(.) :: (b -> c) -> (a -> b) -> a -> c
是两个功能的组成,func2 :: (b -> c)
和func1 :: (a -> b)
.在你的情况下,你有
func2 = head :: [ a ] -> a
Run Code Online (Sandbox Code Playgroud)
和
func1 = filter fst :: [(Bool,b)] -> [(Bool,b)]
Run Code Online (Sandbox Code Playgroud)
所以head
这里[(Bool,b)]
作为参数并按(Bool,b)
定义返回.最后你有:
head . filter fst :: [(Bool,b)] -> (Bool,b)
Run Code Online (Sandbox Code Playgroud)
让我们开始吧(.)
.它的类型签名是
(.) :: (b -> c) -> (a -> b) -> a -> c
Run Code Online (Sandbox Code Playgroud)
它说"鉴于从功能b
到c
,并从功能a
到b
,并且a
,我可以给你一个b
".我们想用head
和
filter fst
,所以`:
(.) :: (b -> c) -> (a -> b) -> a -> c
^^^^^^^^ ^^^^^^^^
head filter fst
Run Code Online (Sandbox Code Playgroud)
现在head
,这是一个从一个东西到一个东西的函数的函数.所以现在我们知道它将b
成为一个数组,并且c
将成为该数组的一个元素.所以为了表达的目的,我们可以认为(.)
有签名:
(.) :: ([d] -> d) -> (a -> [d]) -> a -> d -- Equation (1)
^^^^^^^^^^
filter fst
Run Code Online (Sandbox Code Playgroud)
签名filter
是:
filter :: (e -> Bool) -> [e] -> [e] -- Equation (2)
^^^^^^^^^^^
fst
Run Code Online (Sandbox Code Playgroud)
(注意,我已经更改了类型变量的名称,以避免与a
我们已经拥有的s 混淆!)这说"给定一个函数来自e
Bool,以及一个e
s 列表,我可以给你一个e
s 的列表".该功能fst
有签名:
fst :: (f, g) -> f
Run Code Online (Sandbox Code Playgroud)
他说,"如果有一对包含一个f
和一个g
,我可以给你一个f
".与公式2相比,我们知道它将
e
是一对值,其中第一个元素必须是a Bool
.所以在我们的表达中,我们可以认为filter
有签名:
filter :: ((Bool, g) -> Bool) -> [(Bool, g)] -> [(Bool, g)]
Run Code Online (Sandbox Code Playgroud)
(我在这里所做的就是e
用(Bool, g)
公式2 替换.)表达式filter fst
的类型如下:
filter fst :: [(Bool, g)] -> [(Bool, g)]
Run Code Online (Sandbox Code Playgroud)
让我们再回到公式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)
Run Code Online (Sandbox Code Playgroud)
总结一下:
(.) :: ([(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)]
Run Code Online (Sandbox Code Playgroud)
把它们放在一起:
head . filter fst :: [(Bool, g)] -> (Bool, g)
Run Code Online (Sandbox Code Playgroud)
这相当于你所拥有的,除了我用作g
类型变量而不是b
.
这可能听起来很复杂,因为我用血淋淋的细节描述了它.然而,这种推理很快成为第二天性,你可以在脑海中做到这一点.
(跳过手动推导)
找到head . filter fst
==的类型((.) head) (filter fst)
,给定
head :: [a] -> a
(.) :: (b -> c) -> ((a -> b) -> (a -> c))
filter :: (a -> Bool) -> ([a] -> [a])
fst :: (a, b) -> a
Run Code Online (Sandbox Code Playgroud)
这是通过一个小的 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
Run Code Online (Sandbox Code Playgroud)
当在 Prolog 解释器中运行时,它会自动生成,
3 ?- type([[compose, head], [filter, fst]], T).
T = arrow(list(pair(bool, A)), pair(bool, A)) %% -- [(Bool,a)] -> (Bool,a)
Run Code Online (Sandbox Code Playgroud)
其中类型以纯语法方式表示为复合数据项。例如,类型[a] -> a
由 表示arrow(list(A), A)
,可能与 Haskell 等效Arrow (List (Logvar "a")) (Logvar "a")
,给出适当的data
定义。
仅使用了一个推理规则,即应用程序的推理规则,以及 Prolog 的结构统一,即如果复合词具有相同的形状且其成分匹配,则复合词匹配:f(a 1 , a 2 , ... a n )和g (b 1 , b 2 , ... b m )匹配仅当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)))
Run Code Online (Sandbox Code Playgroud)
要以机械方式手动执行类型推断,涉及将一个东西写在另一个下面,注意旁边的等价物并执行替换,从而模仿 Prolog 的操作。我们可以将 any->, (_,_), []
等纯粹视为句法标记,而根本不了解它们的含义,并使用结构统一来机械地执行该过程,在这里,只有一种类型推断规则,即。的规则应用:(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)
Run Code Online (Sandbox Code Playgroud)