Haskell

Scheme

Scala

      • for 表達式 (和一般程式語言為 for 述句不同,for 表達式有返回值) 是 map、flatMap 和 filter 的語法糖,較容易讓人理解其意圖。
         // definition、filter 和 yield 可選。yield 使得 for 表達式有返回值。
         // 此例中,返回由 n 組成的列表 (list)。
         for {
            p <- persons              // a generator
            n = p.name                // a definition
            if (n startsWith "To")    // a filter
          } yield n
      • 所謂的高階函數 (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.

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。
    • 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

ScalaTest

術語

外部連結

登录