撰写function组合:(。)。(。)如何工作?
(.)
需要两个函数,它们取一个值并返回一个值:
(.) :: (b -> c) -> (a -> b) -> a -> c
由于(.)
有两个参数,所以我觉得(.).(.)
应该是无效的,但它是完全正确的:
(.).(.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
这里发生了什么? 我意识到这个问题措辞严厉……所有的function都是由于咖喱而引起的。 也许更好的方式来说这是types不匹配。
让我们先玩typechecker的机械certificate。 之后我会描述一个直观的思考方式。
我想将(.)
应用于(.)
,然后将(.)
应用于结果。 第一个应用程序可以帮助我们定义variables的一些等价性。
((.) :: (b -> c) -> (a -> b) -> a -> c) ((.) :: (b' -> c') -> (a' -> b') -> a' -> c') ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'') let b = (b' -> c') c = (a' -> b') -> a' -> c' ((.) (.) :: (a -> b) -> a -> c) ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'')
然后我们开始第二个,但很快就卡住了…
let a = (b'' -> c'')
这是关键:我们想让let b = (a'' -> b'') -> a'' -> c''
,但是我们已经定义了b
,所以我们必须尝试统一 – 以匹配我们最好的两个定义。 幸运的是,他们确实匹配
UNIFY b = (b' -> c') =:= (a'' -> b'') -> a'' -> c'' which implies b' = a'' -> b'' c' = a'' -> c''
通过这些定义/统一我们可以继续申请
((.) (.) (.) :: (b'' -> c'') -> (a' -> b') -> (a' -> c'))
然后展开
((.) (.) (.) :: (b'' -> c'') -> (a' -> a'' -> b'') -> (a' -> a'' -> c''))
并清理它
substitute b'' -> b c'' -> c a' -> a a'' -> a1 (.).(.) :: (b -> c) -> (a -> a1 -> b) -> (a -> a1 -> c)
说实话,这是一个违反直觉的结果。
这是直觉。 首先看看fmap
fmap :: (a -> b) -> (fa -> fb)
它将一个function“提升”成一个Functor
。 我们可以反复应用
fmap.fmap.fmap :: (Functor f, Functor g, Functor h) => (a -> b) -> (f (g (ha)) -> f (g (hb)))
使我们能够将function提升到更深层次的Functors
。
事实certificate,数据types(r ->)
是一个Functor
。
instance Functor ((->) r) where fmap = (.)
这应该看起来很熟悉。 这意味着fmap.fmap
转换为(.).(.)
。 因此, (.).(.)
就是让我们转换(r ->)
Functor
的深层和深层参数types。 (r ->)
Functor
实际上是Reader
Monad
,所以分层的Reader
就像拥有多个独立的全局不变状态。
或者像有多个input参数不受fmap
影响。 (> 1)参数函数的“只是结果”组成一个新的继续函数。
最后值得注意的是,如果你觉得这个东西很有趣,它就构成了控制镜头的核心直觉。
让我们暂时忽略types,只使用lambda微积分。
-
Desugar中缀符号:
(.) (.) (.)
-
ETA-扩大:
(\ ab -> (.) ab) (\ cd -> (.) cd) (\ ef -> (.) ef)
-
内联
(.)
的定义:
(\ abx -> a (bx)) (\ cdy -> c (dy)) (\ efz -> e (fz))
-
替代
a
:
(\ bx -> (\ cdy -> c (dy)) (bx)) (\ efz -> e (fz))
-
替补
b
:
(\ x -> (\ cdy -> c (dy)) ((\ efz -> e (fz)) x))
-
替代
e
:
(\ x -> (\ cdy -> c (dy)) (\ fz -> x (fz)))
-
替补
c
:
(\ x -> (\ dy -> (\ fz -> x (fz)) (dy)))
-
替补
f
:
(\ x -> (\ dy -> (\ z -> x (dyz))))
-
Resugar lambda表示法:
\ xdyz -> x (dyz)
如果你问GHCi,你会发现这是预期的types。 为什么? 因为函数箭头是右关联的以支持柯里:types(b -> c) -> (a -> b) -> a -> c
真的意味着(b -> c) -> ((a -> b) -> (a -> c))
。 同时,typesvariablesb
可以代表任何types, 包括函数types 。 看到连接?
这是一个相同现象的简单例子:
id :: a -> a id x = x
id的types说id应该有一个参数。 事实上,我们可以用一个论据来说:
> id "hello" "hello"
但事实certificate我们也可以用两个参数来调用它:
> id not True False
甚至:
> id id "hello" "hello"
到底是怎么回事? 理解id not True
关键是先看看id not
。 显然,这是允许的,因为它将id应用于一个参数。 not
的types是Bool -> Bool
,所以我们知道id的types应该是Bool -> Bool
,所以我们知道这个id的出现有type:
id :: (Bool -> Bool) -> (Bool -> Bool)
或者,用更less的括号:
id :: (Bool -> Bool) -> Bool -> Bool
所以这个id的发生实际上有两个参数 。
同样的推理也适用于id id "hello"
和(.) . (.)
(.) . (.)
。
这是我认为先把握一般情况,然后再考虑具体情况的简单方法之一。 所以我们来考虑仿函数。 我们知道仿函数提供了一种在结构上映射函数的方法 –
class Functor f where fmap :: (a -> b) -> fa -> fb
但是如果我们有两层函数呢? 例如,列表的列表? 在这种情况下,我们可以使用两层fmap
>>> let xs = [[1,2,3], [4,5,6]] >>> fmap (fmap (+10)) xs [[11,12,13],[14,15,16]]
但是模式f (gx)
与(f . g) x
完全相同,所以我们可以写出来
>>> (fmap . fmap) (+10) xs [[11,12,13],[14,15,16]]
什么是fmap . fmap
的typesfmap . fmap
fmap . fmap
?
>>> :t fmap.fmap :: (Functor g, Functor f) => (a -> b) -> f (ga) -> f (gb)
我们看到它映射了两层函数,就像我们想要的那样。 但现在请记住(->) r
是一个函数(函数的types,你可能更喜欢读作(r ->)
),它的函子实例是
instance Functor ((->) r) where fmap fg = f . g
对于一个函数, fmap
只是函数组合! 当我们fmap
两个fmap
我们映射了函数functor的两个级别。 我们最初有一些types(->) s ((->) ra)
,它相当于s -> r -> a
,而我们最终得到了typess -> r -> b
,所以types(.).(.)
必须是
(.).(.) :: (a -> b) -> (s -> r -> a) -> (s -> r -> b)
它接受它的第一个函数,并使用它来转换第二个(双参数)函数的输出。 例如,函数((.).(.)) show (+)
是两个参数的函数,首先将它们的参数加在一起,然后使用show
将结果转换为String
:
>>> ((.).(.)) show (+) 11 22 "33"
例如,有一个自然泛化思考更长的fmap
链
fmap.fmap.fmap :: (Functor f, Functor g, Functor h) => (a -> b) -> f (g (ha)) -> f (g (hb))
它映射三层函数,相当于由三个参数构成的函数:
(.).(.).(.) :: (a -> b) -> (r -> s -> t -> a) -> (r -> s -> t -> b)
例如
>>> import Data.Map >>> ((.).(.).(.)) show insert 1 True empty "fromList [(1,True)]"
它将True
值插入到键1
的空映射中,然后使用show
将输出转换为string。
这些function通常是有用的,所以你有时看到它们被定义为
(.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b) (.:) = (.).(.)
所以你可以写
>>> let f = show .: (+) >>> f 10 20 "30"
当然,可以给出一个简单的,有针对性的(.:)
定义
(.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b) (f .: g) xy = f (gxy)
这可能有助于使(.).(.)
神秘化。
你是对的, (.)
只有两个参数。 你似乎混淆了haskell的语法。 在expression式(.).(.)
,它实际上是中间的点,将另外两个点作为参数,就像在expression式100 + 200
中可以写成(+) 100 200
。
(.).(.) === (number the dots) (1.)2.(3.) === (rewrite using just syntax rules) (2.)(1.)(3.) === (unnumber and put spaces) (.) (.) (.) ===
从(.) (.) (.)
中应该更清楚地知道第一个(.)
将第二个(.)
和第三个(.)
作为参数。
是的,这是由于咖喱。 (.)
因为Haskell中的所有函数只有一个参数。 你正在创作的是第一次部分调用每个组合的(.)
,它接受它的第一个参数(组合的第一个函数)。
(先阅读我的函数组合的答案 ,$运算符和无点风格。)
想象一下,你有一个简单的function:它加起来2个数字,然后否定结果。 我们将它称为foo
:
foo ab = negate (a + b)
现在,让我们一步一步地将它看成是免费的,看看我们最终的结果:
foo ab = negate $ a + b foo ab = negate $ (+) ab foo ab = negate $ (+) a $ b foo ab = negate . (+) a $ b foo a = negate . (+) a -- fx = gx is equivalent to f = g foo a = (.) negate ((+) a) -- any infix operator is just a function foo a = (negate.) ((+) a) -- (2+) is the same as ((+) 2) foo a = (negate.) $ (+) a foo a = (negate.) . (+) $ a foo = (negate.) . (+) foo = ((.) negate) . (+) foo = (.) ((.) negate) (+) -- move dot in the middle in prefix position foo = ((.) ((.) negate)) (+) -- add extra parentheses
现在我们来更仔细地分析expression式(.) ((.) negate)
。 这是(.)
函数的部分应用,其第一个参数是((.) negate)
。 我们可以进一步改造它吗? 我们可以:
(.) ((.) negate) (.) . (.) $ negate -- because f (fx) is the same as (f . f) x (.)(.)(.) $ negate ((.)(.)(.)) negate
(.).(.)
等价于(.)(.)(.)
,因为在第一个expression式中,中间的点可以移动到前缀位置,并用圆括号括起来,从而产生第二个expression式。
现在我们可以重写我们的foo
函数:
foo = ((.).(.)) negate (+) foo = ((.)(.)(.)) negate (+) -- same as previous one foo = negate .: (+) where (.:) = (.).(.)
现在你知道(.).(.)
等价于(\fgxy -> f (gxy))
:
(\fgxy -> f (gxy)) negate (+) 2 3 -- returns -5 ((.).(.)) negate (+) 2 3 -- returns -5