SweatBuffer`s Blog

发酵的奶酪


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • Sitemap

  • 搜索
close
SweatBuffer`s Blog

Functional programming in Scala(三)

发表于 2017-04-28 | 分类于 Functional programming,Scala |

#Scala/Chapter3#

Chapter3 Functional data structures (函数式数据结构)

我们讲函数型程序并不更新 变量或者修改 可变的数据结构 那我们会提出疑问:

  1. 在函数式编程里面 什么样的(what sort of )数据结构 我们可以用
  2. 我们如何在 Scala 中定义他们
  3. 我们如何操作(operate)这些数据结构

在这章我们将会学习函数式 数据结构 (functional data structures )的概念和如何使用他们。我们将借此机会介绍 在函数式编程里 数据类型(data type)是如何定义的,学习 模式匹配(pattern matching)相关的技术,练习编写和归纳 纯粹的函数(pure functions)。

3.1 Defining functional data structures (定义 函数式数据结构)

一个函数式数据结构只有在使用 纯粹的函数(pure functions)的时候使用。记住,一个纯粹函数必须不能直接改变data 或者 执行其他的 side effect (附加的东西 比如在一个买咖啡的函数里面,执行连接信用卡server的代码 这个就属于 side effect)。
所以 函数式数据结构定义的内容是不可改变的。
例如:在Scala里的 List() 和 Nil 就是不可变的。在进行List 操作的时候 比如 计算 3 + 4 我们并不改变 3或者4本身 而是新生成一个变量 7. 两个input 都不改变。连接两个链表操作也是啊, append 链表A 和 链表B 他并不改变输入本身,而是新建立一个链表。
这是不是意味着我们做了很多额外的复制工作呢?其实不是的。首先我们看一下最普遍的函数数据结构,单链表 (Singly linked list)。

1
2
3
sealed trait List[+A]
final case class Cons[+A](head: A,tail: List[A])extends List[A]
final case object Nil extends List[Nothing]

sealed trait 是什么意思?
trait 是一个抽象的接口(abstract interface) 可以选择性的包含一些方法的实现。在这里声明一个 trait 叫做 List ,不含有任何方法。添加sealed在前面意味着 这个 trait 的所有的实现必须在这个文件里面声明。
这里面有两种实现 或者说 List 的 数据 构造(data constructors) 来代表 List 可以使用的的两种形式。
一个List 可以为空,由 Nil 来指示,不为空的时候由 Cons来指示。一个不为空的List 由 | Head | Tail | 来组成。Tail 是 表示剩余元素的一个List 。
正因为函数可以是多态的,数据类型也一样,通过添加 类型参数(type parameter) [+A] 在sealed trait List 后面 然后在 Cons data constructor 里面使用这个 A 类型的参数,我们声明 这个 List 数据类型为多态的意味着 我们可以使用相同的定义 对一个 Int 元素 List[Int], Double 元素(List[Double]), String 元素(List[String]) 等等。“+” 意味着 类型参数 A 是 协变的,共变的(Covariant). 意味着 A 是一个List 的 共变,积极参数。(个人理解这句话的意思是:List 和 参数 A 是共同, A 是Int,List 类型就是 List[Int], 就怎么说呢 他俩一起变化,一起等价这种概念吧)

例如:
Covariant: For all types x and y .

  • if x <: y, then List[x] <: List[y] (<:表示继承关系中的辈分高低)
  • If not annotated, parameter is invariant, meaning there is no subtype relationship List[x] and List[y]
    就是如果 参数有父子关系的话,那么List 也具有 父子关系。(个人理解)
1
2
3
val ex1:List[Double] = Nil
val ex2:List[Int] = Cons(1,Nil)
val ex3:List[String] = Cons("a", Cons("b",Nil))

一个数据构造函数声明 给我们一个方法去构造 这个数据类型的形式。例如:上面的三个👆式子。
Case object Nil 让我们写一个Nil去构造一个空的List , case class Cons 让我们写 Cons(“a”, Cons(“b”, Nil)) 去构造一个任意长度的单链表。
注意:因为List 是参数化的,A, 对A,这里有 可以被不同的类型实例化的多态函数。 ex2 实例化 A 类型的参数到 Int , ex3 实例化为 String,ex1 很有趣,Nil 是被实例化为 List[Double]类型了,这是被允许的,因为空的List 没有元素,可以被看做任意类型。
“老艺术家”说 Nil 定义的时候是 继承了 List[Nothing] 这是所有List 类的 最小辈分的一种,可以是任何类的子类。顺便拓展一下 :
继承关系: Animal类 <- Cat类 <- Kitty类 ————————
Animal
————————
^
————————
Cat
————————
^
————————
Kitty
————————

1
2
3
4
5
6
7
8
class Foo{
Cat foo(Cat c)
}
class Bar extends Foo{
Kitty (Kitty c)
+ foo +
Animal (Animal c)
}

返回类型问题和参数类型的问题:
先说 ~返回类型~ 哦,
亲子关系: Animal类 <- Cat类 <- Kitty类 ,
那么在内存里的空间状态是:[ ] < [ ] < [ ] 顺序是相反的,因为子类要保证父母能有的成员变量,成员方法自己都持有,但是子类持有的 变量和方法,父母类就不一定有了。所以返回的时候你要返回Kitty类型,ok,没有问题,因为他的空间包含了Cat 所需要的空间大小,但是返回 Animal 惨了,Animal的比 Cat 小哎,调用的时候 会有问题。换个人性化思维思考:父类里面的foo是猫,那么子类的 foo 只可能是 猫这个大类里面的小类别:波斯猫,kitty猫,你返回一个爬行哺乳动物就不对了。
~参数类型~ :参数是用来做 operation 的,那么父类的参数是Cat ,显然他需要用猫的某些技能或者特性,比如喵喵叫,舔自己,闻屁股,那么Bar 中的参数必然也要最起码满足这个要求,但是不许超出这个范围,你input 一个kitty特有的粉红色是猫这个类不具有的性质,那么就有问题了。
要保证在这里调用的东西,父类方法里面最起码都有。

综上所述:返回类型可以使本身,或者子类 往下走;参数类型可以是自己或者父类 往上走。好像延展的有点多了。。。
所以~~ Nil 是 List[ Nothing ], 在 Scala 里面,Nothing 是所有类型的子类,那么Nil 就是所有 List 类型的子类,所以 Nil 可以赋给任何类型的List 值。

添加补充一下:

Scala Convariance and Contravariance(逆变与协变)

Function范式里面定义了函数的”入参”和”出参” 分别是“逆变”(Contravariance)和”协变”(Convariance)的。

所以A=>B这样的函数类型,也可以有继承关系的。
我们做个测试,先简单些,只看出参类型的(协变容易理解些),A=>B和A=>C两个函数类型;
如果C extends B 则A=>C 是 A=>B的子类型

1
2
3
4
5
6
7
8
9
10
scala> class A; class Cats; class Kitty extends Cats
//定义A=>C类型的函数
scala> val t2 = (p:A)=>new Kitty
//可以把 A=>C类型的函数赋值给 A=>B类型的
scala> val t3:A=>Cats = t2
//或直接把t2造型为 A=>Cats
scala> t2.asInstanceOf[A=>Cats]

再看看入参类型,这个是逆变,继承关系正好相反。
假设: B=>A, C=>A 如果 C extends B 则 B=>A 是 C=>A 的子类型

