code

2016年10月31日 星期一

Scala 筆記18 - Higher-order list functions

map and filter

一些predefined list functions接受其他的function當作參數傳入,所以這些functions當然就是higher-order functions。

1. map
應該是很常用的function,接受一個transformation function,回傳transformed過後的新的list:

def squareList(xs: List[Int]): List[Int] =
  xs match {
    case Nil => Nil
    case y :: ys => (y*y) :: squareList(ys)
  }

def squareList2(xs: List[Int]): List[Int] =
  xs.map( (x:Int) => x*x )



2. filter類別 (假設input List(2,-4,5,7,1))


partition會回傳一個pair of list,分別是filter和filterNot的結果,但是只要一次list traverse。




takeWhile的語意為take something while predicate is true,所以會從頭include element,直到第一次遇到predicate evaluation為false時停止。

dropWhile剛好跟takeWhile相反,會從頭開始不include element,直到第一次遇到predicate evaluation 為false時,把剩下的全部include。
span則適合partition一樣,回傳一個pair,各是takeWhile和dropWhile的結果,但是in single traverse。

使用map and filter

pack會把一個list中的重複相連的item打包成一個sublist:
例如: pack(List("a", "a", "a", "b", "c", "c", "a"))
會變成:List(List("a", "a", "a"), List("b"), List("c", "c"), List("a"))

def pack[T](xs: List[T]): List[List[T]] = xs match {
  case Nil => Nil
  case x :: xs1 =>
    val span = xs.span( y => y==x )
    span._1 :: pack(span._2)
}

用了這個pack真的是輕鬆愉快,因為我們在high level運作,思考模式接近人類語言了。




有了pack,我們來試試看implement run-length encoding,就是把pack的結果化為pair!

input: encode(List("a", "a", "a", "b", "c", "c", "a"))
output: List(("a", 3), ("b", 1), ("c", 2), ("a", 1))

def encode[T](xs: List[T]): List[(T,Int)] = {
  pack(xs).map( (x:List[T]) => (x.head, x.length) )
}

有沒有好簡單,好輕鬆~


Binary Reduction

把一個list reduce成某個value,例如sum a list這種的operation也是很常見。

1. reduceLeft,把一個 List(a,b,c, ... )變成 (.. (((a op b) op c) op d).... op z)

所以sum可以簡單地用reduceLeft表示:

List(1,2,3).reduceLeft( (x,y)=>x+y ) //6

Scala甚至可以再簡化 anonymouse function的語法:

List(1,2,3).reduceLeft( _ + _ )

相當簡潔。


2. foldLeft

reduceLeft不能使用在empty list上,會有compile/runtime error。可以用另一個更generalized reduction function:foldLeft

foldLeft是一個curried function,接受了accumulator z當作參數,所以可以operate在empty list上:


def sum(list:List[Int]) : Int = list.foldLeft(0)(_ + _)

事實上reduceLeft就是用foldLeft implement的。

def reduceLeft[T](list:List[T])(op:(T,T)=>T) : T =
  list match {
    case Nil => throw new Error
    case x::xs => xs.foldLeft(x)(op)
  }


3. foldRight

有foldLeft/reduceLeft 當然就可以有foldRight/reduceRight,這就是如何詮釋binary operation的方式不同:

List(1,2,3,4).reduceLeft(_-_)   // ((1-2)-3)-4List(1,2,3,4).reduceRight(_-_)  // 1-(2-(3-4))



使用Binary reduction

有了fold function,我們可以用之來implement list concatenation:

def concat[T](list1:List[T], list2:List[T]) : List[T] = {
  list1.foldRight(list2)(_ :: _)
}

因為fold有accumulator,可以視為list 1拆解成每個element與list2做binary operation,並且accumulate。


2016年10月30日 星期日

Scala筆記 17 - List implementation探究

Scala list是如何implement的? 以及他們的time complexity?

1. head

def head[T](list:List[T]) : T =
  list match {
    case Nil => throw new Error
case x::xs => x }

head很簡單就直接return第一個element,是constant time


2. last

def last[T](list:List[T]): T = list match {
  case Nil => throw new Error
case x::Nil => x
  case x::xs =>last(xs)
}

這個worst case是linear time


3. init (把list中最後一個element去掉)

def init[T](list:List[T]): List[T] = list match {
  case Nil => throw new Error
  case x::Nil => Nil  
  case x::xs => x::init(xs)
}

這個也是linear time


4. concat

def concat[T](list1:List[T], list2:List[T]) : List[T] =
  list1 match {
    case Nil => list2
    case x::Nil => x::list2
    case x::xs => x::concat(xs, list2)
  }

