(.)
有两个函数,它们使用一个值并返回一个值:
(.) :: (b -> c) -> (a -> b) -> a -> c
由于(.)
需要两个参数,我觉得(.).(.)
应该无效,但这很好:
(.).(.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
这里发生了什么?我意识到这个问题措辞严厉......所有的功能都只是因为讨论而采取了一个论点。也许更好的方式是说这些类型不匹配。
答案 0 :(得分:29)
让我们首先使用typechecker进行机械校样。我将在后面描述一种直观的思考方式。
我想将(.)
应用于(.)
,然后我会将(.)
应用于结果。第一个应用程序帮助我们定义一些变量的等价。
((.) :: (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'')
这是关键:我们想let b = (a'' -> b'') -> a'' -> c''
,但我们已经定义了b
,所以我们必须尝试统一 ---以匹配我们的两个定义为我们能做到最好。幸运的是,他们做匹配
UNIFY b = (b' -> c') =:= (a'' -> b'') -> a'' -> c''
which implies
b' = a'' -> b''
c' = a'' -> c''
通过这些定义/统一,我们可以继续申请
((.) (.) (.) :: (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)
它将一个功能“提升”为Functor
。我们可以反复申请
fmap.fmap.fmap :: (Functor f, Functor g, Functor h)
=> (a -> b) -> (f (g (h a)) -> f (g (h b)))
允许我们将功能提升到Functors
的更深层次。
事实证明,数据类型(r ->)
是Functor
。
instance Functor ((->) r) where
fmap = (.)
应该看起来很熟悉。这意味着fmap.fmap
会转换为(.).(.)
。因此,(.).(.)
只是让我们转换(r ->)
Functor
更深层次的参数类型。 (r ->)
Functor
实际上是Reader
Monad
,因此分层Reader
就像拥有多种独立的全局不可变状态。
或者喜欢有多个输入参数不受fmap
影响。有点像在(> 1)arity函数的“只是结果”上组成一个新的延续函数。
最后值得注意的是,如果你觉得这些东西很有意思,那就形成了deriving the Lenses in Control.Lens背后的核心直觉。
答案 1 :(得分:15)
让我们暂时忽略类型,然后使用lambda演算。
Desugar中缀符号:
(.) (.) (.)
埃塔展开:
(\ a b -> (.) a b) (\ c d -> (.) c d) (\ e f -> (.) e f)
内联(.)
的定义:
(\ a b x -> a (b x)) (\ c d y -> c (d y)) (\ e f z -> e (f z))
替换a
:
(\ b x -> (\ c d y -> c (d y)) (b x)) (\ e f z -> e (f z))
替换b
:
(\ x -> (\ c d y -> c (d y)) ((\ e f z -> e (f z)) x))
替换e
:
(\ x -> (\ c d y -> c (d y)) (\ f z -> x (f z)))
替换c
:
(\ x -> (\ d y -> (\ f z -> x (f z)) (d y)))
替换f
:
(\ x -> (\ d y -> (\ z -> x (d y z))))
Resugar lambda表示法:
\ x d y z -> x (d y z)
如果你问GHCi,你会发现它有预期的类型。为什么?因为函数箭头是右关联的以支持currying:类型(b -> c) -> (a -> b) -> a -> c
实际上意味着(b -> c) -> ((a -> b) -> (a -> c))
。同时,类型变量b
可以代表任何类型,包括函数类型。看到连接?
答案 2 :(得分:7)
这是一个巧妙的案例,我认为首先掌握更一般的案例更简单,然后考虑具体案例。那么让我们考虑一下仿函数。我们知道仿函数提供了一种在结构上映射函数的方法 -
class Functor f where
fmap :: (a -> b) -> f a -> f b
但是如果我们有两层仿函数呢?例如,列表列表?在这种情况下,我们可以使用两层fmap
>>> let xs = [[1,2,3], [4,5,6]]
>>> fmap (fmap (+10)) xs
[[11,12,13],[14,15,16]]
但模式f (g x)
与(f . g) x
完全相同,所以我们可以编写
>>> (fmap . fmap) (+10) xs
[[11,12,13],[14,15,16]]
fmap . fmap
的类型是什么?
>>> :t fmap.fmap
:: (Functor g, Functor f) => (a -> b) -> f (g a) -> f (g b)
我们看到它根据我们的需要映射到两层仿函数。但现在请记住,(->) r
是一个仿函数(来自r
的函数类型,您可能更喜欢将其视为(r ->)
),其函数实例为
instance Functor ((->) r) where
fmap f g = f . g
对于一个函数,fmap
只是函数组合!当我们编写两个fmap
时,我们会映射函数仿函数的两个级别。我们最初的某些类型为(->) s ((->) r a)
,相当于s -> r -> a
,我们最终得到s -> r -> b
类型的内容,因此(.).(.)
的类型必须为
(.).(.) :: (a -> b) -> (s -> r -> a) -> (s -> r -> b)
获取其第一个函数,并使用它来转换第二个(双参数)函数的输出。例如,函数((.).(.)) show (+)
是两个参数的函数,首先将其参数相加,然后使用String
将结果转换为show
:
>>> ((.).(.)) show (+) 11 22
"33"
然后考虑fmap
的较长链,例如
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)]"
将值True
插入到带有键1
的空地图中,然后将输出转换为带有show
的字符串。
这些功能通常很有用,因此您有时会将它们定义为
(.:) :: (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)
可能有助于在某种程度上揭开(.).(.)
的神秘面纱。
答案 3 :(得分:6)
以下是同一现象的一个更简单的例子:
id :: a -> a
id x = x
id的类型表明id应该带一个参数。事实上,我们可以用一个论点来称呼它:
> id "hello"
"hello"
但事实证明我们也可以用两个参数来称呼它:
> id not True
False
甚至:
> 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 id "hello"
和(.) . (.)
。
答案 4 :(得分:4)
你是对的,(.)
只有两个参数。你似乎只对haskell的语法感到困惑。在表达式(.).(.)
中,它实际上是中间的点,它将另外两个点作为参数,就像表达式100 + 200
一样,可以写成(+) 100 200
。
(.).(.) === (number the dots)
(1.)2.(3.) === (rewrite using just syntax rules)
(2.)(1.)(3.) === (unnumber and put spaces)
(.) (.) (.) ===
从(.) (.) (.)
开始,第一个(.)
将第二个(.)
和第三个(.)
作为参数,这一点应该更加明确。
答案 5 :(得分:3)
是的,这是由于currying。 (.)
因为Haskell中的所有函数只接受一个参数。你正在编写的是对每个组成的(.)
的第一次部分调用,它采用了它的第一个参数(组合的第一个函数)。
答案 6 :(得分:1)
(首先阅读my answer函数组合,$运算符和无点样式。)
想象一下,你有一个简单的功能:它会累加2个数字,然后否定结果。我们称之为foo
:
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)
。我们可以进一步改造它吗?是的,我们可以:
(.) ((.) negate)
(.) . (.) $ negate -- because f (f x) is the same as (f . f) x
(.)(.)(.) $ negate
((.)(.)(.)) negate
(.).(.)
相当于(.)(.)(.)
,因为在第一个表达式中,中间的点可以在前缀位置移动并用括号括起来,这会产生第二个表达式。
现在我们可以重写foo
函数:
foo = ((.).(.)) negate (+)
foo = ((.)(.)(.)) negate (+) -- same as previous one
foo = negate .: (+)
where (.:) = (.).(.)
现在您知道(.).(.)
等同于(\f g x y -> f (g x y))
:
(\f g x y -> f (g x y)) negate (+) 2 3 -- returns -5
((.).(.)) negate (+) 2 3 -- returns -5