1
2
3
4
5
6
7
8
9
10
scala> class R; class Cats; class Kitty extends Cats
//定义Cats=>R类型的函数
scala> val f1 = (x:Cats)=>new R
//把Cats=>R类型的函数赋值给 Kitty=>R 类型的
scala> val f2:Kitty =>R = f1
//或直接造型
scala> f1.asInstanceOf[Kitty=>R]

协变和逆变的场景与java泛型的”PECS原则”一致

PECS 是Joshua Bloch在《Effictive Java》里提出的一个原则。
当参数(容器)是一个生产者(producer)提供元素给你的代码来用(即容器只读),那么容器的泛型应该使用:
Collection< ? extends T >

当参数(容器)作为一个消费者(consumer)来消费你提供的数据(即容器可写),那么容器的泛型应该使用:
Collection< ? super T >

1
2
3
4
5
object List{
def sum(xs: List[Int]):Int = ???
def product(xs:List[Double]):Double = ??
def apply[A](as: A*):List[A] = ?
}

每一个 数据构造器 同样引入 模式(pattern) 可以被用于 模式设计,就像在 sum 和 product 方法里一样


3.2 Pattern matching (模式匹配)

仔细的看 sum 和 product 的细节,我们放入了 object List 里面,有时候叫做 Object List 的 伴生对象 (companion object)。

伴生对象: 我们除了声明 ~数据类型~ 和 ~数据构造函数~ 之外常常声明 ~伴生对象~。 这是和数据类型有着一样的名字(这里是 List ),我们放入多种方便的函数 到对象中 为了创建或者使用这种数据类型的值。(这句话怪哦)
例如我们希望一个函数 def fill[A](n:Int, a:A):List[A] 创建 一个List 里面有n个元素a,这个List 的伴生对象将会是一个好的地方。伴生对象不仅是在Scala里面的一种约定(Convention)。我们可以叫 这个 模块 Foo 如果我们希望,但是叫他 List 会很明确的显示 这个模块包含着关于 list 的一些方法。

下面的定义都使用了 模式匹配(pattern matching):

1
2
3
4
def sum(ints: List[Int]):Int = ints match{
case Nil => 0
case Cons(x,xs) => x + sum(xs)
}

1
2
3
4
5
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)
}

这些是递归定义,写方法的时候 Scala等函数式编程很喜欢递归。Pattern matching 有点像 switch 文, 首先有一个 expression 像 ds 在 match 前面,然后每个 case 会有各自相应的 pattern 像 Cons(x,xs) 然后 => 后面跟的是 结果。如果 target expression 满足某个case 里的 pattern 那么就实现pattern 对应的 结果。如果 有 满足多个case 的情况,选择最先匹配到的 pattern 来执行。
看一下更多的例子:

  • List(1,2,3) match {case _ => 42} 输出值为42,因为 ‘_’ 意味着无论是什么值,都可以匹配,任意一种情况都会执行这个pattern。
  • List(1,2,3) match {case Cons(h,_) => h} 这种情况会输出列表中的头,也就是说1,这种模式把List 中的 Head 和 Tail 分开了,值得我们学习哦。
  • List(1,2,3) match {case Cons(_,t) => t} 这段代码则是不管Head 是什么,我只要Tail 并且输出Tail 所以输出结果是 List(2,3)
  • List(1,2,3) match {case Nil => 42}会导致 MatchError错误发生。

List(1,2,3) = Cons(1,Cons(2,Cons(3, Nil )))

那是什么决定了是否一个 pattern 匹配一个 expression 呢? 一个 pattern 可能包含 ~常量~ 像 3 或者 “hi” ; ~变量~ 像 x 和 xs,匹配任何事情,由一个小写字母或者下划线开始的标识符标识; ~数据构造函数~ 像 Cons(x,xs) or Nil这种 只匹配相对应格式的值。这些模式的元素们可以被任意嵌套 — Cons(x1, Cons( x2, Nil)) , Cons(y1, Cons(y2, Cons(y3, _))) 是合法的模式。

一个模式匹配一个目标(如果在模式中存在一个变量的分配)到 这个目标的子表达式 (使其结构上和目标相等的 子表达式 ),对于一个匹配的 case , 结果得到的表达式 接下来将会 在他的本地范围里面 access 这些变量的赋值 。(哇塞 英文好难直译啊 T.T)
总结一下这段话:
问题:什么决定了是否一个 模式 匹配一个表达式呢?
答案:一个模式 可能包含 :常量,变量,数据构造器(怪翻译)。模式中的这些元素们可以互相组合嵌套。
尚未总结完毕


Scala 中的可变函数(Variadic functions in Scala)
List 对象中的 apply 函数 就是一个 ~可变函数~ 意味着它允许或者说接受 0 或者更多A类型的参数:

1
2
3
4
5
6
7
def apply[A](as: A*): List[A] = {
if (as.isEmpty) Nil
else Cons(as.head, apply(as.tail: _*))
实际:
if (as.isEmpty) Nil
else as.head::apply(as.tail: _*)
}

对于 数据类型,这是一个常见的俗话去有一个可变的apply 方法 在伴生对象中方便的构建数据类型的实例。通过调用这个方法 apply 然后在伴生对象中替换他,我们可以和语法一样调用它,就像 List(1,2,3,4) 或者 List(“Hi”,”bye”), 和许多我们希望用逗号分开一样。

1
2
3
4
5
6
apply(1,2,3,4,5)
res24: List[Int] = Cons(1,Cons(2,Cons(3,Cons(4,Cons(5,Nil)))))
List(1,2,3,4,5)
res31: List[Int] = List(1, 2, 3, 4, 5)
apply(1,2,"hello","4")
res49: List[Any] = Cons(1,Cons(2,Cons(hello,Cons(4,Nil))))

如上图所示,调用 apply函数会把用逗号分开的值变成列表。

Variadic functions(可变函数)就是为 明确的创建和passing 一个元素序列( Seq of elements)提供一个语法糖🍬(syntactic sugar)。序列(Seq)是一个接口,在Scala库中,像 list, queue, vector 一样的一种数据结构。在 apply 里面 参数 as 将会被绑定成 Seq[A], 这个对象有着 head 方法 和 tail 方法。
特别的 _* 类型标注 允许我们 通过(pass) 一个 Seq 到一个可变成员方法。


3.3 Data sharing in functional data structures (函数式数据结构中的数据共享)

当数据是不可变的时候,我们如何写函数,例如 从一个 List 中 add 或者 remove 元素?答案很简单,当我们添加一个元素1 到 一个列表的前头的时候,比如xs, 我们返回一个新的列表 Cons(1,xs)。 因为列表是不可改变的,我们不需要实际上的复制xs,只需要重新利用它,创建新的列表。我们叫它数据共享( Data sharing)。共享不可改变的数据常常使我们实现函数更加的高效;我们总可以返回不可改变的数据结构而不需要担心后面的数据修改我们的数据。并不需要悲观的通过复制不可变数据结构去避免修改和变形。

用同样的方式,从一个List mylist = Cons(x,xs)中移除头一个元素,我们只需要复制他的 tail就可以了。而 mylist 依旧存在和可用。我们说函数式数据结构是可持续的(persistent), 意味着既有的参照关系绝对不会因为在数据结构上的操作而改变。

数据共享的效率(The efficiency of data sharing)

数据分享的一个非常令人吃惊的例子就是 函数添加一个列表所有的元素到另一个列表的尾部:

1
2
3
4
5
6
7
8
9
def append[A](a1: List[A], a2: List[A]): List[A] =
a1 match{
case Nil => a2
case Cons(h,t) => Cons(h, append(t,a2))
实际:
case Nil => a2
case h::t => h::append(t,a2)
}