這個time complexity proportional to list1的長度


5. reverse

這個要用到我們之前implement的concat (一開始我忘了有implement concat,所以也可以用last/init去兜)

def reverse[T](list:List[T]): List[T] = list match {
  case Nil => Nil
  case x::xs => concat(reverse(xs), List(x))
}

time complexity: concat是linearly proportional to n, 而我們需要走過所有的element,所以是n*n complexity

所以就用我一開始想到的last和init去兜的話,就會有linear time complexity:

def reverseLinear[T](list:List[T]): List[T] = list match {
  case Nil => Nil
  case x::xs => last(list) :: reverseLinear(init(list))
}


6.  removeAt

def removeAt[T](n:Int, list:List[T]) : List[T] = list match {
  case Nil => Nil  
  case x::xs => if (n == 0) xs else x::removeAt(n-1,xs)
}

time complexity: linear


7. flatten
這要用到nested match

def flatten(list: List[Any]): List[Any] = {
  list match {
    case Nil => Nil    case x :: xs => x match {
      case y::ys => concat(flatten(y::ys) , flatten(xs))
      case nonlist => nonlist :: flatten(xs)
    }
  }
}



2016年10月29日 星期六

Scala 筆記16 - Scala built-in lists

Scala list性質

1. immutable (array是mutable)
2. recusive (array是flat)
3. non-homogeneous (每個element不一定要同一種type)
4. empty list factory method 為List(),type是Nil


如何建立一個list

我們可以用List(), List(1,2)之類的factory method來建立List instance,但那是compiler對用case keyword宣告class definition所做的companion object所給予的factory methods。

建立List instance的另一個方法是用 :: operator,就是之前說的cons:


cons operator是right associate,所以 x::y::Nil == x::(y::Nil)

事實上有:的operator都是右方operand的呼叫,所以上式實際上是 (Nil.::(y)).::(x)


List patterns for matching


仍是按照construction的模式去解構比對,以下的insert function示範了如何使用list constructor pattern matching:

def insert(x:Int, xs:List[Int]):List[Int] = xs match {
  case Nil => List(x)
  case y::ys => if (x > y) y::insert(x,ys) else x::xs
}



2016年10月28日 星期五

Scala 筆記16 - Pattern matching

接續前一篇the expression problem,Scala提供了pattern matching的機制:

一個subclass如果在宣告時加上一個case keyword,compiler會建立一個object,並且implement跟constructor一樣參數的apply method,也就是偷偷弄了一個syntatic sugar,可以做此class的factory method:

case class Number(n: Int) extends Expr {
  def isNumber: Boolean = true  def isSum: Boolean = false  def numValue: Int = n
  def leftOp: Expr = throw new Error("Number.leftOp")
  def rightOp: Expr = throw new Error("Number.rightOp")
}

Number(5) //res0: Number = Number(5)

如果沒有case keyword的話,只能用new Number(5)的方式來建立instance。


有了這個case keyword宣告的subclass,我們改寫eval function,用上match..case語法:

def eval(e: Expr): Int = e match {
  case Number(n) => n
  case Sum(e1, e2) => eval(e1) + eval(e2)
}

case後面接的sybmol就是pattern,可以match
1. constructor pattern
2. variable pattern
3. constant pattern

利用patter matching,我們Expr class hierarchy可以改寫成:

trait Expr {}

case class Number(n: Int) extends Expr {}
case class Sum(e1: Expr, e2: Expr) extends Expr {}
case class Prod(e1: Expr, e2: Expr) extends Expr {}
case class Var(x: String) extends Expr {}

def eval(e: Expr): Int = e match {
  case Number(n) => n
  case Sum(e1, e2) => eval(e1) + eval(e2)
}

def show(e: Expr):String = e match {
  case Number(n:Int) => n.toString()
  case Prod(e1: Sum, e2: Expr) => "(" + show(e1) + ") * " + show(e2)
  case Prod(e1: Expr, e2: Expr) => show(e1) + " * " + show(e2)
  case Sum(e1: Expr, e2:Expr) => show(e1) + " + " + show(e2)
  case Var(x: String) => x
}


結論就是用pattern matching可以
1. 避免low-level type matching
2. 避免type classifiers
3. 避免無謂的accessor (例如Expr 中不用再定義numValue這個method,因為在pattern-matching的過程中就能bind到這個value)


Scala 筆記15 - The expression problem

The expression problem

如果要做一個compiler,通常會有以下的class hierarchy:一個base class Expr:

trait Expr {
  def isNumber: Boolean
  def isSum: Boolean
  def numValue: Int
  def leftOp: Expr
  def rightOp: Expr
}

