我试图理解为什么功能
map (filter fst)
的类型为
[[(Bool, a)]] -> [[(Bool, a)]]
如何"过滤fst"如果filter必须接收一个返回Bool-Type的函数,fst只返回一个元组的第一个元素?
filter :: (a -> Bool) -> [a] -> [a]
fst :: (a, b) -> a
有人能解释我吗?谢谢;)
答案 0 :(得分:16)
如何"过滤fst"如果过滤器必须接收一个返回Bool-Type的函数,fst只返回一个元组的第一个元素?
从某种意义上说,你已经回答了自己的问题!让我们分解一下:
过滤器必须接收一个返回Bool-Type
的函数
好的,让我们来看看你传递的内容:fst
。 fst
是一个函数吗?是的,是的,所以我们已经把第一部分打倒了。它会返回Bool
吗?那么,让我们来看看它的作用:
fst只返回元组的第一个元素
因此,如果元组的第一个元素是Bool
,那么是的,它确实会返回一个bool!但是,如果元组的第一个元素不是Bool
,那么它就没有了,并且将无法进行类型检查。
让我们再看看你提出的类型。我要更改类型变量的名称只是为了让事情更清楚:
filter :: (a -> Bool) -> [a] -> [a]
fst :: (b, c) -> b
fst
需要(b, c)
并返回b
,过滤器需要一个a
并返回Bool
的函数。我们正在传递fst
,因此上面的a
必须是(b, c)
,因为它是fst
的第一个参数。我们传递到filter
的函数的返回值必须是Bool
,因此上面的b
必须是Bool
。 c
可以是任何内容,因为过滤器根本不使用它。替换a
和b
的值为我们提供了filter fst
的最终类型:
filter fst :: [(Bool, c)] -> [(Bool, c)]
最后,map
的类型是:
map :: (d -> e) -> [d] -> [e]
(同样,我在这里重新命名了类型变量,只是为了将它们与我们上面使用过的那些区分开来,但请记住它实际上并不重要,因为它们所谓的是什么只要它们在类型注释的范围内保持一致)
map (filter fst)
将我们在上面定义的filter fst
作为第一个参数传递给map
。将d
的参数替换为e
的结果,我们可以看到此函数必须为[(Bool, c)] -> [(Bool, c)]
,换句话说,d
和e
都是{{1} }}。将这些插入到我们到达最终类型的函数中:
(Bool, c)
答案 1 :(得分:2)
fst
是一个返回布尔值的函数,只要你限制元组将布尔值作为它们的第一个元素(第二个元素可以是任何东西,因此(Bool, a)
答案 2 :(得分:1)
:t filter -- (a -> Bool) -> [a] -> [a]
:t fst -- (a,b) -> a
但是,我们也可以交换fst
的类型变量来获取:
:t fst -- (c,b) -> c
因此,filter
的第一个参数类型为a -> Bool
,而fst
本身为(c,b) -> c
。现在我们尝试将它结合起来(我认为这称为统一):
1st arg of filter: a -> Bool
fst: (c,b) -> c
由此我们可以推断c
必须是Bool
(因为右边必须相等)并获得:
1st arg of filter: a -> Bool
fst: (Bool,b) -> Bool
从上面我们推断,a
必须是(Bool,b)
,并获得:
1st arg of filter: (Bool,b) -> Bool
fst: (Bool,b) -> Bool
我们已经完成了。
答案 3 :(得分:1)
由于我已经在danielpwright的回答发布之前写下了这篇文章,所以无论如何我发布了它。我现在正在通过我的思维过程查看filter fst
的类型。
首先,记下类型签名(更改fst,使其类型变量名称不与过滤器的名称冲突):
filter :: (a -> Bool) -> [a] -> [a]
fst :: (b, c) -> b
将(a -> Bool)
与((b, c) -> b)
匹配:
b
必须为Bool
,这意味着a
必须为(Bool,c)
通过使用此信息对filter
进行专门化,它变为:
filter :: ((Bool,c) -> Bool) -> [(Bool,c)] -> [(Bool,c)]
导致
filter fst :: [(Bool, c)] -> [(Bool, c)]
答案 4 :(得分:1)
你真的只需要解决一些类型方程式。让我们开始吧:
filter :: (a -> Bool) -> [a] -> [a]
fst :: (b, c) -> b
因此,filter fst :: [a] -> [a]
其中a
是以下类型等式的解决方案:
a -> Bool = (b, c) -> b
暗示
a = (b, c)
Bool = b
暗示
a = (Bool, c)
因此,filter fst :: [(Bool, c)] -> [(Bool, c)]
。
现在我们有:
map :: (a -> b) -> [a] -> [b]
filter fst :: [(Bool, c)] -> [(Bool, c)]
所以,map (filter fst) :: [a] -> [b]
其中a
和b
由以下类型公式给出:
a -> b = [(Bool, c)] -> [(Bool, c)]
暗示
a = [(Bool, c)]
b = [(Bool, c)]
因此,map (filter fst) :: [[(Bool, c)]] -> [[(Bool, c)]]
。
答案 5 :(得分:1)
正如其他人所做的那样,我想在这里解决类型方程;但是我想以更直观的方式将它们写下来,因此可以以更自动的方式执行派生。我们来看看。
filter :: ( a -> Bool) -> [a] -> [a]
fst :: (c,d) -> c -- always use unique type var names,
------------------------ -- rename freely but consistently
filter fst :: [a] -> [a], a -> Bool ~ (c,d) -> c
a ~ (c,d) , Bool ~ c
a ~ (Bool,d)
---------------------------
:: [(Bool,d)] -> [(Bool,d)]
纯机械的东西。 :)有了这个,
map :: ( a -> b ) -> [a] -> [b]
filter fst :: [(Bool,d)] -> [(Bool,d)]
------------------------------
map (filter fst) :: [a] -> [b], a ~ [(Bool,d)] , b ~ [(Bool,d)]
-------------------------------
:: [[(Bool,d)]] -> [[(Bool,d)]]
最终类型中的类型变量可以自由重命名以便于阅读(当然,以一致的方式)。
我的答案唯一能补充到其他答案中已经显示的内容,就是这个建议(我认为这很重要):如果你遵循这个简单的写下一个低于另一个的原则,以非常机械,自动的方式执行这些类型的统一变得非常容易(从而减少出错的可能性)。
另一个例子,包括实际的类型派生Prolog程序,请参阅Haskell: how to infer the type of an expression manually。