请注意这个定义仅仅是复制值 直到第一个链表的尾部,所以他的运行时间和内存利用是仅仅取决于 a1 的长度的。剩下的列表仅仅是指向 a2。如果我们要用两个数列(array)执行同样的操作的话,我们需要拷贝两个array 的所有的元素到输出上。所以在这种情况下,不可变链表是比数列更加高效的。

因为单链表的结构,任意时刻我们想要替换一个Cons的tail的时候,即使他是列表中的最后一个Cons,我们需要拷贝前面所有的Cons对象。写支持不同的高效操作的纯粹函数式数据结构需要我们找到一种聪明的方法去使用数据共享。现在我们并不准备自己亲自做所有的部分,我们可以很开心的使用Scala标准库,这里有定义好的纯粹函数式序列(pure functional sequence)的实现, ~Vector~ 。有着在常数时间下,随机的access(访问), updates, head,tail,init这些成员方法,常数时间下添加序列的头和尾。


提高高阶函数的类型推断

高阶函数像 dropWhile 常常会被 匿名函数(anonymous functions) pass掉。看一个典型的例子:(dropWhile 函数式 当条件式子为假那么开始 不在看元素是否符合条件了,开始迭代序列 ,
即:符合条件的时候继续扫描下一个元素,直到条件不符合,返回剩下的元素的列表)

1
2
3
4
5
def dropWhile[A](as: List[A],f:A=>Boolean):List[A] = as match{
case Cons(h,t) if (f(h)) => dropWhile(t,f)
case e @ Cons(h,t) => e
case _ => Nil
}
1
2
3
4
def dropWhile[A](l: List[A], f: A => Boolean): List[A] = l match {
case Cons(x,y) if(f) => dropWhile(y,f)
case _ => l
}

设计思路:match的时候可以 case Cons(x,y) if(f) 满足条件的时候要同时满足条件。

当我们用 匿名函数 f 调用这个方法的时候,我们必须区分 f 的参数 这里叫 x:

1
2
val xs: List[Int] = List(1,2,3,4,5)
val ex1 = dropWhile(xs, (x: Int) => x < 4)

这里如果不注释 x 的类型为 Int 的话会报错。
非常不幸的是我们需要规定 x 的类型是 Int。dropWhile的第一个参数是一个List[Int], 所以函数的第二个参数必须符合Int。这是之前声明的时候定义好的。Scala 可以推断这个事实,如果我们把 dropwhile 归入到两个参数的链表中:

1
2
3
4
def dropWhile[A](as: List[A])(f: A => Boolean): List[A] = as match{
case Cons(h,t) if f(h) => dropWhile(t)(f)
case _ => as
}

调用这个版本 dropWhile 函数的语法像 dropWhile(xs)(f)。其实就是啦,dropWhile(xs) 返回一个函数,我们利用 f 来作为参数 调用这个函数,其实就是dropWhile 被curry化了。这样grouping 参数的原因是为了帮助 类型推断。我们现在可以不用注释的使用dropWhile函数了:

1
2
val xs: List[Int] = List(1,2,3,4,5)
val ex1: dropWhile(xs)(x => x < 4)

这里的 x 就没有注释 x 的类型。
更普遍的,当一个函数定义里面包含着多种参数 group,类型信息从左到右的贯穿这些参数 groups。这里第一个参数group 固定了 parameterA 为Int,所以他右边的都是Int,x就不需要注释了。

我们通常将我们函数的参数 group 和 排序到 多重参数列表中来最大化 类型推论。


3.4 Recursion over lists and generalizing to higher-order functions (通过链表的递归调用和概述高阶函数)

让我们重新看一下 sum 和 product 的实现。我们非常 轻盈的实现了 product 从而 没有包含了检查 “ 0.0 逻辑 “的问题。

1
2
3
4
5
6
7
8
9
10
def sum(ints: List[Int]):Int = ints match{
case Nil => 0
case Cons(x,xs) => x + sum(xs)
改成 x::xs => x+sum(xs)
}
def product(ds: List[Double]): Double = ds match{
case Nil => 1.0
case Cons(x,xs) => x * product(xs)
改成:case x::xs => x* product(xs)
}

有没有注意到这两个函数是多么的相似,处理逻辑几乎一样。只是 处理数据的类型 一个是 Double 一个是 Int,撇开这个来看 不同点一个是 Nil 情况 返回值不同( 0 and 1.0),操作符号不同(+ and * )。
无论何时,遇到这种重叠的情况,你都想把这种重叠 抽象出来,把子表达式拽出来作为参数。如果一个子表达式关联任何本地变量( + 关联 本地变量 x 和 xs ,product也一样),把这个子表达式 放入采用这些变量作为参数的函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
def foldRight[A,B](as: List[A], z: B)(f: (A,B) => B) :B ={
as match{
case Nil => z
case Cons(x,xs) => f(x, foldRight(xs, z)(f))
改成:
case x::xs => f(x,foldRight(xs,z)(f))
}
def sum2(ns: List[int]) = {
foldRight(ns, 0)((x,y) => x + y)
}
def product2(ns:List[Double]) = {
foldRight(ns,d 1.0)(_ * _)
}
  • 把 f 放入他自己的参数 group 在 as 和 z 之后,让 参数推断决定f 输入类型。
  • * 是 (x, y) => x * y 更简明的一种注释。

你看我们在编程的时候很多方法都有共同点,他们可能都有一个可以归纳的核心逻辑,然后把这个核心逻辑抽离出来就变成foldright了。

foldRight (右折叠)没有指定任何一种元素的参数类型,我们发现 当概括/一般化之后,返回值并不一定是元素一个类型的。一种描述 foldRight 做了什么就是: 他替换了List 的 构造器,用 z 和 f 替换了 Nil 和 Cons :

1
2
3
4
5
6
7
8
9
10
Cons(1, Cons(2, Nil))
f (1, f (2, z ))
foldRight(Cons(1, Cons(2, Cons(3, Nil))), 0) ((x,y) => x+y )
1 + foldRight(Cons(2, Cons(3, Nil)), 0) ((x,y) => x+y )
1 + (2 + foldRight(Cons(3, Nil), 0) ((x,y) => x+y ))
1 + (2 + (3 + (foldRight(Cons(Nil, 0) ((x,y) => x+y ))))
1 + (2 + (3 + (0)
6

foldRight 遍历所有的路径,直到list 末尾。
为什么说foldRight 可能会有 栈溢出,因为他是 一层一层的迭代,在扫描到最后一个数之前所有的数都要留着,从最后一个数开始计算然后在往回一层一层计算,所以会有栈溢出的可能性。

再分析一下foldRight

1
2
3
4
5
def foldRight[A,B](as: List[A], z: B)(f: (A,B) => B) :B =
as match{
case Nil => z
case x::xs => f(x, foldRight(xs, z)(f))
}

两个参数()(), 所以前面foldRight[A][B], 看参数里面(List[A],z:B)这个没啥说的
(f: (A,B)=>B ) 这个指的是 里面的参数是一个函数:参数为A 和 B,A为List中的元素类型,B就是z的类型,最后得出结果为类型B。然后里面的body也是很棒,返回 Cons(x,xs) => f( x, foldRight(xs,z)(f) ) 返回的是列表的第一个元素和后面的迭代的值经过 f 操作。

下面看看foldLeft

1
2
3
4
5
6
7
8
9
10
11
12
13
def foldLeft[A,B](as:List[A],z:B)(f :(B,A) => B):B =as match{
case Cons(h,t) => foldLeft(t,f(z,h))(f)
case Nil => z
}
Cons(1,Cons(2,Cons(3,Nil)))
f(3, f(2, f(1, z)))
foldLeft(Cons(1, Cons(2, Cons(3, Nil))), 0) ((x,y) => x+y )
foldLeft(Cons(2, Cons(3, Nil)), 1+0) ((x,y) => x+y )
foldLeft(Cons(3, Nil), 2+1+0) ((x,y) => x+y ))
foldLeft(Nil, 3+2+1) ((x,y) => x+y ))))
6