兩個subclasses:
class Number(n: Int) extends Expr {
  def isNumber: Boolean = true  def isSum: Boolean = false  def numValue: Int = n
  def leftOp: Expr = throw new Error(”Number.leftOp”)
  def rightOp: Expr = throw new Error(”Number.rightOp”)
}

class Sum(e1: Expr, e2: Expr) extends Expr {
  def isNumber: Boolean = false  def isSum: Boolean = true  def numValue: Int = throw new Error(”Sum.numValue”)
  def leftOp: Expr = e1
  def rightOp: Expr = e2
}


再來是一個evaluation function:

def eval(e: Expr): Int = {
  if (e.isNumber) e.numValue
  else if (e.isSum) eval(e.leftOp) + eval(e.rightOp)
  else throw new Error("Unknown expression " + e)
}

OK,如果要再加新的Expr的subclass:

class Prod(e1: Expr, e2: Expr) extends Expr // e1 * e2class Var(x: String) extends Expr // Variable ‘x’

我們需要定義多少個methods來讓這兩個subclasses加入體系?

  • 1. Expr/Number/Sum要加入 isProd 和 isVar, total = 3*2 = 6 (type classifiers)
  • 2. Prod/Var要implement Expr trait,總共7*2 = 14
  • 3, Var需要一個name method,而這個必須定義在Expr trait裡面,因為我們的eval function只接受Expr當作input參數(我們先不討論可以type casting)。total = 5 (accessors)


所以至少要implement 25個methods。

所以這邊可以看到的是,如果要加入一個新的subclass X,所有其他的class都必須要加入 isX這個判別type用的method,而如果X必須定義新的method,所有的其他class也必須定義。這在現實生活中會遇到很多問題,特別是可能我們沒有access to base traits以及other classes。


Solution1 : 改造eval,使用runtime type check


def eval(e: Expr): Int =
  if (e.isInstanceOf[Number])
    e.asInstanceOf[Number].numValue
  else if (e.isInstanceOf[Sum])
    eval(e.asInstanceOf[Sum].leftOp) +
      eval(e.asInstanceOf[Sum].rightOp)
  else throw new Error(”Unknown expression ” + e)


Scala並不鼓勵這個方式,因為這個其實是low-level thinking,不過這其實有解決了問題,我們只要修改eval增加新的isInstanceOf判斷式就好。

Solution2 : 把eval定義在Expr中,每個subclass負責自己怎麼被eval

這個稱作object decomposition,就是把某件事情的responsibility分給各自的class去定義,外人不管。

所以如果要加入新的subclass Prod和Var,並不會改動到Expr以及其subclass的定義,這解決了問題。

不過如果我們要在Expr加入一個新的method show,OOP的先天設計無法避免的要修改每一個subclass definition來加入新的method

另外一個問題是,這個show method如果需要refer到其他Expr instance(例如以下:)


這個就不能在各自subclass中的show method完成,因為欠缺global view。


Solution3 : Pattern Matching

下回分解。




2016年10月27日 星期四

Scala筆記 14 - type bounds & covariant

Type Bounds

OOP裡面有subclassing的關係,一個superclass pointer可以指向其subclass instance,因為所有能在supertype執行的動作與屬性,都能在其subclass做到。

subclassing是polymorphism的一種。

Scala提供另一種polymorphism: generics。

假設有一個function定義如下:


IntSet假設有兩個subclass: NonEmpty 以及 Empty。

所以這個assertAllPos有可能接受任一種subclass instance,也有可能回傳任一種subclass instance。

不過其實際上執行的邏輯是:如果input是NonEmpty,則output一定是NonEmpty,對Empty來說也是一樣。

所以Scala可以利用generics和“type bounds”來達成這種更正確的宣告。
“A <: B”意思是A是B的subclass



用簡單例子來看,假設有以下class hierarchy:

class A {}
class B extends A {}
class C extends A {}
class D extends B {}

def f(x: A): A = { x }
def f2[T <: A](x: T): T = x //我們接受和回傳的type T只能是A的subtype(包括A)


如果執行以下statement,則可以看出return type都是A:

f(new A)
f(new B)
f(new C)
f(new D)

res0: A = A@63f64c3c
res1: A = B@48034736
res2: A = C@4d2b401b
res3: A = D@7fdb9e4e

但是如果執行f2,可以看出input type就是return type,因為我們使用了 type parameter [T <: A]

f2(new A)
f2(new B)
f2(new C)
f2(new D)

res4: A = A@192224
res5: B = B@152f208
res6: C = C@148d260e
res7: D = D@4c13ac62

