Haskell 组合功能组合:(.)是如何工作的?

Haskell 组合功能组合:(.)是如何工作的?,haskell,currying,pointfree,Haskell,Currying,Pointfree,()接受两个函数,它们接受一个值并返回一个值: (.) :: (b -> c) -> (a -> b) -> a -> c 由于(.)需要两个参数,我觉得(.)应该是无效的,但这很好: (.).(.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c 这是怎么回事?我意识到这个问题的措辞很糟糕……由于currying,所有函数实际上只接受一个参数。也许更好的说法是类型不匹配。你说

()
接受两个函数,它们接受一个值并返回一个值:

(.) :: (b -> c) -> (a -> b) -> a -> c
由于
(.)
需要两个参数,我觉得
(.)应该是无效的,但这很好:

(.).(.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
这是怎么回事?我意识到这个问题的措辞很糟糕……由于currying,所有函数实际上只接受一个参数。也许更好的说法是类型不匹配。

你说得对,
(。
只接受两个参数。您似乎对haskell的语法感到困惑。在表达式<代码>(.)./代码>中,实际上是中间的点,把另外两个点作为参数,就像表达式“代码> 100 + 200 < /COD>”,它可以被写为<代码>(+)100 200 < /C>
(.).(.) === (number the dots)
(1.)2.(3.) === (rewrite using just syntax rules)
(2.)(1.)(3.) === (unnumber and put spaces)
(.) (.) (.) ===

()(
)中应该更清楚地看到,第一个
()
将第二个
()
和第三个
()
作为它的参数。

让我们首先播放机械校对。之后我将描述一种直观的思考方式

我想将
(..
应用于
(..
),然后将
(..
应用于结果。第一个应用程序帮助我们定义一些等价的变量

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

let b = (b' -> c') 
    c = (a' -> b') -> a' -> c'

((.) (.) :: (a -> b) -> a -> c) 
      ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'')
然后我们开始第二步,但很快就被卡住了

let a = (b'' -> c'')
这是关键:我们想
让b=(a'->b'->a'->c'
,但我们已经定义了
b
,所以我们必须尝试统一-,以便尽可能地匹配我们的两个定义。幸运的是,他们确实匹配

通过这些定义/统一,我们可以继续应用

((.) (.) (.) :: (b'' -> c'') -> (a' -> b') -> (a' -> c'))
然后扩展

((.) (.) (.) :: (b'' -> c'') -> (a' -> a'' -> b'') -> (a' -> a'' -> c''))
把它清理干净

substitute b'' -> b
           c'' -> c
           a'  -> a
           a'' -> a1

(.).(.) :: (b -> c) -> (a -> a1 -> b) -> (a -> a1 -> c)
老实说,这有点违反直觉


这是直觉。首先看一下
fmap

fmap :: (a -> b) -> (f a -> f b)
>>> let xs = [[1,2,3], [4,5,6]]
>>> fmap (fmap (+10)) xs
[[11,12,13],[14,15,16]]
它将函数“提升”为
函子。我们可以反复应用它

fmap.fmap.fmap :: (Functor f, Functor g, Functor h) 
               => (a -> b) -> (f (g (h a)) -> f (g (h b)))
允许我们将函数提升到越来越深的
函子层中

事实证明,数据类型
(r->)
是一个
函子

instance Functor ((->) r) where
   fmap = (.)
看起来应该很熟悉。这意味着
fmap.fmap
转换为
(.)。(
)。因此,
()。(
只是让我们转换
(r->)
函子的更深层次的参数类型。
(r->)
函子
实际上是
读卡器
单子
,因此分层的
读卡器
就像拥有多种独立的全局不可变状态

或者类似于具有多个不受
fmap
ing影响的输入参数。有点像在(>1)算术函数的“结果”上构造一个新的连续函数



最后值得注意的是,如果你认为这些东西很有趣,那么它就形成了背后的核心直觉。

是的,这是因为咖喱<代码>(。
,因为Haskell中的所有函数只接受一个参数。您正在编写的是对每个已编写的
(.)
的第一个部分调用,该调用采用其第一个参数(该组合的第一个函数)。

让我们暂时忽略类型,只使用lambda演算

  • Desugar中缀符号:
    ()()

  • Eta扩展:
    (\ab->(.)ab)(\cd->(.)cd)(\ef->(.)ef)

  • 内联
    ()

    (\abx->a(bx))(\cdy->c(dy))(\efz->e(fz))

  • 替换
    a

    (\bx->(\cdy->c(dy))(bx))(\efz->e(fz))

  • 替换
    b

    (\x->(\cdy->c(dy))(\efz->e(fz))x))

  • 替换
    e

    (\x->(\cdy->c(dy))(\fz->x(fz)))

  • 替换
    c

    (\x->(\dy->(\fz->x(fz))(dy)))

  • 替换
    f

    (\x->(\dy->(\z->x(dyz)))

  • Resugar lambda符号:
    \xdyz->x(dyz)


如果你问GHCi,你会发现这是预期的类型。为什么?因为函数箭头是右关联的以支持货币:类型
(b->c)->(a->b)->a->c
实际上意味着
(b->c)->(a->b)->(a->c))
。同时,类型变量
b
可以代表任何类型,包括函数类型。参见连接?

这里是相同现象的一个简单示例:

id :: a -> a
id x = x
id的类型表示id应该接受一个参数。事实上,我们可以用一个论点来称呼它:

> id "hello" 
"hello"
但事实证明,我们也可以用两个参数来称呼它:

> id not True
False
(.).(.).(.) :: (a -> b) -> (r -> s -> t -> a) -> (r -> s -> t -> b)
甚至:

> id id "hello"
"hello"
发生了什么事?理解
id not True
的关键是首先查看
id not
。显然,这是允许的,因为它将id应用于一个参数。
not
的类型是
Bool->Bool
,因此我们知道id的类型中的
a
应该是
Bool->Bool
,因此我们知道此id的出现具有以下类型:

id :: (Bool -> Bool) -> (Bool -> Bool)
或者,用更少的括号:

id :: (Bool -> Bool) -> Bool -> Bool
因此id的出现实际上需要两个参数


同样的推理也适用于
id“hello”
()。()

这是一个很好的案例,我认为先抓住更一般的案例,然后再考虑具体案例更简单。让我们来考虑函子。我们知道函子提供了一种在结构上映射函数的方法--

但是如果我们有两层函子呢?例如,列表的列表?在这种情况下,我们可以使用两层
fmap

fmap :: (a -> b) -> (f a -> f b)
>>> let xs = [[1,2,3], [4,5,6]]
>>> fmap (fmap (+10)) xs
[[11,12,13],[14,15,16]]
但是模式
f(gx)
(f.g)x
完全相同,因此我们可以编写

>>> (fmap . fmap) (+10) xs
[[11,12,13],[14,15,16]]
fmap的类型是什么
instance Functor ((->) r) where
  fmap f g = f . g
(.).(.) :: (a -> b) -> (s -> r -> a) -> (s -> r -> b)
>>> ((.).(.)) show (+) 11 22
"33"
fmap.fmap.fmap ::
  (Functor f, Functor g, Functor h) => (a -> b) -> f (g (h a)) -> f (g (h b))
(.).(.).(.) :: (a -> b) -> (r -> s -> t -> a) -> (r -> s -> t -> b)
>>> import Data.Map
>>> ((.).(.).(.)) show insert 1 True empty
"fromList [(1,True)]"
(.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b)
(.:) = (.).(.)
>>> let f = show .: (+)
>>> f 10 20
"30"
(.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b)
(f .: g) x y = f (g x y)
foo a b = negate (a + b)
foo a b = negate $ a + b
foo a b = negate $ (+) a b
foo a b = negate $ (+) a $ b
foo a b = negate . (+) a $ b
foo a   = negate . (+) a -- f x = g x is equivalent to f = g
foo a   = (.) negate ((+) a) -- any infix operator is just a function
foo a   = (negate.) ((+) a) -- (2+) is the same as ((+) 2)
foo a   = (negate.) $ (+) a
foo a   = (negate.) . (+) $ a
foo     = (negate.) . (+)
foo     = ((.) negate) . (+)
foo     = (.) ((.) negate) (+) -- move dot in the middle in prefix position
foo     = ((.) ((.) negate)) (+) -- add extra parentheses
(.) ((.) negate)
(.) . (.) $ negate -- because f (f x) is the same as (f . f) x
(.)(.)(.) $ negate
((.)(.)(.)) negate
foo = ((.).(.)) negate (+)
foo = ((.)(.)(.)) negate (+) -- same as previous one
foo = negate .: (+)
  where (.:) = (.).(.)
(\f g x y -> f (g x y)) negate (+) 2 3 -- returns -5
((.).(.)) negate (+) 2 3 -- returns -5