无参风格和使用$符号

10

如何将$和点自由样式结合起来使用?

下面是一个清晰的实用函数示例:

times :: Int -> [a] -> [a]
times n xs = concat $ replicate n xs  

只是编写concat $ replicate会产生错误,同样你也不能编写concat . replicate,因为concat期望的是一个值而不是一个函数。

那么你如何将上述函数转换为点·自由形式?


https://dev59.com/J3E95IYBdhLWcg3wE56C - Josh Lee
5个回答

22

你可以使用这个组合符号:(冒号表示接下来跟着两个参数)

(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.) . (.)

它允许您去掉n

time = concat .: replicate

@dave4420 嗯,在我看来,.: 更具有记忆性。 - fuz
3
我个人更喜欢使用.*,这样下一个可以是.**.***等。无论哪种方式,我们都应该尝试将.:引入Haskell Prime的Prelude中,或至少引入基本库中。 - Dan Burton
fmap fmap fmap.: 的泛化吗? - nponeccop
@nponeccop 是的,但其中一个像(.),所以它更像是fmap . fmap。 - fuz

13

您可以轻松地使用以下方法编写几乎无参数版本:

times n  =  concat . replicate n

可以通过显式的柯里化和非柯里化实现完全无点的版本:

times  =  curry $ concat . uncurry replicate

在我看来,最后一个方案是不必要的复杂,因为它涉及了不必要的“柯里化”和“非柯里化”,请参考Josh和我的答案。 - fuz
@FUZxxl:我知道,我已经点赞了你的回答,我们现在都是+3。 - Fred Foo
3
对于几乎无点符号版本的做法,我表示赞同。虽然我推荐更广泛地采用“.:”,但对于更加复杂的情况,我也建议坚持“几乎”无点符号的写法。 - Dan Burton
3
嗨,@Dan,完全无点风格通常被认为太过无意义,至少在我看来是这样。 - fuz
1
适应你的技能和口味,使用点无风格。通过研究组合逻辑 - 终极点无演算,可以观察到一些基本缺陷。还要看看Tony Hoare关于基于函数的编程的工作。 - nponeccop

12

加入 Freenode 并询问 lambdabot :)

<jleedev> @pl \n xs -> concat $ replicate n xs
<lambdabot> (join .) . replicate

请注意,(foo . ) . bar 是 lambdabot 的典型模式,用于 foo .: bar,因为 .: 显然不被视为无意义的过程。 - Dan Burton

3
在Haskell中,函数合成是可结合的¹:
f . g . h == (f . g) . h == f . (g . h)

任何中缀运算符都只是一个好的旧函数:
2 + 3 == (+) 2 3
f 2 3 = 2 `f` 3

组合运算符也是一个二元函数,它是高阶函数之一,接受两个函数并返回一个函数:

(.) :: (b -> c) -> (a -> b) -> (a -> c)

因此,任何组合操作符都可以重写为以下形式:
f . g == (.) f g
f . g . h == (f . g) . h == ((.) f g) . h == (.) ((.) f g) h
f . g . h == f . (g . h) == f . ((.) g h) == (.) f ((.) g h)

Haskell 中的每个函数默认都可以进行部分应用,这是由于其默认支持柯里化。中缀运算符可以使用的方式非常简洁地进行部分应用:

(-) == (\x y -> x - y)
(2-) == (-) 2 == (\y -> 2 - y)
(-2) == flip (-) 2 == (\x -> (-) x 2) == (\x -> x - 2)
(2-) 3 == -1
(-2) 3 == 1

作为组合运算符只是一个普通的二元函数,你也可以在区块中使用它:
f . g == (.) f g == (f.) g == (.g) f

另一个有趣的二元运算符是$,它只是函数应用:

f x == f $ x
f x y z == (((f x) y) z) == f x y z
f(g(h x)) == f $ g $ h $ x == f . g . h $ x == (f . g . h) x

有了这个知识,我如何将concat $ replicate n xs转换为点式风格?
times n xs = concat $ replicate n xs
times n xs = concat $ (replicate n) xs
times n xs = concat $ replicate n $ xs
times n xs = concat . replicate n $ xs
times n    = concat . replicate n
times n    = (.) concat (replicate n)
times n    = (concat.) (replicate n) -- concat is 1st arg to (.)
times n    = (concat.) $ replicate n
times n    = (concat.) . replicate $ n
times      = (concat.) . replicate

¹Haskell基于范畴论。在范畴论中,一个范畴包括三个要素:一些对象、一些态射以及关于态射的复合的概念。每个态射连接一个源对象和一个目标对象,是单向的。范畴论要求态射的复合是结合的。在Haskell中使用的范畴称为Hask,其对象是类型,而其态射是函数。一个函数f :: Int -> String是将对象Int连接到对象String的一个态射。因此,范畴论要求Haskell中的函数复合是结合的。


1
通过扩展FUZxxl的答案,我们得到了:
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.).(.)

(.::) :: (d -> e) -> (a -> b -> c -> d) -> a -> b -> c -> e
(.::) = (.).(.:)

(.:::) :: (e -> f) -> (a -> b -> c -> d -> e) -> a -> b -> c -> d -> f
(.:::) = (.).(.::)

...

非常好。

额外奖励

(.:::) :: (e -> f) -> (a -> b -> c -> d -> e) -> a -> b -> c -> d -> f
(.:::) = (.:).(.:)

嗯...所以也许我们应该这样说


(.1) = .

(.2) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(.2) = (.1).(.1)

(.3) :: (d -> e) -> (a -> b -> c -> d) -> a -> b -> c -> e
(.3) = (.1).(.2)
-- alternatively, (.3) = (.2).(.1)

(.4) :: (e -> f) -> (a -> b -> c -> d -> e) -> a -> b -> c -> d -> f
(.4) = (.1).(.3)
-- alternative 1 -- (.4) = (.2).(.2)
-- alternative 2 -- (.4) = (.3).(.1)

更好的是。
我们也可以将其扩展到。
fmap2 :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
fmap2 f = fmap (fmap f)

fmap4 :: (Functor f, Functor g, Functor h, functro i) 
   => (a -> b) -> f (g (h (i a))) -> f (g (h (i b)))
fmap4 f = fmap2 (fmap2 f)

遵循相同模式。

最好能够将应用fmap(.)的时间参数化。然而,这些fmap(.)实际上在类型上是不同的。因此,唯一的方法是使用编译时计算,例如TemplateHaskell

对于日常使用,我只会简单建议

Prelude> ((.).(.)) concat replicate 5 [1,2]
[1,2,1,2,1,2,1,2,1,2]
Prelude> ((.).(.).(.)) (*10) foldr (+) 3 [2,1]
60

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