我們也可以同時設定upper and lower type bound,這樣限定可以input的type:

def f3[T >:D <: B](x: T) : T = x

//f3(new A) //不符合type boundsf3(new B)
//f3(new C) //不符合type bounds
f3(new D)


Type Covaraint

所謂type convariant是指,如果B是A的subclass,那 List[B]也是List[A]的subclass呢? Scala的compiler並不是type covariant,所以在compile time就會抓出error,而早期的Java compiler因為沒有generics,所以為了達成某些reusability,在compile time是type covariant,但是依賴runtime check。


Scala筆記 13 - function as objects

之前說Scala中的值都可以是物件,那functions呢?

事實上scala package中有所有function的trait definition,以下是一個1-parameter的trait:



所以某個function A=>B 其實在Scala compiler看來就是extends  Function1[A,B]的object而已!目前最多支援到Function22。

我們知道anonymous function是一個function literal (或說是function value),在compiler眼中其實就是extends Function1[A,B]的trait instance:



舉例來說,以下的expression會被compiler轉譯成Function1物件:



注意f是一個function value/literal。


不過Function1中的apply method是不是會被compiler再次轉譯成Function1物件,然後無限迴圈下?!

還好不會。所有的function定義都只會在被呼叫的時候轉譯成anonymous function value,然後再被轉譯成Function1的instance。這叫eta expansion。


Every object can also act like a function

這個比較奇怪,我們可以把function看成是anonymous Function1 object(如果只有一個參數),但是我們也可以把object用function語法傳遞參數與呼叫:


object Test {
  def apply(x:Int): Int = 0  def apply(): Int = 1}

Test(1) //returns 0
Test()  //returns 1


看網路上說明,這個通常是用來作為factory pattern用的,例如在某個collection class/object 中implement apply method,就可以製作不同configuration的instance,例如:





總之 f(..) 在scala中應該是一個syntatic sugar而已,實際運作上還是要實體化一個object,然後呼叫其對應的apply method。



Scala筆記 12 - Scala as a pure OOL

所謂的pure object oriented language就是所有的value都是object,用這個標準檢視Scala的話,我們可以說Scala是一個pure OOL。

首先所有JVM primitive types都有對應到的Scala classes,而Scala compiler在runtime會將這些Scala class做optimization,例如用32-bit int表示scala.Int。

舉例來說,我們可以用以下的Scala class來表示Boolean type,而且class definition中完全不會出現任何primitive data value

abstract class BooleanX {
  def ifThenElse[T] (t: => T, e: => T) : T
  def && (x: => BooleanX): BooleanX = ifThenElse(x, falseX)
  def || (x: => BooleanX): BooleanX = ifThenElse(trueX, x)
  def unary_! : BooleanX = ifThenElse(falseX, trueX)
  def == (x: BooleanX): BooleanX = ifThenElse(x, x.unary_!)
  def != (x: BooleanX): BooleanX = ifThenElse(x.unary_!, x)
}

object trueX extends BooleanX {
  def ifThenElse[T](t: => T, e: => T) = t
}

object falseX extends BooleanX {
  def ifThenElse[T](t: => T, e: => T) = e
}

乍看之下很難懂,首先要知道,我們必須用數學性質也就是function來定義這個BooleanX要怎麼behave。我們知道boolean值的定義就是truth table,事實上就是一堆opertors/functions能產生預期的結果就可以。

所以先定義一個abstract class BooleanX,裡面最關鍵的就是第一個function ifThenElse,因為其他所有operators都是建築於其上所定義,而它隱性地定義trueX和falseX object應該有的反應。

ifThenElse 可以解讀成如果this是trueX,evaluate第一個參數t,否則evaluate第二個參數e。在這個定義下去看BooleanX其他的function定義才能懂。


所以如果我們要加上一個 < operator,其expected outcome建立在這 falseX < trueX 微針,其餘為假的事實上:

def < (x:BooleanX): BooleanX = ifThenElse(falseX, x) //falseX < trueX is fact

以下程式跑起來會是我們預期的結果:

object Main extends App {
  println(trueX < trueX)
  println(trueX < falseX)
  println(falseX < trueX)
  println(falseX < falseX)

}

Test.falseX$@5ebec15
Test.falseX$@5ebec15
Test.trueX$@21bcffb5
Test.falseX$@5ebec15


Pure OO Natural Number

另一個例子是我們可以寫出pure object的自然數:

abstract class Nat {
  def isZero: Boolean = predecessor == null  
  def predecessor: Nat
  def successor: Nat = new Succ(this)
  def + (that: Nat): Nat
  def - (that: Nat): Nat

}

