Haskell 美元与()

Haskell 美元与(),haskell,functional-programming,Haskell,Functional Programming,我开始学习Haskell,遇到了一个我无法理解的问题。我有一个从键值列表(from)中查找值的方法: 我尝试了一下,并决定通过以下方式摆脱$sign: let findKey key xs = snd . head . filter (\(k,v) -> key == k) ( xs ) 然而,它甚至不解析(过滤器应用于太多的参数错误)。我读过$sign被用来简单地替换括号,我不明白为什么这个简单的代码更改是不好的。有人能给我解释一下吗?操作员的优先级最低,所以 snd . head .

我开始学习Haskell,遇到了一个我无法理解的问题。我有一个从键值列表(from)中查找值的方法:

我尝试了一下,并决定通过以下方式摆脱$sign:

let findKey key xs = snd . head . filter (\(k,v) -> key == k) ( xs )

然而,它甚至不解析(过滤器应用于太多的参数错误)。我读过$sign被用来简单地替换括号,我不明白为什么这个简单的代码更改是不好的。有人能给我解释一下吗?

操作员的优先级最低,所以

snd . head . filter (\(k,v) -> key == k) $ xs
读作

(snd . head . filter (\(k,v) -> key == k)) xs
而你的第二个表达是

snd . head . ( filter (\(k,v) -> key == k) xs )
中缀运算符
($)
只是“函数应用程序”。换句话说

 f   x     -- and
 f $ x
都是一样的。由于在Haskell中,括号仅用于消除优先级歧义(以及元组表示法和),因此我们还可以用其他几种方式编写上述内容

 f     x
 f  $  x
(f)    x
 f    (x)
(f)   (x)    -- and even
(f) $ (x)
在每种情况下,上述表达式都表示相同的内容:“将函数
f
应用于参数
x

