函数式编程阅读笔记

函数式编程的基础来源于“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

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

原文发布于微信公众号 - 鸿的学习笔记(shujuxuexizhilu)

原文发表时间:2017-08-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python爬虫与数据挖掘

Python正则表达式初识(四)

普天同庆的日子里,送出最真的祝福,祝祖国繁荣昌盛,祝朋友事业有成,祝父母身体健康,祝大家永远开心,祝所有人幸福平安~~

693
来自专栏潇涧技术专栏

Python Data Structures - C2 Sort

参考内容: 1.Problem Solving with Python Chapter5: Search and Sorting online_link ...

891
来自专栏GIS讲堂

面向对象思想

类:描述了具有相同特性(属性)和相同行为(操作方法)的对象。在程序中,类就是数据类型。

1264
来自专栏Play & Scala 技术分享

为Play初学者准备的Scala基础知识

3596
来自专栏互联网开发者交流社区

面向对象的Java实现

1-1:封装 a.为什么需要封装(封装可以是数据方便维护、增加实用性、方便扩展等等。通过面向对象的思想,模拟现实生活中的事物。) b.什么是封装(封装就是将...

871
来自专栏Java帮帮-微信公众号-技术文章全总结

第五天 方法【悟空教程】

2067
来自专栏数据结构与算法

P3370 【模板】字符串哈希

题目描述 如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字、大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串。 友情提醒:如果真...

2974
来自专栏程序员互动联盟

【编程基础】聊聊C语言-常用运算符

上一篇我们讲了C语言中的基本运算符,他们就像基石一样奠定了我们进行基本算术运算的基础。我们马上将上一篇留得题的答案公布如下: 5/4=1 5.0/4=1.250...

3967
来自专栏IT派

这段代码很Pythonic | 相见恨晚的 itertools 库

最近事情不是很多,想写一些技术文章分享给大家,同时也对自己一段时间来碎片化接受的知识进行一下梳理,所谓写清楚才能说清楚,说清楚才能想清楚,就是这个道理了。

923
来自专栏鸿的学习笔记

从Scala和Python的“shell”说起

在《写给Python和Scala的碎碎念》的系列的开篇,让我们从最简单的交互式“shell”开始,一步步来看看Python和Scala的对于同一件...

1052

扫码关注云+社区

领取腾讯云代金券