object Zero extends Nat {
  def predecessor: Nat = null  
  def + (that: Nat): Nat = that
  def - (that: Nat): Nat = throw new NoSuchElementException()

  override def toString: String = "0"
}

class Succ(n: Nat) extends Nat { //this is the successor of n
  def predecessor: Nat = n

  def + (that: Nat): Nat = new Succ(n + that)

  def - (that: Nat): Nat = if (that.isZero) this else (predecessor - that.predecessor)

  override def toString: String = {
    def helper(amount:Nat, acc:Int) : String = {
      if (amount.isZero) acc.toString
      else helper(amount.predecessor, acc+1)
    }
    helper(this, 0)
  }
}


object Main extends App {

  val one =  new Succ(Zero)
  val two = new Succ(one)
  val ten = two + two + two + two + two  
  val five = ten - two - one - two
  println(Zero)
  println(one)
  println(ten)
  println(five)

}


2016年10月26日 星期三

Scala 筆記11 - List & Polymorphism

List

List在Scala中的邏輯結構是長這樣:


每個Con cell有一個值,以及另一個object reference指向下一個con cell,最後一個con cell指向Nil,一個特別的cell class,代表list的結束。



 要建立一個List[T](1,2,3)的基礎方法:



上面是List的implementation,注意的點:

1. List[T]是一個parameterized trait (generalization)
2. Cons的constructor接受了 val head: T,這樣宣告方式相當於implement了 head method
3. Nil class中的head和tail method return type是Nothing,這是ok的,首先Nothing是所有class的subclass,所以當然也是T的subclass,另外Nothing的語義也符合Nil class此兩個methods的語義。




Polymorphism

中文是多型化,我們在上面的List implementation看到了兩種多型化:

1. subclassing: 所有extends List[T]的class都是一個subclass
2. generics: 使用parameterization [T]就是一種多型化



Scala 筆記10 - Binary search tree union

課堂中有個binary search tree例子的邏輯讓我感到很不直覺,但又讓人驚嘆FP能寫出優美的expression (不過看code的人可能會有點傷腦筋):

首先我們有以下兩種node定義,Empty class是leaf node:


non-leaf node則是NonEmpty class:



既然是binary search tree,就滿足left-tree elem < right-tree elem。看看NonEmpty的union方法 ((left union right) union other) incl elem

1. 首先 (left union right) 會一直往下traverse,直到遇到兩個children都是leaf node的NonEmpty object,由於left和right都是Empty object,所以(left union right) = right, (right union other) = other,而other incl elem就會把這個NonEmpty node elem給incl到sibling的tree中

2. trace過會發現,這個expression的執行path的確就是其語意,非常神奇,但是隔了幾年要我在寫出來,我還是寫不出來。

我寫成以下的版本:

def union(other: Inset): Inset = {
  val incl1 = other.incl(elem)
  val incl2 = left.union(incl1)
  right.union(incl2)
}

這算是top-down的方法,而課堂中的方法為bottom-up方法,後者對我來說比較不直覺!


2016年10月25日 星期二

Scala筆記 9 - Operators overloading & precedence

Operators

Scala的identifier命名接受符號開頭的symbol,所以要overload 某些operators例如+ - * /,都是可能的,不過這還需要Scala支援以下語法:

x.+(y) 也可以寫成 x + y,當某個function只有一個parameter的時候,我們在呼叫時可以省略.()這種語法。

所以Scala可以模擬一般數學的binary operators。

那unary operator呢? 例如我們想要有一個negate(x),能否寫成-x?

可以喔,但是要在宣告function name的時候加上 unary_ 這個symbol。

一個例子:



Operator Precedence Rules

即便我們能利用operator overloading模擬出數學算式,但是“先乘除後加減”這種隱性的規則,如何達成呢?

