部分应用 Haskell 中的多个函数
Partially apply several functions in Haskell
假设,在 Haskell 中,我有一堆函数都依赖于相同的参数类型:
f :: Par -> a -> b
g :: Par -> b -> c
由于我正在编写更多仍依赖于此参数类型的函数,因此我可以执行类似
的操作
h :: Par -> a -> c
h par = myg . myf
where myf = f par
myg = g par
但是我一直不得不写这些 where
行。问题是:这可以避免吗?
[编辑:我试图提供一个最小的例子来说明问题,但显然这个例子太小而无法说明我想要的东西。在实际问题中h当然不仅仅是f和g的合成。所以这是一些实际的代码:
有函数
apply :: ChamberLattice -> ChLatword -> ChLatWord
reduce :: ChamberLattice -> ChLatWord -> ChLatWord
我正在定义一个函数
chaseTurn :: ChamberLattice -> Turn -> Parity -> ChLatWord -> ChLatWord
chaseTurn cl Straight _ xs = xs
chaseTurn cl t parity xs = if ((turn parity xs) == t)
then case myApply xs of
(y1:y2:ys) -> (y1:y2:(myChaseTurn t parity ys))
ys -> ys
else myReduce xs
where myApply = apply cl
myChaseTurn = chaseTurn cl
myReduce = reduce cl
]
(这道题本质上和
Grouping functions in Haskell
但我在那里使用了一些令人分心的不幸词。)
在Haskell中,所有函数都接受一个输入参数。但有时,应用函数的 return 值是一个新函数。那么,作为第一步,您可以通过将函数 f
和 g
:
的 return 值放在方括号中来使其更加明确
f :: Par -> (a -> b)
g :: Par -> (b -> c)
函数也是类型,因此我们可以任意决定将 a -> b
别名为 φ
(phi 而不是 f) 和 b -> c
到 γ
(gamma 而不是 g)。 (是的,当你 运行 没有字母时,你可以找到希腊字母表!)
这意味着您可以将函数视为具有类型
f :: Par -> φ
g :: Par -> γ
这些都是所谓的 reader monad 的自动实例,它也是一个(应用)函子。特别是,(->) Par
,或者,如果有帮助,Par ->
,是一个 Applicative
实例。这意味着您可以使用 pure
和 <*>
。
作为第一次尝试,你可以这样写
pure (\x y -> (x, y)) <*> f <*> g
为了简单地了解该组合的工作原理。可以说,该表达式的类型为 Par -> (φ, γ)
。该 lambda 表达式只是从 f
'container' 中获取 x
并从 g
'container' 中获取 y
,并将它们组合在一个元组中。元组的第一个元素的类型为 φ
,第二个元素的类型为 γ
.
插入 φ
和 γ
的定义,你得到类型 Par -> (a -> b, b -> c)
.
您想要组合这些函数,而不是将 return 值作为函数元组。您可以为此使用函数组合运算符 .
:
h = pure (\x y -> y . x) <*> f <*> g
请注意函数是从右到左组合的,因此 x
(a -> b
) 首先出现,然后是 y
(b -> c
)。
但是,您可以翻转 f
和 g
:
h = pure (\y x -> y . x) <*> g <*> f
然后可以将显式 lambda 表达式简化为:
h = pure (.) <*> g <*> f
最后,您可以使用中缀 <$>
运算符代替 pure (.) <*>
:
h = (.) <$> g <*> f
此函数的类型为 Par -> a -> c
。
您 h par = f par . g par
做了很多,par
东西开始变得混乱。
你不能做 h = f . g
,因为 par
参数也必须传递。
所以你想出了一个强大的组合运算符来为你做这件事:
-- (.) :: (b -> c) -> (a -> b) -> a -> c
(§) :: (par -> b -> c) -> (par -> a -> b) -> par -> a -> c
(§) f g par = f par . g par
现在您可以做到 h = f § g
。这个运算符可能是以前发明的。
顺便说一句,部分应用的函数是instances of Monad。这意味着您可以:
(§) f g par = (do { fpar <- f; gpar <- g; return (fpar . gpar) }) par
或者只是:
(§) f g = do { fpar <- f; gpar <- g; return (fpar . gpar) }
(这里,fpar
是 f
,隐式 par
已应用。monad 实例使 par
隐式。)
如果我们要参数化这个 do-block:
(§) f g = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.) f g
和eta-reduce参数:
(§) = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.)
然后在 Hoogle 上寻找类似于此 do 块的内容,您会发现 liftM2
:
(§) = liftM2 (.)
此时我们真的不需要给它起一个特殊的名字,因为 liftM2 (.)
已经很短了。
你已经发现了 Reader
monad 的用例,如果你可以稍微调整你的签名。如果你有
f :: a -> Par -> b
g :: b -> Par -> c
您可以将它们重新定义为
import Control.Monad.Trans.Reader
f :: a -> Reader Par b
g :: b -> Reader Par c
然后您可以使用普通的 Kleisli 组合运算符定义 h
。
import Control.Monad
h :: a -> Reader Par c
h = f >=> g
(就算不改签名,我觉得你也可以写成h = flip (flip f >=> flip g)
。)
这可以使用隐式参数(不是纯粹的 Haskell 而是 ghc 语言扩展,参见 https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters)来完成。
上面的代码就变成了
f :: (?p :: Par) => a -> b
g :: (?p :: Par) => b -> c
h :: (?p :: Par) => a -> c
h = g . f
假设,在 Haskell 中,我有一堆函数都依赖于相同的参数类型:
f :: Par -> a -> b
g :: Par -> b -> c
由于我正在编写更多仍依赖于此参数类型的函数,因此我可以执行类似
的操作h :: Par -> a -> c
h par = myg . myf
where myf = f par
myg = g par
但是我一直不得不写这些 where
行。问题是:这可以避免吗?
[编辑:我试图提供一个最小的例子来说明问题,但显然这个例子太小而无法说明我想要的东西。在实际问题中h当然不仅仅是f和g的合成。所以这是一些实际的代码:
有函数
apply :: ChamberLattice -> ChLatword -> ChLatWord
reduce :: ChamberLattice -> ChLatWord -> ChLatWord
我正在定义一个函数
chaseTurn :: ChamberLattice -> Turn -> Parity -> ChLatWord -> ChLatWord
chaseTurn cl Straight _ xs = xs
chaseTurn cl t parity xs = if ((turn parity xs) == t)
then case myApply xs of
(y1:y2:ys) -> (y1:y2:(myChaseTurn t parity ys))
ys -> ys
else myReduce xs
where myApply = apply cl
myChaseTurn = chaseTurn cl
myReduce = reduce cl
]
(这道题本质上和 Grouping functions in Haskell 但我在那里使用了一些令人分心的不幸词。)
在Haskell中,所有函数都接受一个输入参数。但有时,应用函数的 return 值是一个新函数。那么,作为第一步,您可以通过将函数 f
和 g
:
f :: Par -> (a -> b)
g :: Par -> (b -> c)
函数也是类型,因此我们可以任意决定将 a -> b
别名为 φ
(phi 而不是 f) 和 b -> c
到 γ
(gamma 而不是 g)。 (是的,当你 运行 没有字母时,你可以找到希腊字母表!)
这意味着您可以将函数视为具有类型
f :: Par -> φ
g :: Par -> γ
这些都是所谓的 reader monad 的自动实例,它也是一个(应用)函子。特别是,(->) Par
,或者,如果有帮助,Par ->
,是一个 Applicative
实例。这意味着您可以使用 pure
和 <*>
。
作为第一次尝试,你可以这样写
pure (\x y -> (x, y)) <*> f <*> g
为了简单地了解该组合的工作原理。可以说,该表达式的类型为 Par -> (φ, γ)
。该 lambda 表达式只是从 f
'container' 中获取 x
并从 g
'container' 中获取 y
,并将它们组合在一个元组中。元组的第一个元素的类型为 φ
,第二个元素的类型为 γ
.
插入 φ
和 γ
的定义,你得到类型 Par -> (a -> b, b -> c)
.
您想要组合这些函数,而不是将 return 值作为函数元组。您可以为此使用函数组合运算符 .
:
h = pure (\x y -> y . x) <*> f <*> g
请注意函数是从右到左组合的,因此 x
(a -> b
) 首先出现,然后是 y
(b -> c
)。
但是,您可以翻转 f
和 g
:
h = pure (\y x -> y . x) <*> g <*> f
然后可以将显式 lambda 表达式简化为:
h = pure (.) <*> g <*> f
最后,您可以使用中缀 <$>
运算符代替 pure (.) <*>
:
h = (.) <$> g <*> f
此函数的类型为 Par -> a -> c
。
您 h par = f par . g par
做了很多,par
东西开始变得混乱。
你不能做 h = f . g
,因为 par
参数也必须传递。
所以你想出了一个强大的组合运算符来为你做这件事:
-- (.) :: (b -> c) -> (a -> b) -> a -> c
(§) :: (par -> b -> c) -> (par -> a -> b) -> par -> a -> c
(§) f g par = f par . g par
现在您可以做到 h = f § g
。这个运算符可能是以前发明的。
顺便说一句,部分应用的函数是instances of Monad。这意味着您可以:
(§) f g par = (do { fpar <- f; gpar <- g; return (fpar . gpar) }) par
或者只是:
(§) f g = do { fpar <- f; gpar <- g; return (fpar . gpar) }
(这里,fpar
是 f
,隐式 par
已应用。monad 实例使 par
隐式。)
如果我们要参数化这个 do-block:
(§) f g = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.) f g
和eta-reduce参数:
(§) = ( \f m1 m2 -> do { x1 <- m1; x2 <- m2; return (f x1 x2) } ) (.)
然后在 Hoogle 上寻找类似于此 do 块的内容,您会发现 liftM2
:
(§) = liftM2 (.)
此时我们真的不需要给它起一个特殊的名字,因为 liftM2 (.)
已经很短了。
你已经发现了 Reader
monad 的用例,如果你可以稍微调整你的签名。如果你有
f :: a -> Par -> b
g :: b -> Par -> c
您可以将它们重新定义为
import Control.Monad.Trans.Reader
f :: a -> Reader Par b
g :: b -> Reader Par c
然后您可以使用普通的 Kleisli 组合运算符定义 h
。
import Control.Monad
h :: a -> Reader Par c
h = f >=> g
(就算不改签名,我觉得你也可以写成h = flip (flip f >=> flip g)
。)
这可以使用隐式参数(不是纯粹的 Haskell 而是 ghc 语言扩展,参见 https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#implicit-parameters)来完成。
上面的代码就变成了
f :: (?p :: Par) => a -> b
g :: (?p :: Par) => b -> c
h :: (?p :: Par) => a -> c
h = g . f