Hatena::Groupkwfsws

kiwofusiの作業ログ このページをアンテナに追加 RSSフィード

 | 

2011-09-16

Scala勉強会 第4回 資料

09:29 | はてなブックマーク - Scala勉強会 第4回 資料 - kiwofusiの作業ログ

2011-09-16 kiwofusi

参考

目次 http://www.impressjapan.jp/books/2745

第08章ソース http://booksites.artima.com/programming_in_scala/examples/html/ch08.html

第09章ソース http://booksites.artima.com/programming_in_scala/examples/html/ch09.html

第08章 関数クロージャー Functions and Closures

大きなプログラムを小さくて管理しやすい部品に分割する。

Scalaにはいろんな関数がある。

8.1 メソッド

メソッドオブジェクトのもつ関数

cf. LongLines.scala

8.2 ローカル関数

ひとつのオブジェクトにいろんなメソッドができるとややこしい。javaだとふつうはprivateメソッドScalaだとローカル関数が使える

ローカル関数関数のなかの関数

スコープはその関数のなかだけ!

cf. LongLines2.scala

親の引数を受け継ぐことができる。

cf. LongLines3.scala

(演習)LongLines.scala→2→3

8.3 一人前の存在としての関数

第一級関数(First-class function)

第一級オブジェクト:ふつうに使えるオブジェクト

演算したり変数に代入したり。

関数リテラル

リテラル:値をソースコードのなかにべた書きしたもの

関数リテラルコンパイル時にクラスになって、実行するときに「値としての関数」にインスンス化される。

引数 x をインクリメントする関数リテラル
(x: Int) => x + 1 // (引数) => 処理内容
  var increase = (x: Int) => x + 1 // 関数を生成して変数に代入increase(10) //=> 11
ブロックに処理を書くと、最後の値が返値になる。
  var increase = (x: Int) => {
    println("We")
    println("are")
    println("here!")
    x + 1 // これを return する
  }
  increase(10) //=> 11
foreachやfilterとの組み合わせ
  val someNumbers = List(-11, -10, -5, 0, 5, 10)
  someNumbers.foreach((x: Int) => println(x))
  someNumbers.filter((x: Int) => x > 0)

8.4 関数リテラルの短縮形

関数リテラル引数の型は省略できるときもある(ターゲットによる型付け)。とりあえず省略して書いてみるといいらしい。

  val someNumbers = List(-11, -10, -5, 0, 5, 10)
  someNumbers.filter((x) => x > 0) // 型の省略
  someNumbers.filter(x => x > 0) // 括弧の省略

8.5 プレースホルダー構文

プレースホルダ:あるものをあとで別のものに置き換えるところ

  printf("%d", 500); // たぶんこれもプレースホルダ
_

引数の名前を省略できる。

_ と書いたところにあとで適切な値が穴埋めされるイメージ。

ここはあとで決めるから!というところに _ で目印をつける。

  val someNumbers = List(-11, -10, -5, 0, 5, 10)
  someNumbers.filter(x => x > 0)
  someNumbers.filter(_ > 0) // someNumbersの要素で穴埋め

  val f = _ + _ //=> エラー コンパイラが型を推論できないとき
  // 型を指定してやればおk
  val f = (_: Int) + (_: Int) //=> f: (Int, Int) => Int = <function>
  f(5, 10) //=> 15

_ を使えるのは、パラメータが多くても1回ずつしか使われないとき。

  val f = (_: Int) * (_: Int)
  f(2,3) //=> 6
  f(2) //=> エラー

8.6 部分適用された関数

「穴埋め」のイメージがより強くなる。

ちょっとづつ穴埋めできる。