Scala按照一般的operator precedence訂了一套規則(越下面,套用在operator identifier上:


所以基本上已經實現“先乘除後加減”的規則。









2016年10月24日 星期一

Scala筆記 8 - 題目動動腦

雖然我很久以前就做過Coursera這門課的題目,但是這次重修了之後,發現還是有一題想了一個多小時才知道怎麼解!

如果已知有以下的function signature:

type Set = Int => Boolean
def exists(s: Set, p: Int => Boolean): Boolean

我們要怎麼implement以下的這個?

def map(s: Set, f: Int => Int): Set


首先,我在這邊提示了用exists來implement map已經是我解題後的後見之明,如果是讀者自己去解題的話,可能還要花不少時間(如果跟我一樣聰明 XD) 才能聯想到要用exists。

Set是一個characteristic function,用來判斷一個integer是否屬於某個set,所以return type為Boolean。

exists則是判斷一個set s中,是否存在某個element x,使得predicate p(x)為true? 如果沒有就回傳false。

map的定義則是如何回傳一個set,其成員為input set s的所有元素都被f transformed過?

其實如果把map如何使用寫出來,就很簡單了,這邊困擾的主要是對functional programming還很不熟悉。

假設s = {1,2,3}, f = x=>2*x, 則被mapped過的set應該要是 {2,4,6}
所以map(s,f)(2) == map(s,f)(4) == map(s,f)(6) == true

所以簡單來說map就是要去s找是否存在一個element經過f之後變成2 or 4 or 6?

講穿了很簡單,但是當初還想了不少時間咧!


2016年10月22日 星期六

Scala 筆記 7 - Currying

如何理解

Currying原本是把多參數function分拆成多個單一參數function的方法。

原本的sum可能是長這樣:

//原本的多參數function
def add(a:Int, b:Int, c:Int) : Int = a+b+c

type是 (val a: Int,val b: Int,val c: Int) => Int


//curried version
def curriedAdd (a:Int) =
  (b:Int) =>
    (c:Int) => a+b+c

type是 (val a: Int) => (Int => (Int => Int))


不過Scala也可以寫成這樣currying:

//another curried add
def curriedAdd2 (a:Int)(b:Int)(c:Int) = a+b+c

但是這個type為 (val a: Int)(val b: Int)(val c: Int) => Int
所以在呼叫的時候必須要告知compiler你是選擇性傳入前面的參數,而不是忘記!用underscore符號來表示:

curriedAdd2(5) _

通式


一個原本是多參數的function f(args1, args2, .... , argsn),其curried function為:


對第n個argument來說,其定義相當於前面有n-1次的partial application (部分formal parameters被input arguments取代),最後結果為接受argsn參數的function g:


每一次的return type都是一個function,直到最後一次才是最終的type:



Currying的好處

老實講還不明朗,有機會再來補充。

Scala 筆記 6 - Higher order functions / Anonymous functions

Higher order functions

Higher order functions簡單來說就是能接受function當作參數傳入,或是能回傳function的functions。沒有這種能力的function則稱為first order function(低層次就對了)。


為何要有higher order function的設計? 主要還是程式模組化的考量,以下面數學式為例,summation部分的邏輯是共用的,不同的是f函式的內涵:



使用了higher order function的建構,summation的邏輯就可以完全與f分開,當然f的input/output type必須被summation規範,相當於f要follow某固定signature,如果對一般imperative language來說的話,interface可以來模擬higher order function這部分的好處。

見下圖:


所以我們可以任意替換f來建立不同summation:


id, cube, fact各為符合 Int => Int signature的function。




Anonymous functions (function literals)

anonymous function(或稱為function literals)單純只是為了方便出現的語法,他簡化被當作參數傳入的function的宣告語法,如果這個function的body做了很少的是(例如只是把參數+1),此時不用再進行獨立的宣告,增進可讀性,簡化程式碼。

例如上段中各種sumXXX可以改寫成以下:


以上未標明各參數的types,因為compiler會infer。

比較明確的function literals寫法還是把各個參數的type標示出來:


一個簡單的使用範例:

def sum(f: Int => Int, a: Int, b: Int): Int = {
  def loop(a: Int, acc: Int): Int = {
    if (a > b) acc
    else loop(a+1, f(a)+acc)
  }
  loop(a, 0)
}

print( sum( x=>x, 1, 10) )


2016年10月21日 星期五

Scala 筆記5 - tail recursion & counting change problem

Tail Recursion

這篇文章把tail calls 和 tail recursion講得很清楚:
http://www.ruanyifeng.com/blog/2015/04/tail-call.html

tail recursion的好處就是可以讓stack frame消耗數目維持跟iteration loop一樣。

雖然Scala 會對tail recursion做optimization,但是tail recursion不一定是最好的coding style,因為除非像是計算factorial那樣會累積非常多stack frames (deep recursion chain),造成可能的stack overflow, 否則基本上還是要以程式碼的簡潔和效率為主,通常不用寫成tail recursion。

一個非tail recursion的factorial function如下:

def factorial(x:Int): Int = {
  if (x == 0)
    1
  else    x*factorial(x-1)
}

如果改寫成tail recursion版本,我們會需要一個accumulator來達成。

def facTail(x:Int): Int = {
  def loop(x:Int, acc:Int): Int = {
    if (x == 0)
      acc
    else      loop(x-1, acc*x)

  }

  loop(x, 1)

}

Counting Change Problem

有各種幣值的零錢一袋,幣值coins以List[Int]表示,請問任意金額n,有幾種這些幣值的組合方式?此function的signature如下:

def countChange(money: Int, coins: List[Int]): Int
老實說,這個問題我寫過兩次,一次就是之前上的Coursera Scala課程,另一次是自修SCIP 使用Scheme的時候,結果我現在還是無法想出解題邏輯,慚愧殘念。
SCIP原文的解題邏輯如下:
"To see why this is true, observe that the ways to make change can be divided into two groups: those that do not use any of the first kind of coin, and those that do. Therefore, the total number of ways to make change for some amount is equal to the number of ways to make change for the amount without using any of the first kind of coin, plus the number of ways to make change assuming that we do use the first kind of coin.  But the latter number is equal to the number of ways to make change for the amount that remains after using a coin of the first kind. 
所以要從coins中組合出n的方法數等於下兩者的和
(1) 使用了幣值x的方法數
(2) 沒使用幣值x的方法數
如果寫成集合來看:
(1) = | { {x,... }, {x,...}, ...} |
(2) = |total set| - (1)
由於(1)的set是可以組合出n的所有組合,如果把每個組合中的x拿掉一個,並不會改變這個set的數目,而這個新的set其實就是組合出(n-x)的所有組合!不了解這個關鍵就無法寫出recurrence。
因為這個recurrence在(1)的時候就會把n減少,如果n在某個回合變成零,代表達成一次幣值組合,這就是我們的base case。完整implementation如下:

def countChange(money: Int, coins: List[Int]): Int = {

  if (coins.isEmpty || money < 0)
    return 0
  if (money == 0)
    return 1
  countChange(money - coins.head, coins) + countChange(money, coins.tail)
}

Scala 筆記4 - Lexical Scope

Nested Functions and Blocks

Scala支援nested function語法,這樣有較好的encapsulation。nested functions須要放入某個block中,block是一個由{ } 包圍住的expression,所以可以作為一個function body,因為expression一定要return value。

以下是利用Scala來實作牛頓法逼近平方根:



上圖中block內的functions會遮蔽block外的同名functions,相當於宣告一個local function,其scope範圍與一般local variables一樣。

所以上圖的實作可以把裡面nested functions的x parameter去掉,因為所有的nested functions都在最外層x的lexical scope內:


如此就乾淨多了。












2016年10月20日 星期四

Scala 筆記 3 - conditionals以及value definition

Conditionals

scala中的條件式if-else語法和Java一樣,但是"then part"只能是expression (某個符合function return type的value),不能是一個statement(不一定有return value)。

所以在寫作邏輯上會和imperative programming很不一樣。


如果利用if-else來實作&&和|| operator:

def and(x:Boolean, y => Boolean): Boolean = if (!x) false else y

def or(x:Boolean, y => Boolean): Boolean = if (x) true else y

注意y不一定需要evaluate,所以我們pass y by name。


Value definition

之前說value和function沒有什麼不同,可以把value看成是常數函式,也就是substitution model中最後的產物。

既然value是一個常數函式,那一但定義了,應該只要在定義時被evaluate一次就好,也就是強制它擁有call by value的特性。Scala提供了宣告value的語法:


y在之後使用到時,不需要再從square(2) evaluate起,而是直接以4代入參數。
所以要注意如果利用val宣告一個infinite loop,會在當下就進入無窮迴圈!!!!


一般的function可以視作為call by name的特性,因為只有在expression/statement中使用到才會被evaluate,而且可以被evaluate多次即便可能有相同的input。








Scala 筆記 2 - 定義一個function

定義一個function

在Scala中,定義一個function/value是以以下的語法:

def power(x: Double, y: Double): Double = ...

上面的語義是說,我們定義 power(x: Double, y: Double) 為一個Double value,如果以數學函式的觀點就是把(x,y) map到一個Double value,Double也就是此function的return type。

常數也是一個function,數學中有常數函式 (不論input是什麼,永遠output同一個數),所以我們說value和function其實是同一個意義。


function 解析順序

了解一個function在runtime怎麼解析對寫FP來說很重要,如果弄不清楚的話會搞得相當頭大,也很難寫的精簡。

看以下範例:和imperative function解析是一樣,先把sumOfSquares的兩個參數替換(substitute)成兩個values,然後分別代入下一個函數式square得到值,之後再往下帶入加減乘除這類的function,一步步替換,直到不能在替換為止。



以上的邏輯稱作substitution model,這是我們在計算一個代數式會用的邏輯。

不過substitution model只能用在沒有"side effect"的函數,什麼意思呢?
以Java的"++" operator來說,以下的expression並不能用substitution model重寫出來:

x++;

x++ 近似於定義 def y(x:Int) : Int = x+1,但是你無法透過任何substitution過程來把x的值換成x+1,除非Scala允許mutable data type。


按照substitution model的邏輯,以下的定義是一個無窮迴圈:

def loop:Int = loop

(1) 把左邊的loop sybmbol換成右邊的定義,也就是loop
(2) 把(1)的結果 loop替換成 其定義,又是loop
...
..
.

call by value / call by name

substitution model的過程是把所有parameter替換成value之後才能代入下一個function,所以就是我們熟悉的call by value的邏輯。

那call by name是用什麼解析邏輯呢?如果傳入的參數不先替換成value,而是原封不動地直接帶入function,這樣的邏輯就會產生call by name的結果。


從上面的解析步驟,我們可以看到 2+2 這個expression不會被evaluate,直到再也沒有非primitive function可以代入時,才會被evaluate。


  • call by value只會對同一個expression evaluate一次,例如2+2這個expression。
  • call by name只在真正用到(reference到,所以名之)這個expression的時候才去evaluate,但是有可能evaluate超過一次,例如2+2這個expression在square function中被evaluate了兩次。

Scala default使用call by value來解析參數,因為可以免除掉無謂的計算,速度差異可能達到exponential等級,但是也提供了用call by name解析參數的語法,例如以下的參數y將會
用call by name解析:

def f(x:Int, y => Int): Int




一個範例算算看

def test(x:Int, y:Int): Int = x * x

(1)
****call by value
test(2, 3)
= 2 * 2
= 4

***call by name
test(2,3)
= 2 * 2
= 4



(2)
****call by value
test(3+4, 3)
= test(7, 3)
= 7 * 7
= 49

***call by name
test(3+4, 3)
= (3+4) * (3+4)
= 7 * (3+4)
= 7 * 7
= 49


(3)
****call by value
test(7, 2*4)
= test(7, 8)
= 7 * 7
= 49

***call by name
test(7, 2*4)
= 7 * 7
= 49


(4)
****call by value
test(3+4, 2*4)
= test(7, 2*4)
= test(7, 8)
= 7 * 7
= 49

***call by name
test(3+4, 2*4)
= (3+4) * (3+4)
= 7 * (3+4)
= 7 * 7
= 49


Scala 筆記1 - 什麼是functional programming?

這是我在Coursera上Scala Specialization的筆記,與同好共享。

目前存在至少三種programming paradigms (paradigms可以說是觀點或是思考模式)。

1. logic programming: 包含prolog之類的語言。
2. imperative programming: 主流的c, c++, java等。特色就是mutable variables, 以及其衍生而來的loopable control structures (e.g. for loop)
3. functional programming: erlang, scala等。

Imperative paradigm主要是Von Neumann machine架構下的運算邏輯,例如以下的對應:


上面的邏輯會發展出word by word大小的資料結構,如果想要scale up來處理更大的複雜問題,自然而然地就要往高階抽象單元發展,例如collections, strings, polynomials,...。

高階抽象單元若是依賴數學性質去建立的話,就會產生一個imperative programming設計上達不到的矛盾: immutability。數學中並不存在某個operator, 使得一個物件在改變某些屬性後仍能被稱做同一物件的邏輯,例如某個多項式ax+b=c,如果改變了a係數為a+1,我們不會說這改變後的多項式仍是未改變前的同一實體(雖然只是係數改變)。但是一個模擬多項式的Java class的確是可以改變其data member (例如某個叫做a的float),而在記憶體中仍然是同一個instance。

以上的行為將會破壞數學理論及其衍生出來定律的可靠性,所以不適合拿來當作依照數學理論來建立的高階抽象單元。

所以按照數學理論設計的functional paradigm, 其中一個主要特點就是data is immutable! 這一點是和imperative paradigm最不同之處。functional programming顧名思義就是要focus在function設計上,此處的function是指數學上的函數,也就是思考如何把某個input map到某個output,此input甚至可以是另一個函數,所以在程式語言設計上,function也是一個value,可以被當作參數傳遞於任何function中,這樣的在imperative language沒有的"特權",可以稱做first-class citizens。

某個value其實就是某個function mapping出來的結果,只要有input就會有output,所以把function看作value來任意傳遞與宣告,在functional programming的邏輯架構中不會奇怪。

另外,上了今天的課我才知道,原來我很久以前就寫過functional programming language啊!!!
那就是smalltalk,雖然我已經忘了是怎麼一回事了....



(目前在coursera/edx 玩過的包括 SML, OCaml, Scala. 自學的有Scheme)。