你在看看这个左折叠的 z 的值是每次迭代都会更新一次的,然后每次的这个更新后的值在带入下一个函数里面,所以这个就不会有栈溢出的问题。
很神奇哦,就是明明是顺序运算但是却 写成这个样子。。
在这里他用List 的第一个的元素和参数z 进行 f 运算然后放入第二次迭代的参数进行第二次迭代。所以可以看做是一边运算一遍进行迭代。而foldright 则是一直迭代到底然后在返回来。
两者有啥区别??

(考点!!)List.reverse 教授好像说会考 List 的 Reverse ~~

分别利用 dropLeft 和 dropRight 来编写 reverse函数:

1
2
3
4
5
6
7
8
9
def reverse[A](as: List[A]): List[A] = {
foldLeft(as, List.empty[A])((acc,a) => Cons(a,acc))
或者
foldLeft(as, Nil:List[A])((acc,a)=>Cons(a,acc))
实际:
foldLeft(as,Nil:List[A])((a,acc)=> acc::a )
或者
foldLeft(as,Nil:List[A])((acc,b) => append(b::Nil,acc))
}
1
2
3
4
5
6
7
def reverse[A](as:List[A]):List[A]= {
foldRight(as,List.empty[A])((a,b)=>append(b,Cons(a,Nil)))
实际:
foldRight(as,Nil:List[A])((a,b)=>append(b,a::Nil))
或者:
}

拆开 foldLeft和foldRight 版本的reverse

1
2
3
4
5
6
7
8
9
10
reverse-foldLeft(List(1,2,3,4,5)) =
foldLeft(list(1,2,3,4,5),Nil)((a,acc)=> acc::a)=
(Nil,1)=> 1::Nil
foldLeft(List(2,3,4,5),1::Nil) =
(1::Nil,2) => 2::1::Nil
foldLeft(List(3,4,5),2::1::Nil) =
(2::1::Nil,3) => 3::2::1::Nil
...
5::4::3::2::1::Nil


1
2
3
4
5
6
reverse-foldRight(List(1,2,3)) =
foldRight(List(1,2,3),Nil)((a,b)=>append(b,a::Nil))
append(floldRight(2::3::Nil,Nil),1::Nil)
append(append(foldRight(3::Nil,Nil),2::Nil),1::Nil)
append(append(append(Nil,3::Nil),2::Nil),1::Nil)
3::2::1::Nil

思路:
利用foldRight和foldLeft的 原理和性质。
Left呢就是利用第一个元素和初始值进行计算然后带入到第二个元素计算中的初始值中,迭代计算。
Right呢就是 先不进行运算,先迭代到最底层然后从最后一个元素和初始值进行运算在返回来的这么一个过程。

匿名函数的下划线注释(Underscore notation for anonymous functions)

匿名函数(x,y) => x+ y 可以写成 + ,在 x 和 y 的类型可以被Scala 推测出的环境下。 这个非常实用,在case里面速写的时候,条件:在函数body中 参数只被提到一次的时候。在匿名函数中的每个下划线 像 + 引入了一种新的函数参数和参照它。参数以 从左至右的顺序被介绍。例如 x, y都只用了一次,顺序是 x, y 所以 可以 , 来替代
下面有一些例子:

1
2
3
4
5
_ + _ (x, y) => x + y
_ * 2 x => x * 2
_.head xs => xs.head
_ drop _ (xs, n) => xs.drop(n)
_.drop(_)也可以

请明智的使用这个语法。在表达式中这个语法的意义像 foo( , g(List( + 1), _ ))可能不会很清楚。关于这些 基于下划线的 匿名函数的 scope 有严格的 规则,在Scala 细则里面,除非你必须使用它,我们建议使用普通的参数命名规范。

(考点!!)List.flatten

flatten函数就是把List(List(1,2,3), List(“a”,”b”,”c”),List(a,b,c))摊开了变成一个LIst(1,2,3,”a”,”b”,”c”,a,b,c)……

1
2
3
4
5
6
7
8
def flatten[A](ffa: List[List[A]]):List[A] = ffa match{
case Cons(h,t) => append(h, flatten(t))
case Nil => Nil
}
def flatten[A](ffa: List[List[A]]):List[A] ={
foldLeft(ffa,List.empty[A])(append(_,_))
}

(考点!!)List.join

1
2
3
def join[A](ffa:List[List[A]]):List[A] = {
foldRight(ffa,List.empty[A])(append(_,_))
}

(考点!!)Map

引子:想要在list 中的每个元素都➕1 怎么写?

1
2
3
4
def incOne(as:List[Int]):List[Int] = as match{
case h::t => h+1::incOne(t)
case Nil => Nil
}

implicit class 是啥?

List[Double] -> List[String]

1
2
3
4
def doubleToString(as:List[Double]):List[String] = as match{
case h::t => h.toString::doubleToString(t)
case Nil => Nil
}

仔细观察这两个函数 发没发现 共同点:他们都没有改变原有List 的结构,只是单纯的改变了List 中 每一个元素的 值或者属性。这个跟foldright或者foldleft不太一样,fold系列改变了 List 的结构,拆开表格之后进行了这那那这的操作。

1
2
3
4
def map[A,B](fa:List[A])(f:A => B): List[B] = fa match {
case Cons(h,t) => f(h)::map(t)(f)
case Nil => Nil
}
1
2
3
4
def map2[A,B](as:List[A])(f:A => B): List[B] =
foldRight(as,List.empty[B])((a,acc)=> f(a)::acc )
或者:
foldLeft(as,Nil:List[B])((acc,a)=> append(acc,f(a)::Nil) )

这里再说一嘴,foldRight 和 foldLeft是 scala里面 很重要的两个函数,它俩啥都能做。会考哦~~

那究竟什么是 map ,而map 为什么在scala中这么重要呢?
map 有两个输入,一个是 List 一个是 函数 f , 他把list 中的每一个元素进行了f函数的处理之后 返回 这个原来的List。但是要说的是,原有的List 大结构可能不变,但是多少会变,比如

1
2
scala> map(List(1,2,3))((x=>x match{case 3 => List('a','b') case _ => x*2}))
res57: List[Any] = List(2, 4, List(a, b))

这个例子输入的List类型是List[Int] 但是出来之后变成List[Any]了,只是结构还是三个元素这个不变而已。

1
2
3
4
5
6
7
8
9
10
object demo{
val as = List(1,2)
val bs = List(3,4)
for{
a <- as
b <- bs
}yield (a,b)
buxing 必须有 flatmap 和 map 类型
}

yield是什么呢?yield 是跟在for 循环后面常常使用的一个关键字,然后他会把for中的 项记载下来在循环结束之后返回,类型和输入的类型是一样的。

