学んだことをなぐり書き


ローカル関数

/**
 * ruby ではメソッドのスコープ範囲がクラス(モジュール)単位だが
 * scala の関数のスコープ範囲はブロック単位である。
 * よってローカル変数のような形でヘルパー関数を定義できる。
 */
def func {
  def local = println("local")
  local
}

func
// ブロックから外れると呼び出せなくなる。
//local
local
  • 参照
rubyでメソッドの中にメソッドを定義した場合の挙動

プレースホルダー記法

/**
 * パラメーターが関数リテラル内で一度しか使われてない場合には、
 * アンダースコア(ブレースホルダー)を使って省略することが出来る。
 */
// 2個の引数をとる関数リテラルとして展開される。
val plus = (_: Int) + (_: Int)
println(plus(4, 5))

val p = (x: Any) => print(x + " ")

val numbers = (0 to 10 by 2).toList
// 通常の展開ならばこのような形になる。
numbers.foreach { x => p(x) }
println
// パラメーターを一度しか使われない場合は _ で省略することが出来る。
numbers.foreach { p(_) }
println
// さらに _ も省略することが出来る。
numbers.foreach(p)
println
9
0 2 4 6 8 10
0 2 4 6 8 10
0 2 4 6 8 10

関数の部分適用

def sum(a: Int, b: Int, c: Int) = println(a + b + c)

// 関数を変数に格納する場合は関数名の後ろに _ をつける。
// この時 def を 関数リテラル(trait FunctionN の インスタンス)に変換している。
val a = sum _
a(1, 2, 3)

// a(1, 2, 3) は実際には trait Function3 の apply() を呼び出している。
a.apply(1, 2, 3)

val b = sum(1, _: Int, 3)
b(2)
6
6
6

クロージャー

/**
 * x は関数の文脈の中で意味(関数のパラメーターでInt型)を与えられているので束縛変数(bound variables)という。
 * more は関数リテラル自身は意味を与えてないので自由変数(free variables)という。
 * 自由変数がある関数リテラルのことをクロージャーと呼ぶ
 */
var more = 1
val addMore = (x: Int) => x + more

println(addMore(30))

// more変数自体を束縛(binding)しているので more の更新後も値を参照できる。
more = 100
println(addMore(30))

// クロージャーを生成する関数
def gen(more: Int) = (x: Int) => x + more
val inc = gen(3)
println(inc(1))
31
130
4

可変長引数

// 可変長引数にするには引数の型の後ろに * を付ける。
// 配列の形になって渡される(厳密な型は違うが機能は配列とほぼ一緒)
def check(args: Int*) = println(args.getClass)
def echo(args: String*) {
  for (arg <- args) print(arg + " ")
  println
}

check()
echo()
echo("A")
echo("A", "B")
echo("A", "B", "C")

val arr = Array("A", "B", "C", "D")
// 配列を可変長引数に渡したい場合は変数名: の後にスペースを挟んで _* とする。
echo(arr: _*)
class scala.collection.mutable.WrappedArray$ofInt

A
A B
A B C
A B C D

末尾再帰

/**
 * 最後の処理として自分自身を呼び出す再帰関数を「末尾再帰」と呼ぶ
 * scala は末尾再帰の場合コンパイル時にwhileに書き直してくれる。
 */
def boom(x: Int) {
  print(x + " ")
  if (x == 0) x else boom(x - 1)
}
boom(10)
println

// @tailrec アノテーションをつけた場合末尾再帰を強制する。
// 末尾再帰になってない場合はコンパイルエラーが出る。
import scala.annotation.tailrec

// 最後の処理を自分自身で終えれてないので末尾再帰ではない
// @tailrec
def sum(x: Int): Int =
  if (x == 1) x else sum(x - 1) + x
println(sum(10))

// 末尾再帰にするには状態変数を追加する。
@tailrec
def sum2(x: Int, ret: Int = 0): Int =
  if (x == 0) ret else sum2(x - 1, ret + x)
