haskell趣学指南学习笔记5:高阶函数

高阶函数

haskell中的函数可以接受函数作为参数,也可以将函数作为返回值,这样的函数叫做高阶函数。他们是解决问题、简化代码的得力工具,在haskell这类函数式编程语言中它不可或缺。

5.1 柯里函数

多元参数?哈哈

  • 本质上,haskell所有的函数都只有一个参数
  • 所有在前面笔记中出现过的多参数函数都是柯里函数: 不一次性取完所有参数,而是在每次调用时只取得一个参数,并返回一个一元函数来接收下一个参数,以此类推

好处

  • 这样一来,就可以以部分的参数来调用某函数,得到一个部分应用(partial application)函数
  • 这样构造的函数简单快捷,且结果随时可以传递给其他函数构造出新的函数

利用好处

  • 返回参数类型的逐步解析
    • multThree :: Int ->(Int->(Int->Int))
    • Int->(Int->Int)
    • Int->Int
    • Int

以下例子将会演示如何通过少调参数调用函数来创建新的函数

5.1.1 截断

  • 通过截断,可以对中缀函数进行部分应用 ,将一个参数放在中缀函数的一侧,并在外面用括号括起来,即可截断这个中缀函数
  • 使用截断时,唯一需要注意的是:在haskell中(-4)表示的是负四,想要表示减去一个参数的函数,可以用(substract 4)实现

5.1.2 打印函数

在haskell中,想要打印一个函数,必须要调用show函数,只有返回值是show类型的实例,才可能显示到屏幕

5.2 再来点高阶函数

  • haskell中->是自然的右结合,一般使用不需要使用括号,然而以上的括号是必须的
  • 它标明了第一个参数是一个参数和返回值类型都是a的函数,第二个参数与返回值的类型也都是a

5.2.1 实现ZipWith

ZipWith函数

此函数取一个函数和两个列表作为参数,然后使用两个列表中相应的元素取调用该函数,将两个表结合到一起

实现

在haskell中,在编写函数(尤其是高阶函数)时如果拿不准函数的类型,可以先省略类型,再用:t查看haskell类型推导的结果

5.2.2 实现flip

  • flip函数简单的取一个函数作为参数,返回一个效果相同的新函数
  • 唯一的区别是新旧函数的前两个参数的顺序是颠倒的

  • flip处理过的函数一般都用来传给其他函数
  • 我们可以在处理高阶函数时,发挥柯里函数的优势,预先考虑清楚全情景的应用
  • 根据返回值的要求,传递合适的函数

5.3 函数式程序员工具箱

因为:
– 身为函数式程序员,很少需要对单个值多费心思
– 更多的情景是处理一系列字母或者其他类型的数据
– 通过转换这一类集合来求取最后的结果
于是:
我们很可能需要以下这些函数

5.3.1 map函数

概述

map函数取一个函数和一个列表作为参数,将这个函数应用到该列表的每个元素,产生一个新的列表

更多用法

5.3.2 filter函数

概述

  • filter函数取一个谓词(predicate)和一个列表,返回由列表中所有符合该条件的元素组成的列表
  • 谓词值返回值为布尔值的函数

更多用法

相对于带有多个谓词的列表推导式而言,filter需要进行多次过滤,或者通过逻辑函数&&来组合谓词,才能达到和列表推导式相同的效果

5.3.3 有关map与filter的更多示例

由于haskell的多参数实现原理,这种现象便变得很容易理解

克拉兹序列

定义如下:

  • 从自然数开始
  • 如果是1,停止
  • 如果是偶数,除以二
  • 如果是奇数,将它乘以3然后加1
  • 取所得结果,重复以上算法

这个函数的类型为numLongChains :: Int 这是因为Int类型处理出的类型为Int,可以使用fromInterval函数来处理所得结果以生成Num a

5.4 lambda

概念

  • lambda就是一次性的匿名函数
  • 有些时候需要传给高阶函数一个特定功能的函数,就会用到lambda
  • lambda是一个表达式,因此我们可以将任意将其传给函数

说明

  • 使用lambda,就写一个(因为它看起来像是希腊字母入–斜着看)后面跟着函数的参数列表,参数之间使用空格分隔,->后面是函数体,通常习惯使用括号将lambda括起来
  • 不熟悉柯里函数和部分应用的初学者可能很容易使用lambda,然而事实上大部分lambda没有必要

  • lambda可以取多个参数
  • 可以在lambda中使用一个模式的模式匹配
  • 只要lambda的模式匹配失效,就会引发运行时错误,所以务必慎用
  • 当编写不带括号的lambda时,编译器会假定->右侧都是lambda函数体

上面两个等价的函数,前者更好的理解柯里化.下面一个更加直观易懂

优秀案例

5.5 折叠纸鹤

概述

回顾haskell中的递归,不难发现处理列表的函数大多数拥有(x:xs)这样的固定模式:

这一模式如此常见,以至于haskell设计者直接引入了折叠函数(fold)函数:允许我们将一个数据结构约为单个值

用法

  • 一个折叠取一个二元函数(取两个参数的函数),一个初始值(通常称为累加值)以及一个待折叠的列表
  • 可以从左开始折叠,也可以从右开始折叠
  • 折叠函数会取初始值和列表的起始元素来应用二元函数,得到的返回值作为新的累加值,然后继续重复这一操作,到遍历完毕时,得到的累加值就是最终的结果

5.5.1 使用foldl进行左折叠

通常,因为柯里化,foo a=bar b a形式的函数都可以改为foo =bar b

5.5.2 通过foldr进行右折叠

  • 左右折叠类似,不过考虑到:++的效率及haskell的常见模式,还是使用右折叠更是多一些
  • 另外,左折叠无法处理无限列表

5.5.3 flodl1与flodr1函数

  • 与前两者非常相似,只不过他们不需要提供初元素,默认为零(实际是取列表第一个元素作为初始值)
  • 于是带折叠列表必须不为空

5.5.4 折叠的几个例子

5.5.6 无限列表的折叠

无限列表只能运用右折叠,且利用了逻辑运算符的短路特性

5.5.7 扫描

  • scanl和scanr和foldl和foldr相似,不过他们会将累加值的变化记录记录到列表中
  • scanl1和scanr1相当于flodl1和flodr1
  • 扫描可以用于跟踪基于折叠实现的函数执行过程

takeWhile函数可以实现到某条件时的截断

5.6 有$的函数应用

$函数,又称为函数应用符(function application operator)

  • 普通的,用空格隔开的函数的应用有着最高的优先级,而使用$运算符的函数有着最低的优先级
  • 普通函数,函数调用是从左到右结合的,而$是右结合的
  • 此运算符可以帮助减少表达式中括号的数目

5.7 函数组合

**函数组合(function composition)*是这样定义的:(f·g)(x)=f(g(x))

理所当然但是仍然值得注意的是:f的返回类型需要和g的参数类型相符合

5.7.1 带有多个参数函数的组合

对于带有多个参数的函数,可以使用部分应用使得其变成只有一个参数的函数,在使用.符号进行函数组合

5.7.2 Point-Free风格

函数组合的另一用处是pointfree(即pointless风格)的函数

Tagged with:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据