1
2
3
4
5
6
7
8
9
def flatMap[A,B](as:List[A])(f: A => List[B]): List[B] = as match {
case Cons(h,t) => append(f(h),flatMap(t)(f))
case Nil => Nil
}
def flatMap2[A,B](as:List[A])(f: A => List[B]): List[B] =
foldRight(as,List.empty[B])((a,acc)=>append(f(a),acc))
或者:
foldLeft(as,List.empty[B])((acc,a)=>append(acc,f(a)))

其实哦,flatMap 和 Map 基本一样,唯一的不一样就是 它把Map 给 flatten了,也就是把里面的嵌套List结构都打散,变成了一个List。

在试图用 foldRight 和 foldLeft重写的时候 你要知道的是,这里面类型最最重要。
思路:

  • 首先 目标是:把元素里的每个元素都经过 f 函数变换,然后用append函数 将 List 变为一个,而没有嵌套List结构。所以 foldRight 和 foldLeft函数都符合范围。
  • 其次我们思考格式匹配:
    1
    2
    3
    def flatMap2[A,B](as:List[A])(f: A => List[B]): List[B] =
    def foldRight[A,B](as: List[A], z: B)(f: (A,B) => B) :B
    def foldLeft[A,B](as:List[A],z:B)(f :(B,A) => B):B

因为我们要使用这两个函数,那么必须知道这两个函数的 参数类型和返回值类型,哇塞,能想到这个就已经算是具备一定编程思想的银了哎。
那不论foldRight还是foldLeft 第一个参数是不需要思考的 一样的,都是as,而且初始值 是 Nil 也没有错。 所以 :
foldRight(as,List.empty[B]) 或者:
foldLeft(as,List.empty[B])
然后,我们思考 f 操作,foldRight的 f : (A,B) =>B 那么我们希望append(f(第一个元素),后面的List ) 是结果变成:append(1,append(2,append(3,Nil)))这种。所以要
foldRight(as,List.empty[B])((a,acc)=>append(f(a),acc))
接下来思考 foldLeft的 f 操作 : f : (B,A)=>B 那么就是 Nil 作为 f 的第一参数,和 f (List 中的 第一个元素) 做 append操作,
foldLeft(as,List.empty[B])((acc,a)=>append(acc,f(a)))
但是要保持元素和Nil 的顺序,但是append 有着这么一个特性,append(Nil,a2) = a2,所以这里就算 append (Nil, f(a))也没有关系,还是f(a),
但是编程的时候 想的好复杂,left right 的内部逻辑还思考了很多,但是其实不需要思考,调用函数的时候,不许要考虑调用的函数的内部逻辑,只要参数类型对了,你选择的函数对了,就不要考虑太多了。


1
2
3
4
5
6
7
8
9
def map2[A,B](as:List[A])(f:A => B): List[B] =
foldRight(as,List.empty[B])((a,acc)=> f(a)::acc )
或者:
foldLeft(as,Nil:List[B])((acc,a)=> append(acc,f(a)::Nil) )
-----------------------------------------------------------
def flatMap2[A,B](as:List[A])(f: A => List[B]): List[B] =
foldRight(as,List.empty[B])((a,acc)=>append(f(a),acc))
或者:
foldLeft(as,List.empty[B])((acc,a)=>append(acc,f(a)))

在利用 foldRight 和 foldLeft 编写 map 和 flatMap的时候又多了一些感悟,这种感悟是一种感觉,不是简单地看规则就有的感觉。

f 操作

先看 map 和 flatMap的 f 操作,map是 A=>B 也就是 list中的A类型元素经过 f 操作 生成 B类型的结果,注意这里不是生成了List 类型 而是B类型。
而flatMap的 f 操作生成的是 A=>List[B] 元素类型为B的List类型,他们两个产物不太一样。

这一不同直接影响到了 body 的编写,因为 foldRight 和 foldLeft 要求的f 操作的类型分别是 (A,B)=>B 和 (B,A)=>B
那么看好了,
map: foldRight(as,List.empty[B])((a,acc)=> f(a)::acc )
flatMap: foldRight(as,List.empty[B])((a,acc)=>append(f(a),acc))
这里面 (a,acc) 分别代表的是 (List中的每一个元素(A类型), Nil 和 迭代返回的B类型的 结果)。所以(a,acc)=> f(a)::acc 的 类型检测就是 (A,B)=> B::B 而从List.empty[B]或者Nil:List[B]这段代码系统就可以检测到 foldRight 的B类型是 List[B],所以 B::B 返回一个List[B] 类型没啥问题,或者 B::List[B] 这种也是 返回 LIst[B]那么这有回到了第一节课的时候,有点脑子混乱,B类型和List[B]类性有点乱。而flatMap则不可以直接 f(a)::acc而是必须 append(f(a),acc)因为多了flaten的过程。

map: foldLeft(as,Nil:List[B])((acc,a)=> append(acc,f(a)::Nil) )
flatMap: foldLeft(as,List.empty[B])((acc,a)=>append(acc,f(a)))
foldLeft的 f 操作的类型是 (B,A)=>B , 所以 后面的那个a 才是 list中的元素。所以要 f(a)。 然后为什么要 f(a)::Nil 因为append要求 添加List[A]和 List[B]。而 f(a)返回的是 B类型的参数,必须加上NIl 编程 list[B]类型的才可以运行append。
要切记的是 Cons(List(a),List(b)) 和 append(List(a),List(b))是不一样的哦。


莫纳德 拥有 flatmap 和map 。
那什么是 莫纳德?

学C++ 和 C 学 指针的时候最难
函数式方法中 莫纳德最难。。。

berrito???


1
2
3
def flatMap[A,B](as:List[A])(f: A => List[B]): List[B] = {
flatten(map(as)(f))
}

flatmap就是先mapping 然后在flatting ~~


List.pure

1
2
3
4
5
6
7
8
def pure[A](a: A): List[A] = List(a)
def filter[A](as: List[A])(f: A => Boolean):List[A] = {
flatMap(as)(a => if(f(a)) List(a) else Nil)
flatMap(as)(a => if(f(a)) pure(a) else Nil)
因为这里用的是参数 LIst 以后的话不一定参数类型就是LIst类型的。
}

3.5 Trees (树)

3.6 Summary(总结)

