我怎么能理解"(.).(.)"?

Som*_*ame 33 haskell functional-programming pointfree

我相信我对fmap . fmapFunctors 了解,但是在功能方面它已经让我头疼了好几个月了.

我已经看到你可以只应用(.)to 的定义(.) . (.),但我忘了怎么做.
当我自己尝试时,它总是错误的:

(.) f g = \x -> f (g x)
(.) (.) (.) = \x -> (.) ((.) x)
\x f -> (.) ((.) x) f
\x f y  -> (((.)(f y)) x)
\x f y g-> (((.)(f y) g) x)
\x f y g-> ((f (g y)) x)
\x f y g-> ((f (g y)) x):: t2 -> (t1 -> t2 -> t) -> t3 -> (t3 -> t1) -> t
Run Code Online (Sandbox Code Playgroud)

如果"只是应用定义"是唯一的方法,那么有人怎么想出来(.) . (.)
我必须要有一些更深刻的理解或直觉.

Vit*_*tus 36

提出(.) . (.)实际上是非常简单的,这是它背后的直觉,这是非常棘手的理解.

(.)在将表达式重写为"管道"样式计算时(考虑|在shell中),可以帮到你.但是,一旦你尝试编写一个带有多个参数且只带一个函数的函数,就会变得很尴尬.作为一个例子,我们有一个定义concatMap:

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)
Run Code Online (Sandbox Code Playgroud)

摆脱xs只是一个标准的操作:

concatMap f = concat . map f
Run Code Online (Sandbox Code Playgroud)

然而,没有"好"的方式摆脱f.这是由事实造成的,它map需要两个参数,我们想要应用concat它的最终结果.

你当然可以应用一些免费的技巧,只需(.):

concatMap f = (.) concat (map f)
concatMap f = (.) concat . map $ f
concatMap = (.) concat . map
concatMap = (concat .) . map
Run Code Online (Sandbox Code Playgroud)

但是,这段代码的可读性大部分已经消失.相反,我们引入了一个新的组合器,它完全符合我们的需要:将第二个函数应用于第一个函数的最终结果.

-- .: is fairly standard name for this combinator
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) x y = f (g x y)

concatMap = concat .: map
Run Code Online (Sandbox Code Playgroud)

好的,这就是动力.让我们来点免费业务.

(.:) = \f g x y -> f (g x y)
     = \f g x y -> f ((g x) y)
     = \f g x y -> f . g x $ y
     = \f g x   -> f . g x
Run Code Online (Sandbox Code Playgroud)

现在,这里有趣的部分.这是另一个无意识的技巧,当你遇到困难时通常会有所帮助:我们重写.为它的前缀形式并尝试从那里继续.

     = \f g x   -> (.) f (g x)
     = \f g x   -> (.) f . g $ x
     = \f g     -> (.) f . g
     = \f g     -> (.) ((.) f) g
     = \f       -> (.) ((.) f)
     = \f       -> (.) . (.) $ f
     =             (.) . (.)
Run Code Online (Sandbox Code Playgroud)

至于直觉,你应该阅读这篇非常好的文章.我会解释一下这个部分(.):

让我们再想想我们的组合子应该做的:它应该适用f结果结果g(我一直在使用的最终结果在部分之前故意的,真的是你得到什么,当你完全应用-与另一模统一类型变量函数类型 - g函数,这里的结果只是g x一些应用程序x).

这是什么意思为我们申请f结果g?好吧,一旦我们申请g了一些价值,我们就会拿出结果并申请f.听起来很熟悉:那是什么(.).

result :: (b -> c) -> ((a -> b) -> (a -> c))
result = (.)
Run Code Online (Sandbox Code Playgroud)

现在,事实证明,这些组合器的组成(我们单词)只是一个功能组合,即:

(.:) = result . result -- the result of result
Run Code Online (Sandbox Code Playgroud)


Dan*_*her 19

您也可以使用您的理解fmap . fmap.

