Haskell
Scheme
Scala
-
-
-
- 所謂的高階函數 (higher-order function),就是其輸入或返回的也是函數 (first-order function)。
-
-
- flatMap: 先 Map,再 flat。Map(f) 對列表中每一個元素應用時,會得到列表 List[T],最終得到 List[List[T]]。flat 把 List[List[T]] 中的每個一元素 List[T] 打散並加以合併,最終形成單一個列表 List[T]。
-
- zipWithIndex:
scala> val abcde = List('a', 'b', 'c', 'd', 'e') abcde: List[Char] = List(a, b, c, d, e) scala> abcde.zipWithIndex res15: List[(Char, Int)] = List((a,0), (b,1), (c,2), (d,3), (e,4))
-
-
- 類型參數化 (即 C++ 的模板)
- S <: T
- S 是 T 的子類 (subclass)。
- S <: T 是否意味著 List[S] <: List[T],是協變 (covariant,協同變動) 和逆協變所要解決的問題。預設為非協變 ( nonvariant),即 List[S] 和 List[T] 之間不存在任何繼承關係。
-
-
An implicit parameter is a parameter to method or constructor that is marked as implicit. This means that if a parameter value is not supplied then the compiler will search for an "implicit" value defined within scope (according to resolution rules.) Implicit parameter resolution rules will be discussed soon.
One important restriction is that there can only be a single implicit keyword per method. It must be at the start of a parameter list (which also makes all values of that parameter list be implicit). I further understand that only the last parameter list may be implicit.
-
-
- Lexical 是特殊的 parser,取消 Inherited 中的 Tokens、Scanners 和 Parsers,可以看到 Lexical 自己定義的成員。
- 點擊 Type Members 中的 Parser,可以查看 Parser 提供的操作符 (如: ^^^)。
- 解析器 (parser) 組和子/連結符 (combinator): 解析器可以視作為一個函數,輸入為字串,輸出為一資料結構。透過組合子,Scala 可以將多個解析器合併成單一個解析器。
- Parser[T]: Input ⇒ ParseResult[T]
- T: parse 之後的結果。
- Input: 可以是單純的字元串流 (type Elem = Char) 或是從 lexer 傳回的 (Reader[Elem])。
- ParseResult[T]: 可以是 Success(result: T, in: Input),或是 Failure(errmsg: String, in: Input)。
- | combinator 會看到 call-by-name (a: ⇒ A) 的應用。
- | combinator 可以看做是函數,call-by-name 代表該參數只有在被調用到時才會執行。與其相對,一般我們看到參數傳遞的方式都是 call-by-value,即參數傳入函數之前,就會被求值 (執行)。
-
-
- 無法找到型別為 scala.reflect.ClassManifest[T] 的 implicit 參數,可以透過 import 引入正確的 implicit 參數解決。
-
- 函式欲根據容器內的元素類型,調用對映的處理函式。
- 改由 caller 根據容器內的元素類型,傳給 callee 對映的處理函式 (callee(f)(xs: List[T]))。
Functional Programming in Scala
- Ch.1
-
- 程式中的表達式可以被其運算結果取代,不影響程式語義。
- Pure function
- x 滿足 referential transparency,f(x) 亦滿足 referential transparency,則稱函式 f 為 pure。
-
- Ch.3
- List 是一種代數資料型別,本身從其它 ADT 建構而成,也可以用來建構其它 ADT。
- Ch.4
- 例外不滿足 referential transparency。Option 和 Either 是較為推薦的錯誤處理方式。例外只有在程序無法合理進行下去才被丟出。
- Option 可以想成是最多只有包含一個元素的 List,None 對應 Nil。
- 一般函式透過 lifting 可以處理 Option。
// (x: Option[A]) => x map f 可以寫成 _ map f def lift[A, B](f: A => B): Option[A] => Option[B] = (x: Option[A]) => x map f val absO: Option[Double] => Option[Double] = lift(math.abs) // 注意! 用 map/flatMap 實現不容易閱讀。把 Option 當作 List,用 for-comprehension 實現。 def map2[A, B, C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] = for { aa <- a bb <- b } yield f(aa, bb)
- xs map f,f 返回 Option。我們希望若 xs 任一元素 apply f 返回 None,則 xs map f 返回 None。
def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] = a match { case Nil => Some(Nil) case h::t => map2(f(h), traverse(t)(f))(_ :: _) }
- Either 比 Option 對錯誤可以帶有更多資訊,而不只是返回 None。Right 代表正確,Left 代表錯誤。
- 我們可以將 side-effect 包裝成一般 value 返回加以處理。Monad 就是此種概念下的產物。
- Ch.5
- 一個函式稱為 strict,代表在被調用之前,其參數均被求值 (所有參數均為 call-by-value)。我們在一般編程語言中所見到的函式皆為 strict。
- 一個函式若有 call-by-name 參數,即為 non-strict (not strict)。
// cond - call by value // onTrue, onFalse - call by name def if2[A](cond: Boolean, onTrue: => A, onFalse: => A): A = if (cond) onTrue else onFalse // onTrue 和 onFalse 在傳入 if2 時不會被求值,此時才會根據 cond 決定誰被求值。 def if2[A](cond: Boolean, onTrue: () => A, onFalse: () => A): A = if (cond) onTrue() else onFalse() // 上面代碼是此版本的語法糖。onTrue 和 onFalse 分別是 lamda function。
-
- 未被求值的表達式 (onTrue() 和 onFalse()) 又被稱為 Thunk。
-
- call-by-name 的參數如果有副作用,必須要注意該副作用可能會執行多次。
def maybeTwice(b: Boolean, i: => Int) = if (b) i + i else 0 // i 被引用兩次,由於 i 是 call-by-name,代表 i 會被求值兩次。 val x = maybeTwice(true, { println("hi"); 1 + 41 }) // 印出兩次 "hi",x = 84。 def maybeTwice(b: Boolean, i: => Int) = { lazy val j = i // j 第一次被引用,會對 i 求值,並將該值 cache 起來; 後續引用 j 不再對 i 求值。 if (b) j + j else 0 // i 被引用兩次,由於 i 是 call-by-name,代表 i 會被求值兩次。 val x = maybeTwice(true, { println("hi"); 1 + 41 }) // 印出一次 "hi",x = 84。
-
- Stream 類似 List。差別在於對其做 map 或是其它運算時,Stream 只會在 caller 需要時,對其中元素進行運算; 而 List 是對其中所有元素一次進行運算。
scala> (1 to 5).toList res10: List[Int] = List(1, 2, 3, 4, 5) // list 中所有元素立刻被求值。 scala> (1 to 5).toStream res11: scala.collection.immutable.Stream[Int] = Stream(1, ?) // ? 代表剩下部分未被求值。
- Separation of concerns。將代碼視作為資料在程序中傳遞,在適當時候可以調用。匿名函式或是 Option 都是此種概念的應用。Lazy evaluation 讓我們可以將表達式的描述和求值加以分開,只有在需要時才對表達式求值。
- Ch.6
- 為保持 referential transparency,把 side effect 或是 state 作為返回值。
Scalaz
-
$ sbt set scalaVersion := "2.12.1" set libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.2.9" set initialCommands += "import scalaz._, Scalaz._" session save console
# 下載 scalaz-core_2.12-7.2.9.jar $ scala -cp scalaz-core_2.12-7.2.9.jar