関数引数に適用する」。

  val someNumbers = List(-11, -10, -5, 0, 5, 10)
  someNumbers.foreach(x => println(x))
  someNumbers.foreach(println _) // 引数の括弧を省略できる
  // ↑じつは部分適用

  def sum(a: Int, b: Int, c: Int) = a + b + c
  sum(1, 2, 3) // 引数 1,2,3 に関数 sum を適用している!

  // 部分適用:必要な引数を全部は与えずに関数を呼び出す
  val a = sum _ // 一個も引数を与えていない(三つ足りない)
  a.apply(1, 2, 3) // 三つの引数を穴埋めして a を適用する
  a(1, 2, 3) // 上と同じ意味

  val b = sum(1, _: Int, 3) // 二つ目を「あとで埋める」
  b(2) //=> 6 二つ目の引数を穴埋めして b を適用する
  b(5) //=> 9

  someNumbers.foreach(println _) // 関数呼び出しのパラメータ省略
  someNumbers.foreach(println) // 関数呼び出しの _ 省略
  // ※関数が呼び出されるときだけ(foreachの引数は関数)
  val c = sum //=> エラー
  val d = sum _
  d(10, 20, 30) //=> 60

(演習)LongLinesに使ってみる

8.7 クロージャ

よくわからなかった!

参考

[JavaScript] 猿でもわかるクロージャ超入門 まとめ - DQNEO起業日記 http://dqn.sakusakutto.jp/2009/01/javascript_5.html

ウノウラボ by Zynga Japan: 5分くらいで知るScala http://labs.unoh.net/2008/10/5scala.html

束縛(binding, bindings)

binding:値と識別子(たとえば変数)との結びつき

  val num = 100 // num という識別子に 100 という値を結びつけた

文脈(スコープ)によって binding は変わる。

  val a = 1
  {
    val a = 2 // ローカルな binding
    println(a) //=> 2
  }
  println(a) //=> 1

  val a = 1
  {
    val a = 2
    {
      println(a) //=> 2 一段上の文脈の bindng をとってくる
    }
  }
一段上(外)の binding をとってきて自分のものにする

more は自由変数

x は関数の中で bind された変数(束縛変数; bound variables)

  var more = 1
  val addMore = (x: Int) => x + more // 変数 more を取り込む
  addMore(10) //=> 11

  var more = 9999
  addMore(10) //=> 10009
  val someNumbers = List(-11, -10, -5, 0, 5, 10)
  var sum = 0
  someNumbers.foreach(sum +=  _) // 外の sum を書き換える
  // ※ (x: Int) => sum += x の短縮形
  sum //=> -11

このへんよくわからん。

  def makeIncreaser(more: Int) = (x: Int) => x + more
  val inc1 = makeIncreaser(1)
  val inc9999 = makeIncreaser(9999)
  inc1(10) //=> 11
  inc9999(10) //=> 10009

8.8 連続パラメータ

繰り返しのパラメータを受け取れる。

たとえば引数に0個以上の文字列を受け取りたいとき。

最後のパラメータに * をつける。

  def echo(args: String*) = // 0個以上のStringを受け取る
    for (arg <- args) println(arg) // args は Array
  echo()
  echo("one") //=> "one"
  echo("hello", "world!") //=> "hello", "world!"

  val arr = Array("What's", "up", "doc?")
  echo(arr) // こういう書き方はエラー
  echo(arr: _*) // arr の個々の要素を引数に渡す
  //=> "What's", "up", "doc?"f

(演習)LongLinesに使ってみる

8.9 末尾再帰

varを使わない繰り返し処理を書くには再帰を使う。

最後に自分自身を呼び出す再帰関数(末尾再帰)は最適化されるので速い。

cf. Approximate.scala

最適化された末尾再帰ではスタックフレーム(メモリ領域)は新しくつくられない。

cf. TailRecursion.scala

二つの再帰関数で呼び出しあう場合とか、名前だけ違う同じ関数で末尾再帰するときは、最適化されない。

  def isEven(x: Int): Boolean =
    if (x == 0) true else isOdd(x - 1)
  def isOdd(x: Int): Boolean =
    if (x == 0) false else isEven(x - 1)

  val funValue = nestedFun _
  def nestedFun(x: Int) { 
    if (x != 0) { println(x); funValue(x - 1) }
  }