SweatBuffer`s Blog

Functional programming in Scala(一)

发表于 2017-04-24 | 分类于 Functional programming,Scala |

#Scala/Chapter1#

What is a pure function

Pure function is one that lacks side effects.
what is side effects?

  • 修改一个变量
  • 当场修改一个数据结构
  • 对对象设置领域
  • 抛出异常和处理error
  • 命令提示符中打印,读取用户输入
  • 读写文件
  • 画图
  • 连接server等~~

用一句话说 一个函数除了根据我们的输入值计算结果之外不做任何我们可以观察得到的行为,那么可以基本看为纯粹函数,prue function。

Referential Transparency(RT)

referential transparency 也叫 引用性透明: referential Transparency不只是 function的性质,也是 expressions的性质:在任何的程序里面 表达式可以在不改变程序语义的情况下,用结果值,result 来替换表达式。

Referential transparency and purity

An expression e is referential transparent if , for all programs p, all occurrences of e in pcan be replaced by the result of evaluating e without affecting the meaning of p.
A function f is pure if the expression f(x)is referentially transparent for all referentially transparent x.

Substitution model

Referential transparency forces the invariant that everything a function does is represented by the ~value~ that it returns, according to the result type of the functions.
This constraint enables a simple and natural mode of reasoning about program evaluation called the ~substitution model~

在这样的规则下,我们的计算过程就是想是解代数方程。我们扩张表达式的所有的部分然后用他们的 参考对象 替换所有的变量,然后分解到最简式子。换句话说 RT 允许 方程式推理(equational reasoning )

关于RT的个人的想法:

RT叫 referential transparency 引用性透明,相比较C 这种语言来说有着很大的不同。scala中 对一个变量调用他的一些方法,产生了一个新的结果,这个结果会给予一个新的空间,新的变量名字,之前引用的变量不发生任何变化。
为啥这样设计,因为要支持函数式编程,很符合递归的概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
scala> val x= "Hello,world"
x: String = Hello,world
scala> val r1 = x.reverse
r1: String = dlrow,olleH
scala> var r2 = x.reverse
r2: String = dlrow,olleH
scala> val r1 = "Hello,world".reverse
r1: String = dlrow,olleH
scala> val r2 = "Hello,world".reverse
r2: String = dlrow,olleH

x 是 String 对象,reverse 是 String 类的 成员方法
这个例子就是 x 调用了 reverse之后 并没有改变x自身的值,所以再次调用reverse方法 还是一样的结果。就完全可以用 x 的值”Hello,world”来替代之前式子中的x。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
scala> val x = new StringBuilder("Hello")
x: StringBuilder = Hello
scala> val y = x.append(",world")
y: StringBuilder = Hello,world
scala> val r1 = y.toString
r1: String = Hello,world
scala> val r2 = y.toString
r2: String = Hello,world
scala> val r3 = x.append(",world").toString
r3: String = Hello,world,world
scala> x
res102: StringBuilder = Hello,world,world

这里的x 是 Stringbuilder 对象 , append 是 Stringbuilder 类的 成员方法。
这里 x 调用了一次 append 方法之后,自身的值改变了,所以在第二次调用append 的时候 变成了 Hello,world,world。而 String 对象的 toString方法,没有啥变化,调用了两次也没啥问题。所以Stringbuilder.append不是一个pure function 不是存粹方法。所以side effect 使得程序推理编的很困难。

SweatBuffer`s Blog

Functional programming in Scala(0)

发表于 2017-04-15 | 分类于 Functional programming,Scala |

#Scala/Chapter0#

Lambda Calculus

lambda之所有这么重要,是因为它具有“二象性”:它既可以被看作一种简单地程序设计语言,用于描述计算过程;也可以被看做一个数学对象,用于推导证明一些命题。

Function (函数)

  • Black Box
  • Pure
    我们把函数看成一个Black Box,x -> f -> y 就是只有一个输入和对于同一个输入只产生唯一一种结果。也就是说输出 y 只依赖于 输入 x ,并无其他任何因素可以影响函数的输出结果 y。

Functional Model of Computation

  • Express computation based on
    • (anonymous ) function ~abstraction~ and
    • function ~application~ via binding and substitution
      function abstraction 函数抽象指的是 用lambda表示的式子
      function application 指的是lambda式子右边的值带入左边的lambda式子里面替换 式子的 body 里面 与 变量绑定的值。
  • Equivalent to state-based Turing Machine by Alan Turing

Syntax

lambda expressions are :

  • E = ID for example : x,y,z,…..variables ID
  • E = /ID. M the abstraction symbols lambda ‘/’ and dot ‘.’
  • E = (MN) parentheses ()
  • E = (E)
    pure lambda calculus does not define constants, operators, etc.

便于理解的例子 & Currying

平方和函数:f(x,y) = x*x + y*y | f(3,4) = 3*3 + 4*4
映射: (x,y) -> x*x +y*y | ((x,y) -> x*x +y*y)(3,4) = 3*3 + 4*4
变一下形式,编程单参数函数:
(x -> (y -> x*x + y*y)) 什么意思呢,把x 映射为另外一个y的映射~ (x -> (y -> x*x + y*y))(3) = (y -> 3*3 + y*y)
(y -> 3*3 + y*y)(4) = 3*3 + 4*4
这样子就把 f(x,y) 变成了 f(x)(y) 用类似的方法把多个参数的函数编程单个参数的函数的高阶函数,这个转换叫做 currying。
更易于理解的看一下:
def f(x:Int,y:Int):Int = x*x +y*y变成了:

1
2
3
def f(x:Int):Int = {
def f(y:Int):Int = x*x + y*y
}

以上都是我个人理解,代码不准确,想表达某一个意思呢,我也不知道怎么说好,第一 上面的应该是匿名函数,我写成了有名字的函数呢。然后 。。。

为什么要转换?意义是什么?


Notation Simplification(符号简化)

  • lambda 式子中的最外层的括号可以摘掉 例如 (/x.x) -> /x.x
  • lambda 式子是左结合的,所以是左面优先的 例如 (((MN)P)Q) -> MNPQ

Disambiguation Rules(不暧昧规则)

  • Application are assumed to be left associative :
    • M N P my be written instead of ((MN)P)
  • The body of an abstraction extends as far as possible:
    • /x.MN are not (/x.M)N , they are different
    • (/x./y.yx)ab != /x./y.yxab
    • ((/x./y.yx)ab) and (/x./y.(yxab))
  • /x.AB means /x.(AB) is an function abstraction while
  • (/x.A)B is an function application means B is a parameter of
    the function (/x.A)

Abstraction Syntax Tree

lambda: abstraction operator
apply: application operator

  • the right subtree of the apply node is the actual argument
  • the left subtree of the apply node is (with a lambda at its root) the function
  • the left child of the lambda is the _ parameter.
  • the right child of the lambda is the function body.

Problems with the naive rewriting rule

Simple rule for rewriting (/x.M)Nmeans “replace all occurrences of x in M with N”. However , there are two problems with this rule

  • “Scope escape” problem
  • “Capture” or “name clash” problem

Problem 1 : Scope escape :

1
2
3
4
5
6
7
8
9
(/x.(x + ((/x.x+1)3)))2
=> (/x.(x+(3+1)))2
=> (/x.(x+4))2
=> (2+4) => 6
--------------------------------------------------------------
(/x.(x + ((/x.x+1)3)))2
=> (2+ ((/x.2+1)3)) )
=> (2+ (2+1))
=> (2+3) => 5

也就是这里的 第一个x 绑定的是最外层的x 而 最内层的x 绑定的则是 内部的x 他俩绑定的值不一样所以 用最外层2 带入值的时候不可以 替换内部的呢,所以会出现问题。

Problem 2 : Name clash :

1
2
3
4
5
6
7
((/x./y.x)y)z
=> (/y.y)z
=> z
----------------
((/x./a.x)y)z
=>(/a.y)z
=> y

本来的y 和 /y 没有绑定关系的,但是在带入之后突然就有了关系,这样就造成了错误的绑定和错误的结果。

Bound & Free Variables

  • Bound Variable:a variable that is associated with some lambda
  • Free Variable:a variable that is not associated with any lambda
  • Intuitively , in lambda-expression M, variable x is bound if , in the AST, x is in the subtree of a lambda with left child x. 这句话换句话就是 如果 这个 x 是处于 lambda Scope 范围内的话 他就是被 lambda 绑定的变量,除非里面没有另一个 lambda x 式子。
  1. FV(x) = {x}
    In the expression x , variable x is free. 因为没有lambda 单独的x 当然是free
  2. FV(M,N) = FV(M) U FV(N)
    In the expression M N :
    a. The free variables of M N are the union of two sets.
    b. The bound variables of M N are also the union of two sets.
  3. FV(/x.M) = FV(M) - {x} (除了x M中的自由变量都是自由的)
    In the expression /x.M,
    every x in M is bound;
    Every variable y != x that is free in M is free in /x.M;
    Every variable is bound in M is bound in /x.M.