println(sum2(10))
10 9 8 7 6 5 4 3 2 1 0 
55
55

高階関数

// パラメーターとして関数をとる関数を高階関数(higher-order functions)と呼ぶ
object FileMatcher {
  private def filesHere =(new java.io.File(".")).listFiles
  // String の引数にとり Boolean を返す関数リテラルを引数として持つ高階関数
  private def filesMatching(matcher: String => Boolean) =
    for (file <- filesHere; if matcher(file.getName))
      yield file
  // ファイル名の最後の文字列とマッチ
  def filesEnding(query: String) =
    filesMatching(_.endsWith(query))
  // ファイル名のどこかとマッチ
  def filesContaining(query: String) =
    filesMatching(_.contains(query))
  // 正規表現
  def filesRegex(query: String) =
    filesMatching(_.matches(query))
}

import FileMatcher._
println(filesEnding("scala").mkString(" "))
println(filesContaining("ca").mkString(" "))
println(filesRegex(".*").mkString(" "))
./test.scala
./camera.jpg ./test.scala
./camera.jpg ./test.scala ./waffle.avi

カリー化

// 2個以上のパラメーターリストを持つ場合カリー化(currying)と呼ばれる。
def sum(x: Int)(y: Int) = x + y
println(sum(3)(5))

// sum を呼び出したときに実際起きていることは二回の関数呼び出しの連続である。
// sum が行うであろうコードは以下のようなものになる。
def first(x: Int) = (y: Int) => x + y
val second = first(3)
println(second(5))

// プレースホルダー記法を使えば部分適用関数を作ることが出来る
// 通常の関数と違って _ の前にスペースはいらない。
// sum_ は Scala の識別子(変数名など)として認められる形だが sum(1)_ はそうではないからである。
val onePlus = sum(1)_
println(onePlus(10))
8
8
11

新しい制御構造

// 関数の引数が一つの場合括弧を中括弧にすることが認められている。
println { "braces, curly brackets" }
// 引数が2個以上の場合はコンパイルエラーになる
// "aiueo".substring { 2, 4 }
println("aiueo".substring(2, 4))

// この記法とカリー化を利用することにより新しい制御構造を作ることが出来る。
import java.io.{File, PrintWriter}
def withPrintWriter(file: File)(op: PrintWriter => Unit) {
  val writer = new PrintWriter(file)
  try {
    op(writer)
  } finally {
    writer.close()
  }
}
val file = new File("date.txt")
// 自動的に close する制御構造
withPrintWriter(file) {
  writer => writer.println(new java.util.Date)
}
println(io.Source.fromFile(file).getLines.mkString("\n"))
braces, curly brackets
ue
Sat Jul 09 17:45:06 JST 2011

名前渡しパラメーター

def myAssert(predicate: () => Boolean) =
  if (!predicate()) throw new AssertionError
// () => は冗長なので省略したい。
myAssert(() => 5 > 3)

// 名前渡しパラメーターにするには 変数名: => 戻り値の型 の形する
def myAssert2(predicate: => Boolean) =
  if (!predicate) throw new AssertionError

// 組み込みの制御構造のように使えるようになった。
myAssert2(5 > 3)

def upper(func: String => String) =
  println(func("aiueo").toUpperCase)
def upper2(func: => String) =
  println(func.toUpperCase)

def single(x: String) = x + " " + x
def double(x: String, y: String) = x + " " + y

upper(single)
// upper は文字列の引数一つの関数リテラルしか指定できない。
//upper(double)

// 一方 upper2 は返り値が文字列でさえあればどんな関数リテラルでも指定できる。
// だが upper のように関数中で処理された値を受け取ることが出来ない。
// どちらがいいかは場合による。臨機応変に使いわけることが肝心でござる。
upper2(single("aiueo"))
upper2(double("aiueo", "abcde"))
AIUEO AIUEO
AIUEO AIUEO
AIUEO ABCDE

メンバーのみ編集できます