第09章 制御抽象化 Control Abstraction

関数値(値としての関数; fuction values)を使って制御構造をつくろう。

9.1 重複するコードの削減

関数の共通部分=関数の中身、呼び出すたびに変わる部分=引数

高階関数引数関数を受け取る関数。コードを凝縮して単純化できる。

引数として関数値を使う場合、アルゴリズムの非共通部分は、それ自体他のアルゴリズムである」(???)

cf. Files1.scala

共通部分をまとめたいが、以下のように値としてメソッド名は渡せない。

  def filesMatching(query: String, method) =
    for (file <- filesHere; if file.getName.method(query))
      yield file

そこで関数値を渡す。

cf. Files2.scala

さらにクロージャを使う。

cf. Files.scala

ややこしい!

(演習)Files1→2→Files

9.2 クライアントコードの単純化

API自体に高階関数を使う(?)。

 // 負の数を含むか?
 def containsNeg(nums: List[Int]): Boolean = {
    var exists = false
    for (num <- nums)
      if (num < 0)
        exists = true
    exists
  }
  containsNeg(List(1, 2, 3, 4)) //=> false
  containsNeg(List(1, 2, -3, 4)) //=> true

  // こう書ける
  def containsNeg(nums: List[Int]) = nums.exists(_ < 0)
  containsNeg(Nil) //=> false
  containsNeg(List(0, -1, -2)) //=> true

  // 奇数を含むか?
  def containsOdd(nums: List[Int]): Boolean = {
    var exists = false
    for (num <- nums)
      if (num % 2 == 1)
        exists = true
    exists
  }

  // こう書ける
  def containsOdd(nums: List[Int]) = nums.exists(_ % 2 == 1)

ループを実行するメソッドを活用してコードを短くしよう。

(日本語いみふ。あとで読む

9.3 カリー化

  // ふつうの関数
  def plainOldSum(x: Int, y: Int) = x + y
  plainOldSum(1, 2) //=> 3

  // カリー化。二個のパラメータリスト
  def curriedSum(x: Int)(y: Int) = x + y
  curriedSum(1)(2) //=> 3
  // 二回連続で関数呼び出し

以下のようなイメージ。

  def first(x: Int) = (y: Int) => x + y // 第1の関数
  val second = first(1) // 第2の関数生成
  second(2) //=> 3

second に当たる関数プレースホルダでつくれる。

  val onePlus = curriedSum(1)_ // _ の前に空白は要らない
  onePlus(2) //=> 3

  val twoPlus = curriedSum(2)_
  twoPlus(2) //=> 4

9.4 新しい制御構造を作る

制御構造は、引数として関数をとるメソッドでつくれる。

  // 同じ操作を二回繰り返す関数
  def twice(op: Double => Double, x: Double) = op(op(x))
  twice(_ + 1, 5) //=> 7.0

繰り返される制御パターンは新しい制御構造にしよう。

さっきのfilesMatchingも制御パターン。

リソースのオープン、操作、クローズをする制御構造。関数リソースを貸し出す「ローンパターン」。 cf. WithPrintWriter1.scala

中括弧を使うと制御構造っぽくてナイス(ただし引数が一個のときだけ!)。 cf. WithPrintWriter2.scala

(演習)WithPrintWriter1→2

9.5 名前渡しパラメータ

  withPrintWriter(file) {
    writer => writer.println(new java.util.Date) // もっとシンプルにしたい
  }

cf. Assert.scala

  // これでよくね? って思う。
  def boolAssert(predicate: Boolean) =
    if (assertionsEnabled && !predicate)
      throw new AssertionError

  boolAssert(5 > 3) // いちおう動く

  var assertionsEnabled = false
  boolAssert(x / 0 == 0) // 呼び出しの前に評価されてエラー
 |