( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

Scalaプログラマレベル(アプリケーション/ライブラリ)[翻訳]

Scalaの難易度とか学習曲線について、最近また議論されてるようですが、公式にはこのようなレベル分けになってますよ。

Scala levels: beginner to expert, application programmer to library designer | The Scala Programming Language


アプリケーションプログラマとライブラリプログラマに別れてます。アプリケーションプログラマLv2とライブラリプログラマLv1が大体同じくらいの習熟度だそうです。
なんか、これ見ると中級アプリケーションプログラマくらいならすぐになれますよね。


Scala簡単だワー。Q.E.D

Level A1: 初級アプリケーションプログラマ(Beginning application programmer)

  • Javaのような式や文を利用できる。標準的な演算子やメソッド呼び出し、条件分岐、ループ、try/catch。
  • class, object, def, val, var, import, package
  • メソッド呼び出しで中置記法を使える
  • 簡単なクロージャの利用
  • コレクションでmap, filterなどを扱える
  • for内包表記

Level A2: 中級アプリケーションプログラマ(Intermediate application programmer)

  • パターンマッチング
  • Traitを組み合わせることができる
  • 再帰、特に末尾再帰に対する理解
  • XMLリテラル

Level A3: 特級アプリケーションプログラマ(Expert application programmer)

  • 畳み込み関数(fold)。例えば、foldLeft, foldRightなど
  • Streamとその他遅延データ構造を適切に扱える
  • Actors
  • パーサコンビネータ

Level L1: 下級ライブラリ設計者(Junior library designer0

  • 型パラメータ
  • Trait
  • 遅延評価(lazy val)
  • カリー化を利用した抽象化された制御構造
  • 名前渡しパラメータ(By-name parameters)

Level L2: 上級ライブラリ設計者(Senior library designer)

  • 変異指定アノテーション(Variance annotations)を利用した共変/反変なジェネリック
  • 存在型(Existential types)。例えば、Javaワイルドカードへのインターフェースとして
  • Self type annotations とcake patternを利用したdependency injection
  • 構造的部分型(Structural types)。例えば、静的なduck-typingなど
  • map/flatmap/withFilterを新しく定義することによるfor内包表記への対応
  • 抽出子(Extractors)

Level L3: 特級イブラリ設計者(Expert library designer)

  • 事前定義(Early initializers)
  • 抽象型(Abstract types)
  • 暗黙の定義(implicit conversion/implicit parameter)
  • 高階型引数(Higher-kinded types)

ほとんど使われていないマニアックな機能「事前定義 (Early Definitions)」- Scala Advent Calendar jp 2011 Day 5

このエントリは Scala Advent Calendar jp 2011 の5日目です。

Scalaやってる人なら一度はScala言語仕様に目を通したことがあると思います無いとは言わせない。

この言語仕様を見ると、たまに思いもよらない発見があったりしますが、その中でほとんど利用されているところを見たことがない不遇な機能「5.1.6 事前定義 (Early Definitions)」について書こうと思います*1

事前定義 (Early Definitions)とは?

テンプレートを事前フィールド定義(early field definition)節で始めることができ、それにより スーパー型のコンストラクタがコールされる前に、ある特定のフィールド値を定義できます。
次のテンプレート中で

{ val p1 : T1 = e1 ...
val pn : Tn = en
} with sc with mt1 with mtn {stats}

p1, ..., pn 定義の最初のパターンは事前定義(early difinition)と呼ばれます。 それらはテンプ レートの一部をなすフィールドを定義します。 すべての事前定義は、少なくとも 1 つの変数を 定義していなくてはなりません。

Scala言語仕様

つまり、クラス宣言で、withなどでtraitを宣言する前に、特殊な初期化ブロックを記述することが許されている、というワケですが、 なんだかわかりませんね。実例をみましょう。

よくあるoverride and lazy val問題

ある程度Scalaやるとハマる問題の一つとして、スーパークラスの初期化ブロックから参照されているフィールド、メソッドを初期化ブロック内でオーバーライドするとnullになってしまう問題があります。実例を見ましょう。

trait Train {
  val announce:String
  println( announce )
}

class KQ extends Train {
  val announce:String = "ダァ!! シエリイェッス!!シエリイェッス!!"
}

Trainトレイトは、初期化ブロック内で抽象メンバーannounceをprintlnで出力します。クラスKQは、Trainトレイトを継承してannounceにあの言葉を設定します。これで、KQクラスをnewすると、みんな大好きなあの言葉が出力されるハズです。やってみましょう*2

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Train {
  val announce:String
  println( announce )
}

class KQ extends Train {
  val announce:String = "ダァ!! シエリイェッス!!シエリイェッス!!"
}

// Exiting paste mode, now interpreting.

defined trait Train
defined class KQ

scala> new KQ
null
res3: KQ = KQ@48bc9f58

アレ、nullになっとるやん。ダァがシエリィエさないじゃないですかー!?なぜぜ?

これは、継承先のTrainの初期化ブロックが呼ばれた後に、KQの初期化ブロックが呼ばれるからです。この問題を解決するには、annouceをlazy valにすれば解決できます。

class KQ extends Train {
  lazy val announce:String = "ダァ!! シエリイェッス!!シエリイェッス!!"
}


lazy valにしてもよいのですが、オーバーライドするメンバーがすでに継承先で具体的な値を与えられている場合は、lazy valでオーバーライドし直すことはできません。\(^o^)/

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Train {
  val announce:String = ""
  println( announce )
}

class KQ extends Train {
  override lazy val announce:String = "ダァ!! シエリイェッス!!シエリイェッス!!"
}

// Exiting paste mode, now interpreting.

<console>:14: error: overriding value announce in trait Train of type String;
 lazy value announce cannot override a concrete non-lazy value
         override lazy val announce:String = "ダァ!! シエリイェッス!!シエリイェッス!!"

事前定義で解決する

この問題は、事前定義を利用することでシエリイェッス!することができます。KQの宣言を、このように行います。

class KQ extends {
  override val announce:String = "ダァ!! シエリイェッス!!シエリイェッス!!"
} with Train

実行してみましょう。

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait Train {
  val announce:String = ""
  println( announce )
}

class KQ extends {
  override val announce:String = "ダァ!! シエリイェッス!!シエリイェッス!!"
} with Train

// Exiting paste mode, now interpreting.

defined trait Train
defined class KQ

scala> new KQ
ダァ!! シエリイェッス!!シエリイェッス!!
res6: KQ = KQ@49164555


ダァ!! シエリイェッス!!シエリイェッス!!

ダァ!! シエリイェッス!!シエリイェッス!!

ダァ!! シエリイェッス!!シエリイェッス!!

ダァ!! シエリイェッス!!シエリイェッス!!

ダァ!! シエリイェッス!!シエリイェッス!!

ダァ!! シエリイェッス!!シエリイェッス!!

ダァ!! シエリイェッス!!シエリイェッス!!

*1:いちおうコップ本にも書いてある

*2:余談だけどREPLでコンパニオンなどの動作確認したい場合は:paste使うとよい。:pasteで入力した範囲が同一スコープで扱われる

寝てる場合じゃねぇ!(Scala)

寝てる場合じゃねぇ! - プログラマーの脳みそ

Scalaだとコンパイラ騙さなくても言語がサポートする機能でふつーにできるし。

object NoSleep extends App {

  trait Sleepy[A] { def sleep(n:Long):Unit }
  implicit val hoge = new Sleepy[Nothing]{
    def sleep(n:Long) = println("寝てる場合じゃねぇ!")
  }

  def sleep[A:Sleepy] = try {
    implicitly[Sleepy[A]].sleep(1000)
  } catch {
    case ex:InterruptedException => ex.printStackTrace()
  }

  sleep
}

Scala2.10に文字列中の\{ _ } で式展開する機能が入るかも?

以下の記事で紹介されているが、Scala2.10の実験的な機能として、 このコミットにより、 文字列中の式展開が入るかもしれない。


Algorithmically challenged: String Interpolation on 2.10?


式展開とは、Rubyでは"foo #{hogehoge}"のように文字列中に式を埋め込んで評価した結果を元に文字列を作成すること。2.10のnightly buildを持ってきて、-Xexperimentalオプションを付けて起動すると、試してみることができる。

ozaki@mbp-4 $ scala -Xexperimental
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
Welcome to Scala version 2.10.0.r25825-b20111013020230 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_26).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val x = 1.1
x: Double = 1.1

scala> println("We have a \{ x ;2.2f}% chance of success")
We have a 1.10% chance of success

scala> "the random value is \{ util.Random.nextInt }!!"
res2: String = the random value is 289196571!!


上記のように、sprintfのオプションを合わせて指定してフォーマットを整えることができるようだ。
他にも、 このGistで紹介されているように、無名関数リテラルのように扱うこともできる。

scala> val f = "Value of \{ _ : String} is \{ _ : Int }"
f: (String, Int) => String = <function2>

scala>  f("foo", 23)
res1: String = Value of foo is 23

scala> val g : (String, Int) => String = "Value of \{ _ } is \{ _ }" // Types can be inferred
g: (String, Int) => String = <function2>

scala>  g("bar", 13)
res2: String = Value of bar is 13

scala> def show[T](t : T)(f : T => String) = f(t)
show: [T](t: T)(f: T => String)String

scala> show(23)("> \{ _ } <")        // Hole inferred as Int
res3: String = > 23 <

scala> show("wibble")("> \{ _ } <")  // Hole inferred as String
res4: String = > wibble <


ただし、現時点では残念ながらヒアドキュメント中の式展開には対応していない。

scala> """
     | ( ゚∀゚)o彡°おっぱい!おっぱい! \{ util.Random.nextInt }
     | """
res7: String = 
"
( ゚∀゚)o彡°おっぱい!おっぱい! \{ util.Random.nextInt }
"

src/compiler/scala/tools/nsc/ast/parser/Scanners.scala のprivate def getMultiLineStringLit()をいじれば対応できそうだけど……。