我怎么能理解“(。)。(。)”?

时间:2013-02-22 17:23:03

标签: haskell functional-programming pointfree

我相信我对Functors了解fmap . fmap,但在功能方面,它已经伤害了我好几个月了。

我已经看到您可以将(.)的定义应用于(.) . (.),但我已经忘记了如何做到这一点。
当我自己尝试时,它总是错误的:

(.) 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

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

5 个答案:

答案 0 :(得分:35)

提出(.) . (.)实际上非常简单,这就是它所做的直觉背后的理解。

将表达式重写为“管道”样式计算时,

(.)会让你走得很远(想想shell中的|)。但是,一旦你尝试编写一个带有多个参数且只带一个函数的函数,就会变得很尴尬。例如,让我们定义concatMap

concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)

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

concatMap f = concat . map f

然而,摆脱f没有“好”的方法。这是因为map有两个参数,我们想在其最终结果上应用concat

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

concatMap f = (.) concat (map f)
concatMap f = (.) concat . map $ f
concatMap = (.) concat . map
concatMap = (concat .) . map

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

-- .: 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

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

(.:) = \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

现在,有趣的部分来了。这是另一个通常有助于陷入困境的免费技巧:我们将.重写为其前缀形式并尝试从那里继续。

     = \f g x   -> (.) f (g x)
     = \f g x   -> (.) f . g $ x
     = \f g     -> (.) f . g
     = \f g     -> (.) ((.) f) g
     = \f       -> (.) ((.) f)
     = \f       -> (.) . (.) $ f
     =             (.) . (.)

至于直觉,你应该阅读这个very nice article。我将解释关于(.)

的部分

让我们再考虑一下我们的组合器应该做什么:它应该f应用于g结果结果(我是在故意之前一直在部件中使用最终结果,它就是你完全应用时得到的 - 模数统一类型变量与另一种函数类型 - g函数,结果< / em>这里只是某些g x的应用x

f应用于g结果对我们意味着什么?好吧,一旦我们将g应用于某个值,我们将获取结果并将f应用于此值。听起来很熟悉:这就是(.)的作用。

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

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

(.:) = result . result -- the result of result

答案 1 :(得分:18)

您还可以理解fmap . fmap

如果您有两个Functor s foobar,那么

fmap . fmap :: (a -> b) -> foo (bar a) -> foo (bar b)

fmap . fmap接受一个函数并为两个Functor的组合生成一个诱导函数。

现在,对于任何类型t(->) t都是Functor,而fmap的{​​{1}}是Functor

(.) (.) . (.)fmap . fmapFunctor(->) 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)

它“组合”一个函数f :: a -> b,其函数包含两个参数g :: s -> t -> a

((.) . (.)) f g = \x y -> f (g x y)

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

(.) . (.) . (.) :: (a -> b) -> (s -> t -> u -> a) -> (s -> t -> u -> b)

答案 2 :(得分:4)

当您引入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

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

(在ghci中进行扩展最简单,您可以使用:t expression检查每一步)

修改

更深层次的直觉是:

(.)简单定义为

\f g -> \x -> f (g x)

我们可以简化为

\f g x -> f (g x)

因此,当你提供两个参数时,它是curry并仍然需要另一个参数来解决。 每次使用带有2个参数的(.)时,都会为另一个参数创建“需要”。

(.) . (.)当然只是(.) (.) (.),所以让我们展开它:

(\f0 g0 x0 -> f0 (g0 x0)) (\f1 g1 x1 -> f1 (g1 x1)) (\f2 g2 x2 -> f2 (g2 x2))

我们可以f0g0进行beta-reduce(但我们没有x0!):

\x0 -> (\f1 g1 x1 -> f1 (g1 x1)) ((\f2 g2 x2 -> f2 (g2 x2)) x0) 

替换f1 ...

的第二个表达式
\x0 -> \g1 x1 -> ((\f2 g2 x2 -> f2 (g2 x2)) x0) (g1 x1) 

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

\x0 -> \g1 x1 -> (\g2 x2 -> x0 (g2 x2)) (g1 x1) 

这开始显得正常...... 让我们上一次beta-reduce(在g2上):

\x0 -> \g1 x1 -> (\x2 -> x0 ((g1 x1) x2))

所以我们只剩下

\x0 g1 x1 x2 -> x0 ((g1 x1) x2)

,其中的参数仍然有序。

答案 3 :(得分:3)

所以,这是我在进行稍微增量扩展时得到的结果

(.) f g   = \x -> f (g x)
(.) . g   = \x -> (.) (g x)
          = \x -> \y -> (.) (g x) y
          = \x -> \y -> \z -> (g x) (y z)
          = \x y z -> (g x) (y z)
(.) . (.) = \x y z -> ((.) x) (y z)
          = \x y z -> \k -> x (y z k)
          = \x y z k -> x (y z k)

其中,根据ghci具有正确的类型

Prelude> :t (.) . (.)
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
Prelude> :t \x y z k -> x (y z k)
\x y z k -> x (y z k)
  :: (t1 -> t) -> (t2 -> t3 -> t1) -> t2 -> t3 -> t
Prelude> 

虽然我不知道这个组合器的起源,但可能就是这样 开发用于组合逻辑,在那里你严格使用组合器, 所以你不能使用更方便的lambda表达式来定义东西。可能有 有些直觉可以解决这些问题,但我还没有找到它。 最有可能的是,如果你必须这么做,你会发展出一定程度的直觉。

答案 4 :(得分:3)

最简单的方法是编写方程式combinators-style,而不是lambda表达式:a b c = (\x -> ... body ...)等同于a b c x = ... body ...,反之亦然,前提是{{ 1}}不会出现在x之间。所以,

{a,b,c}

如果给定-- _B = (.) _B f g x = f (g x) _B _B _B f g x y = _B (_B f) g x y = (_B f) (g x) y = _B f (g x) y = f ((g x) y) = f (g x y) ,您想要将其转换为into a combinatory form(摆脱所有括号和变量重复),就会发现这一点。然后应用与组合器定义相对应的模式,希望向后跟踪此派生。但这远不如机械/自动。