那么为什么要使用这些语法呢<代码>($)之所以有用,有两个原因

  • 它的优先级很低,所以有时可以代替很多括号
  • 为函数应用程序的操作指定一个显式名称很好

  • 在第一种情况下,考虑下面的右嵌套函数应用< /P>

    f (g (h (i (j x))))
    
    读这篇文章可能有点困难,知道括号的数目是否正确也有点困难。然而,它“只是”一堆应用程序,所以应该使用
    ($)
    来表示这个短语。确实有

     f $ g $ h $ i $ j $ x
    
    有些人觉得这更容易阅读。更现代的风格还融入了
    (.)
    ,以强调这个短语的整个左侧只是一个由函数组成的管道

     f . g . h . i . j $ x
    
    lof :: [Int -> Int]
    lof = [ (+1), (subtract 1), (*2) ]
    
    正如我们在上面看到的,这个短语与

    (f . g . h . i . j)  x
    
    有时读起来会更好


    有时我们希望能够传递函数应用的思想。例如,如果我们有一个函数列表

     f . g . h . i . j $ x
    
    lof :: [Int -> Int]
    lof = [ (+1), (subtract 1), (*2) ]
    
    我们可能希望通过它们上面的值来映射应用程序,例如,将数字
    4
    应用于每个函数

    > map (\fun -> fun 4) lof
    [ 5, 3, 8 ]
    
    但由于这只是一个函数应用程序,我们还可以使用
    ($)
    上的节语法来更明确一些

    > map ($ 4) lof
    [ 5, 3, 8 ]
    
    $
    符号用于简单地替换括号”是非常正确的–但是,它有效地将所有内容都用括号括起来!所以

    snd . head . filter (\(k,v) -> key == k) $ xs
    
    有效地

    ( snd . head . filter (\(k,v) -> key == k) ) ( xs )
    
    当然,
    xs
    周围的参数在这里是不需要的(反正这是一个“原子”),因此在本例中,相关的参数在左侧。事实上,这在哈斯凯尔经常发生,因为一般哲学是尽可能多地将函数视为抽象实体,而不是将函数应用于某个参数时所涉及的特定值。你的定义也可以写出来

    let findKey key xs' = let filter (\(k,v) -> key == k) $ xs
                              x0 = head xs'
                              v0 = snd x0
                          in v0
    
    这将是非常明确的,但所有这些中间值都不是很有趣。因此,我们更愿意使用
    将函数简单地“无点”链接在一起。这通常会让我们摆脱很多陈词滥调,事实上,根据您的定义,可以做到以下几点:

  • η-减少
    xs
    。这个参数刚刚传递给函数链,所以我们不妨说“
    findKey
    ”就是这个链,不管你提供什么参数”

  • 接下来,我们可以避免这个显式lambda:
    \(k,v)->k
    只是
    fst
    函数。然后需要使用
    键进行后期比较

        findKey key = snd . head . filter ((key==) . fst)
    
  • 我就到此为止,因为太多的自由点是毫无意义和不可读的。但是你可以继续说:现在有新的参数围绕着
    ,我们可以再次摆脱那些使用
    $
    的参数。但是要小心:

        "findKey key = snd . head . filter $ (key==) . fst"
    
    不是对,因为
    $
    会再次将两边都括起来,但是
    (snd.head.filter)
    的类型不好。实际上
    snd.head
    应该只出现在
    filter
    的两个参数之后。进行此类后期合成的一种可能方法是使用函数functor:

    …我们可以更进一步,也可以去掉
    变量,但它看起来不太好。我想你已经明白了


  • $
    符号不是替换括号的神奇语法。它是一个普通的中缀运算符,在任何方面都像
    +
    这样的运算符

    用括号括住单个名称,如
    (xs)
    ,总是相当于
    xs
    1。因此,如果
    $
    就是这样做的,那么无论哪种方式,您都会得到相同的错误

    试着想象一下,如果您在那里有一些您熟悉的其他操作符,例如
    +
    ,会发生什么:

    let findKey key xs = snd . head . filter (\(k,v) -> key == k) + xs
    
    let findKey key xs
          = let filterExp = filter (\(k,v) -> key == k)
                dotExp1 = head . filterExp
                dotExp2 = snd . dotExp1
            in  dotExp2 $ xs
    
    忽略
    +
    对数字起作用的事实,因此这没有任何意义,只考虑表达式的结构;哪些术语被识别为函数,哪些术语被作为参数传递给它们

    事实上,使用
    +
    确实可以成功地解析和类型检查!(它为您提供了一个具有无意义类型类约束的函数,但如果您满足这些约束,它确实意味着一些东西)。让我们了解一下中缀运算符是如何解析的:

    let findKey key xs = snd . head . filter (\(k,v) -> key == k) + xs
    
    优先级最高的总是普通函数应用程序(只需将术语并排写入,不涉及中缀运算符)。这里只有一个例子,
    filter
    应用于lambda定义。这得到了“解决”,并就解析其余运算符而言成为一个子表达式:

    let findKey key xs
          = let filterExp = filter (\(k,v) -> key == k)
            in  snd . head . fileterExp + xs
    
    下一个优先级最高的是
    运算符。我们有几个可以从这里选择,都有相同的优先级。
    是右关联的,因此我们首先选择最右边的一个(但它实际上不会改变我们选择的结果,
    let findKey key xs
          = let filterExp = filter (\(k,v) -> key == k)
                dotExp1 = head . filterExp
            in  snd . dotExp1 + xs
    
    let findKey key xs
          = let filterExp = filter (\(k,v) -> key == k)
                dotExp1 = head . filterExp
                dotExp2 = snd . dotExp1
            in  dotExp2 + xs
    
    let findKey key xs = snd . head . filter (\(k,v) -> key == k) xs
    
    let findKey key xs
          = let filterExp1 = filter (\(k,v) -> key == k)
            in  snd . head . filterExp1 xs
    
    let findKey key xs
          = let filterExp1 = filter (\(k,v) -> key == k)
                filterExp2 = filterExp1 xs
            in  snd . head . filterExp2
    
    let findKey key xs
          = let filterExp1 = filter (\(k,v) -> key == k)
                filterExp2 = filterExp1 xs
                dotExp = head . filterExp2
            in  snd . dotExp
    
    let findKey key xs
          = let filterExp = filter (\(k,v) -> key == k)
                dotExp1 = head . filterExp
                dotExp2 = snd . dotExp1
            in  dotExp2 $ xs
    
    {-# LANGUAGE RankNTypes #-}
    
    -- A higher rank function
    higherRank :: (forall a. a -> a -> a) -> (Int, Char)
    higherRank f =  (f 3 4, f 'a' 'b')
    
    -- Standard application
    test0 :: (Int, Char)
    test0 = higherRank const     -- evaluates to (3,'a')
    
    -- Application via the ($) operator
    test1 :: (Int, Char)
    test1 = higherRank $ const   -- again (3, 'a')
    
    -- A redefinition of ($)
    infixr 0 $$
    ($$) :: (a -> b) -> a -> b
    ($$) = ($)
    
    test2 :: (Int, Char)
    test2 = higherRank $$ const  -- Type error (!)
    -- Couldn't match expected type `forall a. a -> a -> a'
    --             with actual type `a0 -> b0 -> a0'