10

如何结合 using$和 point-free 风格?

一个明显的例子是以下效用函数:

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

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

那么如何将上述函数变成无点样式呢?

4

5 回答 5

22

您可以使用此组合器:(冒号暗示后面有两个参数)

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

它可以让你摆脱n

time = concat .: replicate
于 2011-11-16T16:47:15.307 回答
13

您可以轻松地编写一个几乎没有点的版本

times n  =  concat . replicate n

使用显式 curry 和 uncurry 可以实现完全无点版本:

times  =  curry $ concat . uncurry replicate
于 2011-11-16T16:38:41.773 回答
12

上 freenode 并询问 lambdabot ;)

<jleedev> @pl \n xs -> concat $ replicate n xs
<lambdabot> (join .) . replicate
于 2011-11-16T16:48:10.713 回答
2

在 Haskell 中,函数组合是关联的¹:

f . g . h == (f . g) . h == f . (g . h)

任何中缀运算符都只是一个很好的功能:

2 + 3 == (+) 2 3
f 2 3 = 2 `f` 3

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

(.) :: (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 的函数组合是关联的。

于 2015-02-26T03:03:20.183 回答
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有应用或(.)参数化的时间会更好。但是,那些fmapor (.)s 实际上在类型上有所不同。因此,唯一的方法是使用编译时间计算,例如TemplateHaskell.

对于日常使用,我只是建议

Prelude> ((.).(.)) concat replicate 5 [1,2]
[1,2,1,2,1,2,1,2,1,2]
Prelude> ((.).(.).(.)) (*10) foldr (+) 3 [2,1]
60
于 2015-02-26T09:59:00.403 回答