搜文章
推荐 原创 视频 Java开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发
Lambda在线 > 鸿的学习笔记 > 函数式编程阅读笔记

函数式编程阅读笔记

鸿的学习笔记 2017-11-30

函数式编程的基础来源于“lambda calculus”,这是Alonzo Church的一个奇妙想法。简单来讲,一个函数可以接受另一个函数作为参数也可以输出另一个函数作为结果。这跟关系数据库一样有着坚实的数学基础。当然不是所有的lambda calculus都可以被表示为一个程序,因为数学的理想终究要接受物理的限制。与日常的命令式编程不同的是,变量一旦输入便不可改变。别吐槽,lambda表达式说的可不是,在一个变量的所有时期都不可变更,只不过兴趣点更多的是在于对于数据的操作。当然理论上,lambda calculus等价于turing machine。
object func {
  def main(args: Array[String]): Unit = {
    val a = "sda"
    val a = "ds"
    println(a)
  }
}
这时就会报错
Error:(7, 9) a is already defined as value a
    val a = "ds"
你要问我怎么解决?那就是写函数保持状态,如果要改变状态则使用迭代的方式    
  def reverse(arg:String): String = {
    if (arg.length == 0) {
      return arg
    } else {
      return reverse(arg.substring(1, arg.length)) + arg.substring(0, 1)
    }
  }
 
ps:从内存管理的角度来看,这个确实很容易造成memory hot,但确实是函数式编程的风格

此外,我们来了解下副作用这个概念:
如果一个函数不仅仅只是返回一个值,而且还做了如下的事:
1.擅自修改一个变量
2.直接动了数据结构
3.设置一个对象的成员
4.抛出一个异常或以一个错误停止
5.打印到终端或读取用户的输入
6.读写文件
这个就是副作用的概念。函数式编程的解法就是通过一个纯的内核和一层很薄的外围来处理这个问题

严格的定义如下:
对于一个输入类型为A和输出类型为B的函数f,也就是A => B。任何的内部和外部状态都不会影响到f的转换。这在编程中是很重要的一点。因为比如,.length方法就应该专注于求某个参数的长度,除此之外,啥也不应该做。
引用透明:
先来个例子
  def add(a:Int, b:Int): Int = {
    return a+b
  }
无论怎么变,输入同一组a,b,都不会改变程序的结果。也即是,函数无论进行了什么样的操作都可以用它的返回值来代替。
这样的操作使得等式推理变得异常简单

二、一些概念
这个在SICP里有提到过,就是把一个函数当作另一个参数传递给另一个函数

  def factorial(n:Int): Int = {
    def loop(n:Int, acc:Int): Int =
        if (n <= 0) acc
        else loop(n-1, n*acc)

    loop(n, 1)
  }
 
其实就是闭包的概念

类型参数:也就是说类型也应该是函数的一部分

  def findFirst[A](as:Array[A], p: A => Boolean): Int = {
    def loop(n: Int): Int =
        if (n >= as.length) -1
        else if (p(as(n))) n
        else loop(n + 1)

    loop(0)
  }

ps:怎么觉得这个就是所谓的动态类型

findFirst(Array(1,2,3), (x:Int) => x == 2)

这里使用了匿名函数的概念

三、函数式编程的数据结构

package fpinscala.datastructures


sealed trait List[+A] /*list是一个泛型参数,类型参数用A来表示*/

case object Nil extends List[Nothing] /*用于表现空List的list数据构造器*/

case class Cons[+A](head: A,tail: List[A]) extends List[A]/*呈现非空的List,实际上可以视为一个单向链表*/

object List {

  /**

    * 用于创建List和List操作

    */

    def sum(ints: List[Int]): Int = ints match{

        case Nil => 0

        case Cons(x, xs) => x + sum(xs)

  }


    def product(ds: List[Double]): Double = ds match {

        case Nil => 1.0

        case Cons(0.0, _) => 0.0

        case Cons(x, xs) => x * product(xs)

    }


    def apply[A](as: A*): List[A] =

        if (as.isEmpty) Nil

        else Cons(as.head, apply(as.tail: _*))

}


相当于

List("a", "b")

Cons("a", Cons("b", Nil))

实际上实现的是单向链表的功能

List的类型我们并不关心,Int,String之类的都行,这实际上叫做多态。


模式匹配:

看看sum, product函数

你可以理解为一个switch声明,它表达式的数据结构进行检验和提取子表达式。


当我们要从一个不可变的list里删除元素或者添加元素怎么办?当增加元素时,你取出来的值的引用就是在原始表中增加元素,而不去修改原来的数据结构。也就是复用。

这个概念叫做数据共享

是不是对Spark的RDD的概念有了更深的理解了?



四、对于异常的看法

一个很核心的一点是,可以用普通的值去表现失败和异常,可以通过高阶函数抽象出错误处理和恢复的常用模式。


object except {

  def main(args: Array[String]): Unit = {

    println(failingFn(12))

  }


  def failingFn(i: Int): Int = {

    val y: Int = throw new Exception("fail")

    try{

      val x = 42 +5

      x + y

    }

    catch {case e: Exception => 43}

  }

}


failingFn显然不是引用透明的,引用透明的表达式不会依赖于上下文,可以本地推导,而这些产生了副作用。


一般的异常会存在两个问题:

1.异常破坏了引用透明并引入了上下文依赖

2.异常不是类型安全的


  def mean(xs: Seq[Double]): Double = 

      if (xs.isEmpty)

        throw new ArithmeticException("wrong")

      else xs.sum / xs.length

这个函数是一个部分函数,对于一些输入没有定义

有两个解决办法

1.返回某个可能伪造的Double类型的值。但是这会导致错误悄无声息地返回给调用者,还会在调用点产生一堆代码模板

2.或者如下的解决方法

  def mean(xs:IndexedSeq[Double], onEmpty: Double): Double =

      if (xs.isEmpty) onEmpty

      else xs.sum / xs.length

这样的话,mean就是一个完全函数了

但是会有其他问题,倘若某个异常没有定义的话,如何处理,如何选择分支

这个时候就要用到option类型了

  def mean(xs: Seq[Double]): Option[Double] =

      if (xs.isEmpty) None

      else Some(xs.sum / xs.length)

已被定义的情况,就会返回Some,否则就是None,每一个对应输入的值,都有一个对应输出的值



五、严格求值和惰性求值

 List(1,2,3,4).map(_+10)

 -List[Int] = List(11, 12, 13, 14)

 

具体定义如下:惰性求值可以选择不对它的一个或多个参数求值

严格求值就是总是对它的参数求值


def square(x:Int): Int = x *x


如果是println(square(32+1)),肯定是先算32+1,再求x*X


惰性求值呢?

scala> false && {println("!!");true}

res3: Boolean = false

scala> false && {println("!!");true}

res3: Boolean = false

这两个都没打印出!!,说明了都是把第一判断了,后面的就不会再计算了,这就是惰性求值



版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《函数式编程阅读笔记》的版权归原作者「鸿的学习笔记」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注鸿的学习笔记微信公众号

鸿的学习笔记微信公众号:shujuxuexizhilu

鸿的学习笔记

手机扫描上方二维码即可关注鸿的学习笔记微信公众号

鸿的学习笔记最新文章

精品公众号随机推荐

上一篇 >>

早安,黑苹果人