练习:

1
(/x. x(/y.xyzy)x )xy

Solution of the Problem 1 :

Revised Rewriting Rule : To solve this problem ,given lambda expression
(/x.M)N “Replacing all occurrences of x that are free in M with N”
(/x. x + ((/x.x +1)3)) 2
这里的第一个 x 是 M 中的 free 变量,而 第二个里面的 x 则是在 M中有绑定值。所以我们可以在 application的 时候 用2 来替换 M中的 第一个free的 变量x 。这里的x 对于 M来说是 free 但是对于 lambda 表达式来说是 bounded。
(/x. x + ((/x.x +1)3)) 2 => 2+((/x.x +1)3)

Solution of the Problem 2:

The variable y that is free in the argument to a lambda expression becomes bounded after rewriting , because it is put into the scope of a lambda expression with a formal parameter named y:
((/x./y.x)y)z这里的外层的y 进入内层替换x 之后 就变得绑定了。所以引出了 阿尔法等式:

a-equivalence (a 等价) & a-conversion (a 约定)

The basic idea is that formal parameter names are unimportant ;so rename them as needed to avoid capture or name clash.
a-conversion modifies expressions of the form /x.M to /z.M
rename all the occurrences of x that are free in M to some other variable z that does not occur in M (and then /x is changed to /z).意思就是 M中的free的x 才是 /x.M 中和 lambda 绑定的x 而 M 中 bounded的 x 则是内部绑定的和外部的 lambda 没有绑定关系所以不能替换,所以 先把 外部的 /x. 替换成 /z.把 M中 free的 x 替换成z. 注意的是 z 不允许出现在 M中 反而产生别的问题。
/x./y.x+y可以通过 a-reduces 到 /z./y.z+y

Renaming Operation (a-conversion)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
E{y/x} 用 y 替换 x
- x{y/x} = y
- z{y/x} = z, if z!= x
- (M N){y/x} = (M{y/x})(N{y/x})
- (/x.E){y/x} = (/y.E{y/x}) if no y in E
- (/z.E){y/x} = (/z.E{y/x}) if z != x
example :
((/x.x(/y.xyzy)x) x y){bar/x}
---------------- - -
A B C
=> (/x.x(/y.xyzy)x){bar/x}(x){bar/x}(y){bar/x}
=> (/bar.(x(/y.xyzy)x){bar/x}) bar y
=> (/bar.bar (/y.xyzy){bar/x} bar) bar y
=> (/bar.bar (/y.bar yzy)bar) bar y

a-equivalence

For all expressions E and all variables y that do not occur in E
/x.E =a /y.(E{y/x})

Substitution (替换)

substitution 的目的是把M中的自由变量x (也就是 受/x 约束的 x 替换成 N实现application)
(/x.+x1)2 -> (+ 2 1) replace a variable by a lambda expression

  • E[ x -> N ] ,when E and N are lambda expressions and x is a name.
    1
    2
    3
    4
    5
    (/y./x.yx)(/z.xz)
    =>(/x.yx)[y -> /z.xz]
    =>(/x.(/z.xz)x) => trouble!
    因为 /z.xz 中的 x 本来是 自由变量而在替换之后和里面的 /x 产生bound关系了,所以在替换前对于原来的式子使用 a-reduction!
    => (/w.(/z.xz)w)这样就没有多添加的关系了。

Substitution Rule

1
2
3
4
5
6
7
8
9
E[x->N]
1. x[x->N] = N
2. y[x->N] = y, if x != y
3. (E1 E2)[x->N] = (E1[x->N])(E2[x->N])
4. (/x.E)[x->N] = (/x.E) 里面不可能有 free 的 x !!这个要想一下
5. (/y.E)[x->N] = (/y.E[x->N])when y!= x and y不属于FV(N)如果N中存在free的 y 那么替换之后 会突然增加绑定关系
6. (/y.E)[x->N] = (/z.E{z/y}[x->N])when y != x and y 属于 FV(N) and z != x and z 不属于 FV(N)
sepecial : 4条 有点模糊哦,
(/x.E)[x->N] 意味着 (/x./x.E)(N) 这种情况要带入N 然后替换 /x.E 中和 外部的/x 绑定的 x 但是 /x.E 中肯定不可能存在和外部绑定的x 都被里面的/x所绑定了。所以 (/x./x.E)(N) = /x.E 所以才有的 (/x.E)[x->N] = (/x.E)

Example :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(/x.x)[x->foo] => /x.x by (4)
(+1x)[x->2]
=> (+[x->2] 1[x->2] x[x->2]) by(3)
=> (+ 1 2) by (2),(1)
--------------------------------------------------------------
(/y./x.yx)(/z.xz)
(/x.yx)[y->/z.xz]
=> (/w.yw)[y->/z.xz] by(6) since x 属于 FV(/z.xz)
=> (/w.(yw)[y->/z.xz]) by (5)
=> (/w.(y[y->/z.xz] w[y->/z.xz])) by (3)
=> (/w.(/z.xz) w) by(1),(2)
--------------------------------------------------------------
(/x./y.(/f.fx)y)(fy)
(/y.(/f.fx)y)[x->fy]
=> (/w.(/f.fx)w)[x->fy] by(6) since y 属于 FV(fy)
=> (/w.(/f.fx)[x->fy]w[x->fy]) by(5),(3)
=> (/w.(/f.fx){f/z}[x->fy] w) by(6),(2) since f 属于 FV(fy)
=> (/w.(/z.zx)[x->fy] w )
=> /w. (z(fy)) w
--------------------------------------------------------------
(/y./z.yz)(/z.xz)
(/z.yz)[y->/z.xz]
=> (/z.(yz)[y->/z.xz])
=> (/z.(y[y->/z.xz] z[y->/z.xz]) )
=> /z.( (/z.xz) z )

Precise Meaning of Rewriting : Beta-reduction

进行Substitution 的过程叫做 B-reduction
(/x.M)N -> B M[x -> N]
左边的部分(/x.M)N 叫做 redex(可约式)
右边的部分 M[x -> N] 叫做 contractum (缩减项)
中间的 -> B 记号(notation)意味着 all free occurrences of x replaced with N in a way that avoids capture/Name clash .
个人想法:这是种什么感觉呢 就是 比如一个f(x)(y) ,f(3)通过一次Beta-reduction 把x相关的都用 3 替代了。然后计算 y 部分的。或者说
(/x./y.E)N 然后通过B-reduction 把 跟 /x 相关的绑定变量都用N替代了。接下来就编程了只有一个参数y的函数了。

Normal Form (范式)

A computation is finished when there are no more redexes.
A lambda expression without redexes is in normal form.
A lambda expression has a normal form iff there is some sequence of B-reduction that leads to a normal form.

Ps: 不是所有的式子都可能 B-reduce到 Normal form , 因为有些式子越约越长。。
如果你得到一个B范式,那么可以确定他是唯一的,不用担心因为规约顺序而导致不同的结果,换句话说 一个式子 不论怎么规约,只可能规约出唯一的一种范式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(/x.x)y
=> x[x->y]
=> y
------------
(/x.x(/x.x))(ur)
=>(x(/x.x))[x->ur]
=>x[x->ur](/x.x)[x->ur]
=>ur(/x.x)
-----------------------
(/x.y) ((/z.zz)(/w.w))
------ ---------------
A B
=> (/x.y) (zz)[z->/w.w]
=> (/x.y) ((/w.w)(/w.w))
=> (/x.y) (w)[w->/w.w]
=> (/x.y) (/w.w)
=> (y)[x->/w.w]
=> y
另一种顺序的算法:
(/x.y) ((/z.zz)(/w.w))
------ ---------------
A B
=> (/x.y)[x->((/z.zz)(/w.w))]
=> y

