Haskell 函数组合 - (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
Haskell Function Composition - (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
我想了解如何在无积分的情况下完成以下操作:
withinBounds :: [Int] -> Bool
withinBounds xs = (all (>= 0) xs) && (all (<= 8) xs)
我知道为了 readability/sanity 这样写更好,但我想了解更多有关如何组合函数的信息。我一直在摸索如何才能做到这一点。
整个(扩展?)类型签名是
[Int] -> ([Int] -> Bool) -> ([Int] -> Bool) -> (Bool -> Bool -> Bool) -> Bool
我要获取的组合的类型签名是
(a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
我以 bastard-lambda 形式写了以下笔记。如果有一种方法可以稍微简化 lambda 演算的问题,如果也能解释一下就太好了:
\L@[] -> \f1@([] -> Bool) -> \f2@([] -> Bool) -> \f3@(Bool -> Bool -> Bool) -> f3.(f1.L).(f2.L)
上面.
是应用,@
是捕获(所以f3是(Bool -> Bool -> Bool)的别称)。
非常感谢。
编辑:我知道这不是最佳或可重用的代码,而且我知道将它变成无点会使它在可读性等方面变得更糟。为了澄清,我问的是如何将它变成点-free 因为我想了解更多关于 haskell 和 composition.
编辑 2:A really good SO answer on point-free
您可以利用函数是可应用的这一事实。
并这样写 withinBounds
:
withinBounds = pure (&&) <*> all (>= 0) <*> all (<= 8)
或者这样:
withinBounds = (&&) <$> all (>= 0) <*> all (<= 8)
您可以阅读 Applicatives here and here
有一个 class 基本上专用于无点组合† 具有多个“通道”:Arrow
。如果您决心让一切都变得毫无意义,那么这就是 IMO 的必经之路。丑陋的一点是你经常需要 uncurry 函数:
import Control.Arrow
withinBounds = all (>=0) &&& all (<=8) >>> uncurry (&&)
通过图表可以更好地理解其工作原理:
all (>=0) ────
╱ ╲
──── &&& >>> uncurry (&&) ───
╲ ╱
all (<=8) ────
†Arrow
适用于通用设置;不仅适用于 Hask-函数,而且适用于任何合适的类别。但它足以将其应用于函数。
注意 x<=8 当且仅当 8-x>=0,所以,仅使用序曲,我们可以写成
withinBounds :: [Int] -> Bool
withinBounds = all $ (all (>=0)) . (zipWith (+) [0,8]) . (zipWith (*) [1,-1]) . (replicate 2)
基本上我只是将 x
映射到 [x,x]
然后映射到 [x,8-x]
然后我要求这两个同时 >=0。
当然,正如评论中所指出的,您也可以制作a,b
参数,以便稍后重用。
希望这对您有所帮助。
围绕整个问题做一个结束-运行,我想我可能会这样写:
import Data.Ix
withinBounds = all (inRange (0, 8))
当然,这有点过分了,从那时起人们自然会问如何以无点方式实现inRange
。如果您绝对不能使用 inRange
,那么我将以这种方式内联实现它:
withinBounds = all (liftA2 (&&) (>=0) (<=8))
这使用 reader 应用程序为两个函数提供单个参数。 liftA2
是您请求的组合函数,但参数已翻转:
requested :: (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
liftA2 :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)
我想了解如何在无积分的情况下完成以下操作:
withinBounds :: [Int] -> Bool
withinBounds xs = (all (>= 0) xs) && (all (<= 8) xs)
我知道为了 readability/sanity 这样写更好,但我想了解更多有关如何组合函数的信息。我一直在摸索如何才能做到这一点。 整个(扩展?)类型签名是
[Int] -> ([Int] -> Bool) -> ([Int] -> Bool) -> (Bool -> Bool -> Bool) -> Bool
我要获取的组合的类型签名是
(a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
我以 bastard-lambda 形式写了以下笔记。如果有一种方法可以稍微简化 lambda 演算的问题,如果也能解释一下就太好了:
\L@[] -> \f1@([] -> Bool) -> \f2@([] -> Bool) -> \f3@(Bool -> Bool -> Bool) -> f3.(f1.L).(f2.L)
上面.
是应用,@
是捕获(所以f3是(Bool -> Bool -> Bool)的别称)。
非常感谢。
编辑:我知道这不是最佳或可重用的代码,而且我知道将它变成无点会使它在可读性等方面变得更糟。为了澄清,我问的是如何将它变成点-free 因为我想了解更多关于 haskell 和 composition.
编辑 2:A really good SO answer on point-free
您可以利用函数是可应用的这一事实。
并这样写 withinBounds
:
withinBounds = pure (&&) <*> all (>= 0) <*> all (<= 8)
或者这样:
withinBounds = (&&) <$> all (>= 0) <*> all (<= 8)
您可以阅读 Applicatives here and here
有一个 class 基本上专用于无点组合† 具有多个“通道”:Arrow
。如果您决心让一切都变得毫无意义,那么这就是 IMO 的必经之路。丑陋的一点是你经常需要 uncurry 函数:
import Control.Arrow
withinBounds = all (>=0) &&& all (<=8) >>> uncurry (&&)
通过图表可以更好地理解其工作原理:
all (>=0) ────
╱ ╲
──── &&& >>> uncurry (&&) ───
╲ ╱
all (<=8) ────
†Arrow
适用于通用设置;不仅适用于 Hask-函数,而且适用于任何合适的类别。但它足以将其应用于函数。
注意 x<=8 当且仅当 8-x>=0,所以,仅使用序曲,我们可以写成
withinBounds :: [Int] -> Bool
withinBounds = all $ (all (>=0)) . (zipWith (+) [0,8]) . (zipWith (*) [1,-1]) . (replicate 2)
基本上我只是将 x
映射到 [x,x]
然后映射到 [x,8-x]
然后我要求这两个同时 >=0。
当然,正如评论中所指出的,您也可以制作a,b
参数,以便稍后重用。
希望这对您有所帮助。
围绕整个问题做一个结束-运行,我想我可能会这样写:
import Data.Ix
withinBounds = all (inRange (0, 8))
当然,这有点过分了,从那时起人们自然会问如何以无点方式实现inRange
。如果您绝对不能使用 inRange
,那么我将以这种方式内联实现它:
withinBounds = all (liftA2 (&&) (>=0) (<=8))
这使用 reader 应用程序为两个函数提供单个参数。 liftA2
是您请求的组合函数,但参数已翻转:
requested :: (a -> b) -> (a -> c) -> (b -> c -> d) -> (a -> d)
liftA2 :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)