程式範例 :

小遊戲:猜硬幣正反面,並記錄”猜的次數”及”猜對次數”.”猜的次數”及”猜對次數”這兩個值會隨著玩的次數變動.所以這兩個值是 mutable 的,所以這邊的範例是使用 FP 的寫法來實作這小遊戲,並處理兩個 mutable 的值.

package com.simple.job

import scala.annotation.tailrec
import scala.util.Random

case class GameState(numFlips: Int, numCorrect: Int)
/**
  * Created by daniel on 2018/1/16.
  */
object PlayFlipCoin {

  def main(args: Array[String]): Unit = {
    val r = Random
    val s = GameState(0, 0)
    mainLoop(s, r)
  }

  @tailrec
  def mainLoop(gameState: GameState, random: Random) {

    showPrompt()
    val userInput = getUserInput()

    // handle the result
    userInput match {
      case "H" | "T" => {
        val coinTossResult = tossCoin(random)
        val newNumFlips = gameState.numFlips + 1
        if (userInput == coinTossResult) {
          val newNumCorrect = gameState.numCorrect + 1
          val newGameState = gameState.copy(numFlips = newNumFlips, numCorrect = newNumCorrect)
          printGameState(printableFlipResult(coinTossResult), newGameState)
          mainLoop(newGameState, random)
        } else {
          val newGameState = gameState.copy(numFlips = newNumFlips)
          printGameState(printableFlipResult(coinTossResult), newGameState)
          mainLoop(newGameState, random)
        }
      }
      case _   => {
        printGameOver()
        printGameState(gameState)
        // return out of the recursion here
      }
    }
  }

  def showPrompt(): Unit = { print("\n(h)eads, (t)ails, or (q)uit: ") }

  def getUserInput(): String = readLine.trim.toUpperCase

  def printableFlipResult(flip: String): String = flip match {
    case "H" => "Heads"
    case "T" => "Tails"
  }

  def printGameState(printableFlipResult: String, gameState: GameState): Unit = {
    print(s"Flip was $printableFlipResult. ")
    printGameState(gameState)
  }

  def printGameState(gameState: GameState): Unit = {
    println(s"#Flips: ${gameState.numFlips}, #Correct: ${gameState.numCorrect}")
  }

  def printGameOver(): Unit = println("\n=== GAME OVER ===")

  // returns "H" for heads, "T" for tails
  def tossCoin(r: Random): String = {
    val i = r.nextInt(2)
    i match {
      case 0 => "H"
      case 1 => "T"
    }
  }
}

程式執行結果 :

(h)eads, (t)ails, or (q)uit: h
Flip was Heads. #Flips: 1, #Correct: 1

(h)eads, (t)ails, or (q)uit: T
Flip was Heads. #Flips: 2, #Correct: 1

(h)eads, (t)ails, or (q)uit: h
Flip was Heads. #Flips: 3, #Correct: 2

(h)eads, (t)ails, or (q)uit: q

=== GAME OVER ===
#Flips: 3, #Correct: 2

Process finished with exit code 0

需求

scala API or function could perform to print out detail result step by step as well as following request ?

List( 1, 2, 3, 4 ).reduce( (x,y) => x + y )
Step 1 : op( 1, 2 ) will be the first evaluation. Start with 1, 2, that is x is 1 and y is 2
Step 2: op( op( 1, 2 ), 3 ) - take the next element 3 Take the next element 3: x is op(1,2) = 3 and y = 3
Step 3: op( op( op( 1, 2 ), 3 ), 4) Take the next element 4: x is op(op(1,2), 3 ) = op( 3,3 ) = 6 and y is 4

FP 版本

import scala.annotation.tailrec

def printOp(list: List[Int]): Int = {
  @tailrec
  def op(list: List[Int], currentSum: Int): Int = list match {
    case x :: Nil => currentSum
    case head :: seconde :: tail => {
      val tempSum = head + seconde;
      println(s"op($head,$seconde)=$tempSum")
      op(tempSum :: tail , currentSum + tempSum)
    }
  }
  op(list,0)
}

val list = List.range(1,5)
printOp(list)

Unit 版本

import scala.annotation.tailrec

def printOp(list: List[Int]): Unit = {
  @tailrec
  def op(list: List[Int], currentSum: Int): Unit = list match {
    case x :: Nil => ()
    case head :: seconde :: tail => {
      val tempSum = head + seconde;
      println(s"op($head,$seconde)=$tempSum")
      op(tempSum :: tail , currentSum + tempSum)
    }
  }
  op(list,0)
}

val list = List.range(1,10)
printOp(list)
scala> List( 1, 2, 3, 4 ).reduce( (x,y) => { println(s"op($x,$y)"); x+y} )
op(1,2)
op(3,3)
op(6,4)
res3: Int = 10
scala> List( 1, 2, 3, 4 ).reduce { (x,y) =>
    val r = x + y
    println(s"op($x,$y)=$r")
    r
  }
op(1,2)=3
op(3,3)=6
op(6,4)=10
res0: Int = 10

總結


  • 在使用 for 與 recursion 時,也同時顯現了處理一般 data 與 stream-data 的差異,因為使用 for 會有個界線值比如說 list.size() 但 recursion 時會一直遞迴直到沒有資料了,而 stream-data 會一直傳輸很適合使用 recursion 來處理,java 的 iterable 也類似.