在Haskell中“重复使用”参数的技巧是什么?

21

偶尔我会遇到这样的问题,即我想表达“请将最后一个参数重复使用”,例如为了以 pointfree 的风格编写代码或者避免使用 lambda 表达式。例如:

sqr x = x * x

可以写成:

sqr = doubleArgs (*) where
   doubleArgs f x = f x x

或者考虑这个稍微复杂一点的函数(摘自这个问题):

ins x xs = zipWith (\ a b -> a ++ (x:b)) (inits xs) (tails xs)

如果有这样的一个函数,我可以写出这段代码的pointfree形式:

ins x = dup (zipWith (\ a b -> a ++ (x:b))) inits tails where
     dup f f1 f2 x = f (f1 x) (f2 x)

但是因为我在Hoogle中找不到类似doubleArgs或dup的东西,所以我猜我可能错过了一些技巧或成语。

3个回答

30

来自Control.Monad

join :: (Monad m) -> m (m a) -> m a
join m = m >>= id

instance Monad ((->) r) where
    return = const
    m >>= f = \x -> f (m x) x

扩展:

join :: (a -> a -> b) -> (a -> b)
join f = f >>= id
       = \x -> id (f x) x
       = \x -> f x x

好的,嗯,Control.Monad.join

哦,关于你的pointfree示例,你尝试使用应用符号(来自Control.Applicative)了吗:

ins x = zipWith (\a b -> a ++ (x:b)) <$> inits <*> tails

(我也不知道为什么人们如此喜欢使用a++(x:b)而不是a++[x]++b...它不会更快-内联程序会处理-后者更对称!那么好吧)


2
根据 pointfreedup 可以转换为 liftM2。我真的需要更好地掌握函数的单子实例。 - Antal Spector-Zabusky
2
谢谢你们两个提供了两种解决这类问题的方法。顺便说一下,我尝试了sqr = (*) <$> id <*> id,它也可以工作 :-) - Landei
“a ++ (x: b)” 比你的替代方案少了 3 个字符,也许这就是为什么有些人喜欢它的原因? - John L
1
如果我想强调对称性,我宁愿写concat [a,[x],b]而不是a ++ [x] ++ b - Landei
2
@Antal S-Z:实际上并不需要太多的东西——只是一个轻量级的 Reader monad,易于内联使用。第一个参数作为环境,fmapreturn 与环境无关,正如你所期望的那样,等等。我最喜欢的用法之一是使用条件组合器 (<?>),可以像这样使用 even <?> (\div` 2) <*> (+ 1),我认为这比 \n -> if even n then n div 2 else n + 1 更易读。(注意——liftM2 (\b t e -> if b then t else e)将从两个分支产生副作用,但这与Reader` 无关) - C. A. McCann
这个答案帮助我直观地理解了 join 函数的作用。谢谢。 - N3dst4

12
您所称的“doubleArgs”通常被称为dup - 它是W组合子(在《模拟知更鸟》中称为Warbler)-“基本复制器”。
您所称的“dup”实际上是“starling-prime”组合子。
Haskell有一个相当小的“组合子基础”,请参见Data.Function,再加上一些可应用和可单操作,通过 Applicative 和 Monad 的函数实例添加了更多“标准”组合子(来自Applicative的 <*> 是函数实例的S-starling组合子,liftA2和liftM2是starling-prime)。社区似乎对扩展Data.Function没有太多热情,因此虽然组合子很有趣,但实际上我已经开始倾向于在没有直接可用的组合子的情况下使用长代码。

2
哦,我找到了Haskell的“鸟运算符”:http://hackage.haskell.org/packages/archive/data-aviary/0.2.3/doc/html/Data-Aviary-Birds.html - Landei
2
@Landei - 我认为它们只是“仅供参考”,也就是说,我不建议在工作代码中依赖它们。我应该让 Cabal 描述更明确地表明它们只是“仅供参考”,但我还没有时间去做。 - stephen tetley
2
@Landei 所称的 dup 在 J 语言中也被称为 “动词分叉”,它通过简单地将运算符并置来表示,例如 (f g h) x 而不是 dup f g h x - C. A. McCann

9
这里有第二部分问题的另一个解决方案:箭头!
import Control.Arrow

ins x = inits &&& tails >>> second (map (x:)) >>> uncurry (zipWith (++))

&&&("fanout")将一个参数分配给两个函数,并返回结果对。 >>>("and then")反转函数应用顺序,允许从左到右有一系列操作。 second 仅适用于一对中的第二部分。当然,最后需要使用 uncurry 将该对输入期望两个参数的函数中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接