haskell 中的 Eta 减少
Eta reduction in haskell
我试了很久haskell里减少这个功能,我想表达例如:
mySum x y = x + y
mySum x y = (+) x y
mySum x = (+) x
mySum = (+) -- it's Messi's goal!
我的功能有点复杂,但是我真的做不到,我到处看看,我知道有一些技巧,比如修改右边,然后使用flip
.我试过了,但卡在了这里:
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f x y = map (uncurry f) (zip x y)
步骤:
zipWith' f x y = map (uncurry f) (zip x y)
zipWith' f x y = flip map (zip x y) (uncurry f)
zipWith' f x y = flip map (zip x y) $ uncurry f
然后我不知道如何继续...
我正在寻找一个可以逐步解释如何实现 "Messi's goal" 的答案,我知道要问的问题很多,所以我会尽快添加感谢努力的赏金
zipWith' f x y = map (uncurry f) (zip x y)
将应用程序重写为组合和 eta-reduce:
-- \y -> let g = map (uncurry f); h = zip x in (g . h) y
-- let g = map (uncurry f); h = zip x in g . h
zipWith' f x = map (uncurry f) . zip x
将中缀重写为前缀:
-- g . h = (.) g h
zipWith' f x = (.) (map (uncurry f)) (zip x)
将应用程序重写为组合和 eta-reduce:
-- \x -> let g = (.) (map (uncurry f)); h = zip in (g . h) x
-- let g = (.) (map (uncurry f)); h = zip in g . h
zipWith' f = (.) (map (uncurry f)) . zip
将中缀重写为前缀:
-- g . h = (.) g h
zipWith' f = (.) ((.) (map (uncurry f))) zip
使用flip
将f
移动到右侧:
-- flip f x y = f y x
zipWith' f = flip (.) zip ((.) (map (uncurry f)))
将应用程序重写为组合:
-- g (h (i x)) = (g . h . i) x
zipWith' f = flip (.) zip (((.) . map . uncurry) f)
将应用程序重写为组合和 eta-reduce:
-- \f -> let g = flip (.) zip; h = (.) . map . uncurry in (g . h) f
-- let g = flip (.) zip; h = (.) . map . uncurry in g . h
zipWith' = (flip (.) zip) . ((.) . map . uncurry)
删除多余的括号:
zipWith' = flip (.) zip . (.) . map . uncurry
如果你愿意,可以简化为中缀:
zipWith' = (. zip) . (.) . map . uncurry
不过这个结果可读性不是很好。
通常在编写 完全 无点代码时,您希望利用 ->
来自 Control.Arrow
的应用和箭头组合器。与其尝试编写像 \ f x y -> ...
这样的函数,不如先将参数分组到元组中,以便更轻松地重新排列和传递它们。在这种情况下,我将使用 \ (f, (x, y)) -> ...
\ (f, (x, y)) -> map (uncurry f) (zip x y)
我们可以通过将uncurry
应用于zip
来消除(x, y)
的解包:
\ (f, (x, y)) -> map (uncurry f) (uncurry zip (x, y))
\ (f, xy) -> map (uncurry f) (uncurry zip xy)
现在我们有一个简单的案例:将两个函数(uncurry
和 uncurry zip
)应用于两个参数(f
和 xy
),然后组合结果( map
)。为此,我们可以使用 Control.Arrow
中的 ***
组合子,类型为:
(***) :: Arrow a => a b c -> a b' c' -> a (b, b') (c, c')
专用于函数,即:
(***) @(->) :: (b -> c) -> (b' -> c') -> (b, b') -> (c, c')
这只是让我们对一对中的每个元素应用一个函数。完美!
uncurry *** uncurry zip
:: (a -> b -> c, ([x], [y])) -> ((a, b) -> c, [(x, y)])
您可以将 uncurry f
视为使用函数 f
组合一对元素。所以在这里我们可以使用 uncurry map
:
组合结果
uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c, ([a], [b])) -> [c]
您可以将 curry
视为将元组上的函数转换为多参数函数。这里我们有两层元组,外层 (f, xy)
和内层 (x, y)
。我们可以用 curry
:
解压外层
curry $ uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c) -> ([a], [b]) -> [c]
现在,您可以将 ->
应用程序中的 fmap f
视为“跳过”第一个参数:
fmap @((->) _) :: (a -> b) -> (t -> a) -> t -> b
所以我们可以使用 fmap curry
:
解压第二个元组
fmap curry $ curry $ uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c) -> [a] -> [b] -> [c]
大功告成!或者不完全是。在编写 point-free 代码时,将事情分解成许多名称更清晰的可重用小函数是值得的,例如:
zipWith' = untuple2 $ combineWith map apply zipped
where
untuple2 = fmap curry . curry
combineWith f g h = uncurry f . (g *** h)
apply = uncurry
zipped = uncurry zip
然而,虽然了解这些技术很有用,但所有这些都只是徒劳无益的诡计,很容易迷失方向。大多数时候,你应该只在 Haskell 中使用无点样式赢得可读性,这些结果都不比简单的原始版本更清晰:
zipWith' f x y = map (uncurry f) (zip x y)
或部分无积分版本:
zipWith' f = map (uncurry f) .: zip
where (.:) = (.) . (.)
我试了很久haskell里减少这个功能,我想表达例如:
mySum x y = x + y
mySum x y = (+) x y
mySum x = (+) x
mySum = (+) -- it's Messi's goal!
我的功能有点复杂,但是我真的做不到,我到处看看,我知道有一些技巧,比如修改右边,然后使用flip
.我试过了,但卡在了这里:
zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith' f x y = map (uncurry f) (zip x y)
步骤:
zipWith' f x y = map (uncurry f) (zip x y)
zipWith' f x y = flip map (zip x y) (uncurry f)
zipWith' f x y = flip map (zip x y) $ uncurry f
然后我不知道如何继续...
我正在寻找一个可以逐步解释如何实现 "Messi's goal" 的答案,我知道要问的问题很多,所以我会尽快添加感谢努力的赏金
zipWith' f x y = map (uncurry f) (zip x y)
将应用程序重写为组合和 eta-reduce:
-- \y -> let g = map (uncurry f); h = zip x in (g . h) y
-- let g = map (uncurry f); h = zip x in g . h
zipWith' f x = map (uncurry f) . zip x
将中缀重写为前缀:
-- g . h = (.) g h
zipWith' f x = (.) (map (uncurry f)) (zip x)
将应用程序重写为组合和 eta-reduce:
-- \x -> let g = (.) (map (uncurry f)); h = zip in (g . h) x
-- let g = (.) (map (uncurry f)); h = zip in g . h
zipWith' f = (.) (map (uncurry f)) . zip
将中缀重写为前缀:
-- g . h = (.) g h
zipWith' f = (.) ((.) (map (uncurry f))) zip
使用flip
将f
移动到右侧:
-- flip f x y = f y x
zipWith' f = flip (.) zip ((.) (map (uncurry f)))
将应用程序重写为组合:
-- g (h (i x)) = (g . h . i) x
zipWith' f = flip (.) zip (((.) . map . uncurry) f)
将应用程序重写为组合和 eta-reduce:
-- \f -> let g = flip (.) zip; h = (.) . map . uncurry in (g . h) f
-- let g = flip (.) zip; h = (.) . map . uncurry in g . h
zipWith' = (flip (.) zip) . ((.) . map . uncurry)
删除多余的括号:
zipWith' = flip (.) zip . (.) . map . uncurry
如果你愿意,可以简化为中缀:
zipWith' = (. zip) . (.) . map . uncurry
不过这个结果可读性不是很好。
通常在编写 完全 无点代码时,您希望利用 ->
来自 Control.Arrow
的应用和箭头组合器。与其尝试编写像 \ f x y -> ...
这样的函数,不如先将参数分组到元组中,以便更轻松地重新排列和传递它们。在这种情况下,我将使用 \ (f, (x, y)) -> ...
\ (f, (x, y)) -> map (uncurry f) (zip x y)
我们可以通过将uncurry
应用于zip
来消除(x, y)
的解包:
\ (f, (x, y)) -> map (uncurry f) (uncurry zip (x, y))
\ (f, xy) -> map (uncurry f) (uncurry zip xy)
现在我们有一个简单的案例:将两个函数(uncurry
和 uncurry zip
)应用于两个参数(f
和 xy
),然后组合结果( map
)。为此,我们可以使用 Control.Arrow
中的 ***
组合子,类型为:
(***) :: Arrow a => a b c -> a b' c' -> a (b, b') (c, c')
专用于函数,即:
(***) @(->) :: (b -> c) -> (b' -> c') -> (b, b') -> (c, c')
这只是让我们对一对中的每个元素应用一个函数。完美!
uncurry *** uncurry zip
:: (a -> b -> c, ([x], [y])) -> ((a, b) -> c, [(x, y)])
您可以将 uncurry f
视为使用函数 f
组合一对元素。所以在这里我们可以使用 uncurry map
:
uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c, ([a], [b])) -> [c]
您可以将 curry
视为将元组上的函数转换为多参数函数。这里我们有两层元组,外层 (f, xy)
和内层 (x, y)
。我们可以用 curry
:
curry $ uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c) -> ([a], [b]) -> [c]
现在,您可以将 ->
应用程序中的 fmap f
视为“跳过”第一个参数:
fmap @((->) _) :: (a -> b) -> (t -> a) -> t -> b
所以我们可以使用 fmap curry
:
fmap curry $ curry $ uncurry map . (uncurry *** uncurry zip)
:: (a -> b -> c) -> [a] -> [b] -> [c]
大功告成!或者不完全是。在编写 point-free 代码时,将事情分解成许多名称更清晰的可重用小函数是值得的,例如:
zipWith' = untuple2 $ combineWith map apply zipped
where
untuple2 = fmap curry . curry
combineWith f g h = uncurry f . (g *** h)
apply = uncurry
zipped = uncurry zip
然而,虽然了解这些技术很有用,但所有这些都只是徒劳无益的诡计,很容易迷失方向。大多数时候,你应该只在 Haskell 中使用无点样式赢得可读性,这些结果都不比简单的原始版本更清晰:
zipWith' f x y = map (uncurry f) (zip x y)
或部分无积分版本:
zipWith' f = map (uncurry f) .: zip
where (.:) = (.) . (.)