decadence

個人のメモ帳

Tips05(ドワンゴ社内 scala勉強会その1)

ドワンゴ社内 scala勉強会 2011/11/20
ttp://www.nicovideo.jp/watch/1322200991
yuroyoroさん
 「クリエイティブで高品質なScalaプログラマになるための10のTips」

Optionメソッド

opt.getOrElse(デフォルト値) 指定されたデフォルト値

中間データ

filterしてmapすると中間データが出来る
withFilterを用いると中間データが出来ない.
l withFilter {_%2 == 0} map{_*2}
副作用が無いならfilterよりはwithFilterを使うべき.

strictとnon-strict

リストやマップのコレクションにはstrictとnon-strictがある
strictは操作毎(filter,slice,…)に中間データを作成する
viewはstrictなCollectionをnon-strictに変換する->中間データが出来ない!
col map(f1) map(f2) filter(p)
=> (col.view map(f1) map(f2) filter(p)).force
forceをするとデータを作成する.
標準Collectionはstrict -> viewする事でnon-strict -> forceでデータ抽出

whileとStream

Streamは遅延評価されるnon-strictなListである
val randomStream = Stream.continually{util.Random.nextPrintableChar}
必要分だけ作成する randomStream(5) -> 0~5だけ.
whileで処理fの結果が条件pを満たすまでfの結果を集める
Stream.continually{ f } takeWhile{ p }
Streamは要素を溜め込むので,必要に応じて閉じる.
要所要所で必要なだけならIteratorを使う

for式 -> loopのための構文じゃない!!

Optionがふたつあって両方ともSomeな時に何かしたい.

def oppai(o1: Option[String], o2: Option[Int], o3: Option[Int]) = 
 o1 flatMap { v1 =>
  o2 flatMap{ v2 =>
   o3 map { v1 * v2 * v3 }
  }
 } foreach { println }
}

=>ネストが深くなるので,forで対応

def oppai(o1: Option[String], o2: Option[Int], o3: Option[Int]) = 
 for(	v1 <- o1;
	v2 <- o2;
	v3 <- o3 ) { println(v1 * v2 * v3) }

forには色々渡せる.map/flatMapが定義されているものなら何でも良い
OptionとかListとかFuture(actor !? msg)とか…

Partial Function

= 特定の引数にのみ結果が定義されている関数(定義域のようなもの)

val pf:PartialFunction[Int, String] = {
 case n if n > 0 => "hoge" * n
}
  • 1など入れると例外を投げる(MatchError)

pf.isDefinedAt(-1) -> false
例外投げるとか怖いし,ParialFunctionに定義されたliftを用いてOptionを返すようにする.
val lifted = pf.lift
lifted(2) = Some(hogehoge)
lifted(-1) = None
さらに,orElseでpf同士を合成出来る.
val pfElse = pf1 orElse pf2
pr1で定義されていなければ,pf2を呼び出してくれる.

  • >Liftで用いられている?

Self-Type Annotation

traitがmixinされる先の型の制約を指定する

trait ActorExt{
 self: Actor => // Actorに対してのみ拡張される事を指定

 def !!!(msg:Any, times:Int = 2) = {
  for(_ <- 1 to times) this ! msg
 }
}
  • Structual Subtypingを利用したLoan pattern
// type = 部分型
type Resource = { 
 read(): Int
 close(): Unit
}

trait ClosableResource{
 // このtraitはreadとcloseを持っている型にしかmixin出来ない
 self: Resource =>

 def open(f: Resource => Unit) =
  try{ f(this)} finally{ close() }
}

Phantom Types -> 状態を付与して,コンパイラに設計の意図をチェックさせる

型パラメータを利用してメタデータを付与するデザインパターン
例:SQL文がきちんとエスケープされているかを確かめる
メタデータとしての型(実装は無い,ただのマーカー)

sealed trait Escaped
trait Unsafe extends Escaped
trait Safe extends Escaped

Object Parameter{
 def create(value: String) =
  Parameter[unsafe](value) // 初期値はUnsafeになる.
}

case class Parameter[A <: Escaped] // AにはSafe,Unsafeのいずれかが入る
 private (value: String)(implicit m:Manifest[A]){

 def escape: Parameter[Safe] = 
  if(m.erasure == classOf[Unsafe])
   Parammeter[Safe](value) // escapeが呼ばれたらSafeになる
}

ここでSQLを実行するメソッドでは引数にParameter[Safe]をとる
def executeQuery(params: Parameter[Safe]*) = … // Unsafeなモノは入らない

Manifest

Type erasure // java -> 型パラメータの型情報は実行時には失われている

def is[A](a:Any): Boolean = a.isInstanceOf[A]
is[String](1) -> true

実行時にもパラメータを判定させたい
def is[A](a:Any)(implicit m:Manifest[A]): Boolean = m.erasure.isInstance(a)
is[String](1) -> false

実際実行時にチェックさせるのってどうなの...
というわけで

Generalized Type Constraintsを使う

コンパイル時にチェックさせる!
Parameter[A]がUnsafeの場合にのみescapeが呼ばれるようにチェックしたい
A =:= Unsafe を用いる!!!

def escape(implicit ev: A =:= Unsafe) = 
 Parameter[Safe](value)

条件
A =:= B AとBは同じ型でなければならない
A

embed REPL

REPLはアプリケーションに組み込む事が出来る
ILoop, IMain,…