Yita-Reduction

if v is a variable , E is a lambda expression (denoting a function), and v has no free occurrence in E,
/v.(Ev) =>yita E

/x.(sqr x) =>yita sqr
/x.(add 5 x) =>yita (add 5)

The yita-reduction rule can be used to justify the extensionality of functions; namely, if f(x) = g(x) for all x, then f = g.

理解的不多,就是比如说哦 (/x.fx)y=>fy ,所以是不是 感觉到了 (/x.fx) = f 在某种意义上。但是如果 f 中存在着 x 的自由变量,那么意味着这个f 中的自由变量x 是和 外面的 /x 绑定的,那么就是说 f 会在 y 导入的时候发生变化的。那么久不一定相等了,所以才有的这么一个条件就是在 E 中不存在 v 的自由变量的时
候 /v.(Ev) =yita E

deta-reduction

if lambda calculus has predefined constants(that is , if it is not pure),rules associated with those predefined values and functions are called deta-rules.
for example:
(add 3 5) =>deta 8 ,
(not ture)=>deta flase.

Pure Lambda Calculus

这里想说的核心内容就是: Booleans, integers, and (other data structures) can be entirely replaced by functions!!!

Branch

if p then q else r can be thought as COND p q r

TRUE x y -> x
FALSE x y -> y
Then COND = /p./q./r.p q rwhere
TRUE = /x./y.x FALSE = /x./y.y

1
2
3
4
5
COND TRUE a b 记住了lambda简化必须是左结合
=> (/p./q./r.pqr)(/x./y.x)ab
=> (/q./r.(/x./y.x)qr)ab
=> (/x./y.x)ab
=> a

Boolean Logic

TRUE = /x./y.x
FALSE = /x./y.y
AND = /a./b. a b FALSE
OR = /a./b. a TRUE b
NOT = /a.a FALSE TRUE

1
2
3
4
5
6
7
8
9
10
11
and T F
(/a./b. a b (/x./y.y))(/x./y.x)(/x./y.y)
=> (/x./y.x)(/x./y.y)(/x./y.y)
=> (/y.(/x./y.y))(/x./y.y)
=> (/x./y.y)
=> F
--------------------------------------------------------------
not TRUE
(/a.a FALSE TRUE)TURE
=> (TURE FALSE TRUE)
=> FALSE

and T F

not

Church`s Numerals

Successor Function

Addition

Multiplication

Turing Complete

Factorial

Y Combinator

Recursion

SweatBuffer`s Blog

有关 Statistical Deobfuscation of Android Applications

发表于 2017-01-11 | 分类于 论文学习 |

Abstract

这篇论文提出了一个新的 恢复混淆 的方法,对象是 Android APK , 基于 概率性的’大代码’学习(probabilistic learning of large code) 的这么一种方式。

核心 Idea 是 通过上千种没有经过混淆的 Android软件,学习一种概率性的模型,然后利用这种模型去恢复新的,没见过的Android APK。

这篇文章的核心关注点是恢复 布局混淆(layout obfuscation)。
注:( 混淆技术分为几种:layout obfuscation(布局混淆), Control obfuscation(控制流混淆), Data Obfuscation(数据混淆)and Preventive Obfuscation(预防混淆).)

布局混淆

是一种曾经很流行如今也在使用但并不高深的一种混淆技术。它会重命名程序的元素,例如:classes,packages 和 methods , 使得理解程序的代码变得困难。

具体的来讲 这篇论文里:

  1. 在概率性的图像化模型中,词组化 Android APK的布局混淆问题
  2. 用丰富的 特点集和捕获 Android setting的约束条件 举例说明这个模型,既能确保语义等价和又能维持预测的高精准性。
  3. 显示如何调节 有力的推理和学习算法两者的平衡去实现 总体的预测和可拓展的概率性预测。

作者提出了他们的方法 用一款叫: DEGUARD 的工具, 使用它:

  1. 反逆向 已经使用了非常流行的 叫做 ProGuard 的良性的,开源的布局混淆工具的软件混淆过的 APK 。
  2. 推测 导入的三方库的良性 APK。
  3. 重命名经过了混淆的Android malware元素的名字。

实验结果证明 DEGUARD 实践效果非常高效:他可以恢复 79.1% 经过 ProGuard混淆过的元素名字,91.3% 经混淆的引入的第三方库。而且他在恶意软件中揭示了 处理敏感数据的 字符串解码器和类名。


阅读全文 »
SweatBuffer`s Blog

关于Android NDK开发(一) - NDK 和 JNI 简介

发表于 2017-01-11 | 分类于 Android安全 |

0x00 NDK 简介


什么是Android NDK

NDK是(Native Development Kit)的缩写,也就是开发Native的套件或者说工具集合。
NDK提供了一系列的工具,帮助开发者快速开发C/C++的动态库(.SO文件),并且能够自动将 so和java 应用一起打包成apk.


NDK存在的意义

Android 的 SDK 是基于JAVA实现,意味着基于 SDK 进行开发的应用都必须使用JAVA语言。然而 C/C++ 与 JAVA 各有各的优点何用途,为了能支持 C/C++ 谷歌在开发初期就使其 Dalvik虚拟机支持 JNI 编程方式,也就是第三方应用的 JAVA代码完全可以通过本机的(JNI)框架来调用Native动态库里的函数(.so文件里的各种函数).

也就是说 因为有 NDK 和 JNI 的支持, Android平台可以实现 “JAVA + C”的这么一种编程方式。


为什么要用 NDK (什么时候需要用 C)

  • 可以方便的使用现有的开源库。(大部分的开源库都是用 C/C++ 编写的)
  • 提高程序的执行效率。(很多要求高性能的应用使用C开发,从而提高应用程序的执行效率)。
  • 代码的保护。(apk 的 JAVA 层代码很容易被反编译,而 C/C++ 库的反汇编难度比较大)。
  • 底层程序设计。(例如,应用程序不依赖 Dalvik JAVA 虚拟机 )

0x01 JNI 简介


阅读全文 »
SweatBuffer`s Blog

生病了,2017第一场病

发表于 2017-01-10 | 分类于 个人日志 |

难受。。。昨天从下午四点开始就感觉不消化,难受。忍到了6点跟博士请假回家休养。。然后1点开始 像开了阀门的水龙头一样的在往外脱水。。。。。。。。好像好久都没这样拉肚子过了。。。。也可能是以前积攒的很多,一下再爆发力。。。


病从口入嘛 新的一年我要打理好自己的身体。。。健健康康的。。。生病的时候心里比较脆弱。。。比较难受

SweatBuffer`s Blog

Hello World

发表于 2017-01-07 |

说一下为什么开始写个人博客,是因为最近在逛博客的时候发现大家都好像再用hexo,所以感觉好神奇想尝一下味道。

第二个原因就是整理笔记的时候我就一直在想整理到网上也不错,可是一直都没动手,正好看到了hexo 所以想着试试看看。

感觉好神奇 静态的。渍渍渍。。。

SweatBuffer

SweatBuffer

孤单又灿烂的向日葵

7 日志
4 分类
10 标签
RSS
© 2017 SweatBuffer
由 Hexo 强力驱动
主题 - NexT.Pisces
本站总访问量     您是第个来到的小伙伴