我们的定义是这样开始的:
foo :: (a -> b -> c) -> (c -> d -> e) -> a -> b -> d -> e
foo abc cde a b d = e
现在让我们来填补缺失的部分。
我们需要一个e;获得它的唯一方法是将第二个函数应用于c 和d。
e = cde c d
我们已经获得了d,但我们需要c。我们如何获得c?通过将第一个函数应用于a 和b。
c = abc a b
我们得到了这两个,所以我们完成了。
foo :: (a -> b -> c) -> (c -> d -> e) -> a -> b -> d -> e
foo abc cde a b d = e
where
e = cde c d
c = abc a b
我们可能会停在这里。这是一个非常好的定义。
但是如果我们想让它更简洁,让我们从替换e的定义开始
foo abc cde a b d = cde c d
where
c = abc a b
然后是c
foo abc cde a b d = cde (abc a b) d
我们立即看到我们可以通过 eta reduce 删除 d。
foo abc cde a b = cde (abc a b)
该类型现在稍微更通用了。d -> e 已合并为一个类型变量,因为它实际上可以是任何东西。
foo :: (a -> b -> c) -> (c -> de) -> a -> b -> de
我们现在可以在 aviary 中看到我们的组合器实际上是翻转的黑鸟。
blackbird :: (c -> d) -> (a -> b -> c) -> a -> b -> d
foo :: (a -> b -> c) -> (c -> de) -> a -> b -> de
foo = flip blackbird
事实上,如果我们查看黑鸟的来源,它看起来很像我们所写的。
-- | B1 combinator - blackbird - specs 'oo'.
blackbird :: (c -> d) -> (a -> b -> c) -> a -> b -> d
blackbird f g x y = f (g x y)
我们可以更免点吗?我们可以考虑 uncurrying abc
foo abc cde a b = cde (uncurry abc (a, b))
用函数组合重写这个嵌套
foo abc cde a b = (cde . uncurry abc) (a, b)
然后又回来了
foo abc cde a b = curry (cde . uncurry abc) a b
现在我们可以再砍掉两个参数。
foo abc cde = curry (cde . uncurry abc)
我们绝对应该在这里停下来。但是如果我们现在把论点颠倒过来会怎样
foo = flip $ \cde abc -> curry (cde . uncurry abc)
重写右半部分使其无点
foo = flip $ \cde abc -> (curry . ((cde .) . uncurry)) abc
和 eta 再次减少
foo = flip $ \cde -> curry . ((cde .) . uncurry)
并采取最后荒谬的一步
foo = flip $ (curry .) . (. uncurry) . (.)
我们现在是免费的!