如果你有两个Functor小号foobar,然后

fmap . fmap :: (a -> b) -> foo (bar a) -> foo (bar b)
Run Code Online (Sandbox Code Playgroud)

fmap . fmap取得一个功能,并产生两个组成的诱导功能Functor.

现在,对于任何类型的t,(->) tFunctorfmapFunctor(.).

所以,(.) . (.)fmap . fmap对其中两个案件Functors为(->) s(->) t,因而

(.) . (.) :: (a -> b) -> ((->) s) ((->) t a) -> ((->) s) ((->) t b)
          =  (a -> b) -> (s -> (t -> a))     -> (s -> (t -> b))
          =  (a -> b) -> (s -> t -> a)       -> (s -> t -> b)
Run Code Online (Sandbox Code Playgroud)

它"组合"一个f :: a -> b具有两个参数的函数g :: s -> t -> a,

((.) . (.)) f g = \x y -> f (g x y)
Run Code Online (Sandbox Code Playgroud)

这种观点也清楚地说明了这种模式以及如何扩展到需要更多论证的函数,

(.) . (.) . (.) :: (a -> b) -> (s -> t -> u -> a) -> (s -> t -> u -> b)
Run Code Online (Sandbox Code Playgroud)

等等


ami*_*dfv 5

介绍时,您的解决方案有所不同y。它应该是

\x f y -> ((.) ((.) x) f) y     :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) ((.) x) f) y z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) x (f y)) z     :: (c -> d) -> (a -> b -> c) -> a -> b -> d
-- Or alternately:
\x f y z -> (x . f y) z         :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> (x (f y z))         :: (c -> d) -> (a -> b -> c) -> a -> b -> d
Run Code Online (Sandbox Code Playgroud)

匹配原始类型签名: (.) . (.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d

(最简单的方法是在ghci中进行扩展,您可以在其中检查每个步骤:t expression

编辑:

更直观的理解是:

(.) 简单定义为

\f g -> \x -> f (g x)
Run Code Online (Sandbox Code Playgroud)

我们可以简化为

\f g x -> f (g x)
Run Code Online (Sandbox Code Playgroud)

因此,当您为它提供两个参数时,它是咖喱状的,仍然需要另一个参数来解决。每次(.)与2个参数一起使用时,都会为另一个参数创建一个“需求”。

(.) . (.)当然是(.) (.) (.),所以让我们扩展一下:

(\f0 g0 x0 -> f0 (g0 x0)) (\f1 g1 x1 -> f1 (g1 x1)) (\f2 g2 x2 -> f2 (g2 x2))
Run Code Online (Sandbox Code Playgroud)

我们可以在f0和上进行beta减少g0(但我们没有x0!):

\x0 -> (\f1 g1 x1 -> f1 (g1 x1)) ((\f2 g2 x2 -> f2 (g2 x2)) x0) 
Run Code Online (Sandbox Code Playgroud)

将第二个表达式替换为f1...

\x0 -> \g1 x1 -> ((\f2 g2 x2 -> f2 (g2 x2)) x0) (g1 x1) 
Run Code Online (Sandbox Code Playgroud)

现在它“后退”!(在上减少beta f2):
这是一个有趣的步骤- x0代替了f2-这意味着x可能是数据的而是一个函数。
就是(.) . (.)提供额外参数的“需求”。

\x0 -> \g1 x1 -> (\g2 x2 -> x0 (g2 x2)) (g1 x1) 
Run Code Online (Sandbox Code Playgroud)

这开始看起来很正常...让我们最后一次Beta测试(在上g2):

\x0 -> \g1 x1 -> (\x2 -> x0 ((g1 x1) x2))
Run Code Online (Sandbox Code Playgroud)

所以我们只剩下

\x0 g1 x1 x2 -> x0 ((g1 x1) x2)
Run Code Online (Sandbox Code Playgroud)

,其中的参数仍然顺序正确。