普段Scalaを使っていて、この機能は便利、よくできていると感じているところをご紹介します。
- plenvみたいなやつあるの?
- ライブラリはどうやっていれるの?
- cpanfile/Gemfileみたいなのあるの?
- 自分で書いたコードのビルドはどうやるの?
- がんばってJDKをインストールしよう!
- そしてsbtをインストールしよう!
$ brew install sbt
name := "hello"
scalaVersion := "2.10.3"
object Hello {
def main(args: Array[String]) = println("Hi!")
}
$ sbt
> run
[info] Compiling 1 Scala source to ...
[info] Running Hello
Hi!
[success] Total time: 27 s, completed 2014/09/13 11:37:35
>
- Scalaの処理系(jarファイル)がインストールされる
- クラスファイルやリソースファイル(画像とか)が入ってるzipアーカイブ
- META情報も入っていて、どのクラスが実行できるとか書ける
- Scalaの処理系のjarファイルあれば原理上は実行できる
- sbt でconsoleを実行するとScalaのreplに入れる
- いろいろ試せて便利
- 電卓にも使えるぞ! (ただしJVMが起動するのが遅い)
$ sbt
> console
scala> 1 + 1
res0: Int = 3
scala>
val x = 1 // 再代入できない
var y = 2 // 再代入できる
x = 5 // valだとエラーになる
y = 6 // varだと再代入できる
- 再代入できない変数を明示的に宣言できる!
- Perlでも変数を使いまわすと理解し難いコードができるので、やらない
- ブログチームのコーディング規約でやめようって書いてある
- valって書いとくと再代入されてないことが確実なので安心
- var使うとレビューでめっちゃ怒られる
- めっちゃ便利なswitch文みたいなの
- 最近の言語だとだいたい入ってる
- match式を使う
- 式なので値を返すよ
val msg = x match {
case 0 => "0だ!!"
case 1 => "1だ!!"
case _ => "それ以外だ!!"
}
println(msg)
val msg = x match {
case 0 | 1 => "0か1や"
case n if n < 100 => "100 以下やね"
case _ => "それ以外だ!!"
}
println(msg)
あとででてくる
- コレクションはいろいろついてくる
- List/Array/Map/Set
- 細かいのを数えるといろいろある
- コレクションには便利メソッドがめっちゃついてくる
- List::Util + List::MoreUtils よりいろいろできる
- map/filter/flatMap/find/findAll/reduce
- take/drop/exists/sort/sortBy/zip/partition
- grouped/groupBy
- やまほどある
val list = List(1,2,3,4,5,6,7,8,9) // こういう感じでリスト作れる
list.map { n => // 関数リテラル
n * n
}.filter { n =>
n % 2 == 0
}.sum
list.grouped(2).foreach { ns => println(ns) }
list.groupBy { n => if (n % 2 == 0) "even" else "odd" }
list.reduce { (x, y) => // 引数リスト
n * n // 最後の式が返値
}
- mapもだいたい普通な感じ
- 便利メソッドはいろいろ使える
- とりあえず紹介だけ
val urls = Map(
"www" -> "http://www.hatena.ne.jp",
"b" -> "http://b.hatena.ne.jp",
"blog" -> "http://hatenablog.com"
)
m.get("b") // → Some("http://b.hatena.ne.jp")
m.get("v") // → None
- あるかないかわからない値を表現できる型
- undefチェックするの忘れてた! というのがなくなるすぐれもの
- Option[+A]型
- Some(x)という値
- x は +A型の値 (Option[Int]型だったらInt型)
- Noneという値
- Some(x)という値
- Someの中身を使うには明示的に取り出す操作が必要
- 値があるときはSomeに値をくるむ
- ないときはNone
Some("hello")
Some(1)
Some({ () => 5 * 3 })
None
val urls = Map(
"www" -> "http://www.hatena.ne.jp",
"b" -> "http://b.hatena.ne.jp",
"blog" -> "http://hatenablog.com"
)
val bUrl = urls.get("b") // Some("http://b.hatena.ne.jp")
val vUrl = urls.get("v") // None
// 方法1 (bad)
bUrl.get // SomeかNoneか無視してとりだす/使ってたら怒られる
vUrl.get // ランタイムエラー!!
// 方法2
bUrl.getOrElse("no url") // Someだったら中身の値
vUrl.getOrElse("no url") // Noneだったらデフォルト値
// 方法3
bUrl match { // パターンマッチでそれぞれ処理する
case Some(url) =>
s"bのURLは $url ですぞ"
case None =>
"no url"
}
- getは使ったらアカン
- 特別な操作なしでは値が使えない
- 値をちゃんと取り出したどうかは型でチェックされる
- 値の構造でマッチング
- Someの場合中身の値がurlにはいる!
- 対象が unapply メソッドを実装していると、こういうパターンマッチができる
- case classというのを使うと簡単に作れる
val bUrl = Some("http://b.hatena.ne.jp")
bUrl match {
case Some(url) =>
s"bのURLは $url ですぞ"
case None =>
"no url"
}
- 必ずundefチェックできるとかいうけど、Noneのcase忘れるのでは
scala> bUrl match {
| case Some(url) =>
| s"url is $url"
| }
<console>:10: warning: match may not be exhaustive.
It would fail on the following input: None
bUrl match {
- コンパイラが警告出すぞ!
val bUrl = Some("http://b.hatena.ne.jp")
bUrl.filter { url => isHatenaUrl(url) } // trueならそのまま, flseならNoneになる
bUrl.exists { url => isHatenaUrl(url) } // Someなら条件式の結果, Noneならfalse
bUrl.map { url => getContent(url) } // Someなら値を変換, Noneならそのまま
- 実はListに使えたメソッドは割りと使える
- 要素が1つしか無いListだとみなせなくもない
findEntryBy(entryId) // Option[Entry]
findUserBy(userId) // Option[User]
- entryのauthorを取得したい
- entryがSomeのときだけauthorを探して、authorが見つかったらSomeを返したい
- どちらかが見つからなかったらNone
fincEntryBy(entryId).flatMap { entry =>
findUserBy(entry.authorId)
}
- flatMapを使うとOptionを返すメソッドを次々と繋げられる
- 全部がSomeだったときの処理が書ける
- ただしネストしていくと読みづらい...
- しかし読みやすくする技がある
fincEntryBy(entryId).flatMap { entry =>
findUserBy(entry.authorId).flatMap { user =>
findUserOptionBy(user.id).flatMap { userOption =>
findUserStatusBy(user.id).map { userStatus =>
// 全部見つかった時の処理を書ける
makeResult(entry, user, userOption, userStatus)
}
}
}
}
for ( i <- List(1,2,3,4,5,6,7,8,9) ) {
println(i.toString)
}
for ( i <- (0 until 10) ) {
println(i.toString)
}
- foreach メソッドが実装されてるとforのイテレーション対象にできる
- 以下とほぼ一緒
List(1,2,3,4,5,6,7,8,9).foreach { i =>
println(i.toString)
}
val pows = for ( i <- List(1,2,3,4,5,6,7,8,9) ) yield i * i
- map メソッドが実装されてるとyieldを使って値を生成できる
- 以下とほぼ一緒
val pows = List(1,2,3,4,5,6,7,8,9).map { i =>
i * i
}
for ( i <- List(1,2,3,4,5,6,7,8,9) if i % 2 == 0 ) {
println(i.toString)
}
- filter メソッドが実装されてるとifでfilterできる
- 以下とほぼ一緒
List(1,2,3,4,5,6,7,8,9).filter { i =>
i % 2 == 0
}.foreach { i =>
println(i.toString)
}
for (
i <- List(1,2,3,4,5,6,7,8,9);
j <- List(1,2,3,4,5,6,7,8,9)
) {
print((i*j).toString + " ")
}
- filterメソッドが実装されているとこうかける
List(1,2,3,4,5,6,7,8,9).foreach { i =>
List(1,2,3,4,5,6,7,8,9).foreach { j =>
print((i*j).toString + " ")
}
}
val kuku = for (
i <- List(1,2,3,4,5,6,7,8,9);
j <- List(1,2,3,4,5,6,7,8,9)
) yield i * j
- flatMapとmapが実装されているとこうかける
val kuku = List(1,2,3,4,5,6,7,8,9).flatMap { i =>
List(1,2,3,4,5,6,7,8,9).map { j =>
i * j
}
}
- ならべるとflatMapをネストしてるのと一緒
val kukuku = for (
i <- List(1,2,3,4,5,6,7,8,9);
j <- List(1,2,3,4,5,6,7,8,9);
k <- List(1,2,3,4,5,6,7,8,9);
) yield i * j * k
val kukuku = List(1,2,3,4,5,6,7,8,9).flatMap { i =>
List(1,2,3,4,5,6,7,8,9).flatMap { j =>
List(1,2,3,4,5,6,7,8,9).map { k =>
i * j * k
}
}
}
- flatMapでどんどん処理をつなげていく... どこかで聞いたことがある..
- 以下のようにOptionを返す関数をflatMapでどんどん繋げられるんだった
val result = fincEntryBy(entryId).flatMap { entry =>
findUserBy(entry.authorId).flatMap { user =>
findUserOptionBy(user.id).flatMap { userOption =>
findUserStatusBy(user.id).map { userStatus =>
// 全部見つかった時の処理を書ける
makeResult(entry, user, userOption, userStatus)
}
}
}
}
- つまりforを使うとこうかける!!
val result = for (
entry <- fincEntryBy(entryId);
user <- findUserBy(entry.authorId);
userOption <- findUserOptionBy(user.id);
userStatus <- findUserStatusBy(user.id)
) yield makeResult(entry, user, userOption, userStatus)
-
読みやすい!
-
値が全部SomeならSome(makeResult(entry, user, userOption, userStatus))
-
いずれかの値がNoneならNone
-
for式を使うとある型の値のつなげて処理していくコードを綺麗に書ける
- どうつなげられるかはflatMapの実装による
- Option ならSomeのときは処理がつながるけどNoneならとまる
- OptionやListはモナド
- モナドが要求する関数
- return
- 値をOptionやListに包む関数 => Some("hoge"), List(1,2,3)
- bind
- OptionやListを返す関数を組み合せる関数 => flatMap
- これらがモナド則を満たすことが必要
- return
- for式はHaskellのdo式に相当する
- OptionやList以外にもEitherやStateなどの強力な抽象化メカニズムをモナドとして使えるぞ
- 詳しくは、すごいHaskellたのしく学ぼう! を読もう!
- Scalazというのを使うとより強力なモナドや記法が使えるようになる
- Haskellを使ったらよいのではってなるけど、あったら便利
class Cat(n: String) { // コンストラクタ
val name = n // フィールド
def say(msg: String) : String = {
name + ": " + msg + "ですにゃ"
}
}
println( new Cat("たま").say("こんにちは") )
class Tiger(n: String) extends Cat(n) { // 継承
override def say(msg: String) : String = { // オーバーライド
name + ": " + msg + "だがおー"
}
}
println( new Tiger("とら").say("こんにちは") )
- クラスの定義に対して1つしか存在しないオブジェクトを簡単に定義できる
- classで定義したクラスと同名でobjectを定義するとクラスメソッドを定義できる特別なobjectになる
- コンパニオンオブジェクト
object CatService {
val serviceName = "猫製造機"
def createByName(name :String): Cat = new Cat(name)
}
val mike = CatService.createByName("みけ")
mike.say("ねむい")
object Tama extends Cat("たま") {
override def say(msg: String) : String =
"たまにゃー"
}
object Cat { // すでにあるクラスと同じ名前だと
// 定義されたメソッドはクラスメソッドのように振る舞う
def create(name: String) : Cat = new Cat(name)
}
val hachi = Cat.create("はち")
- クラスに似てる
- データ構造を定義しやすくカスタマイズされてる
- いくつかのメソッドがいい感じに生える
- toString/hashCode
- apply/unapply (コンパニオンオブジェクトに)
case class Cat(name: String) { // nameは勝手にfieldになる
def say(msg: String) :String = ???
}
val buchi = Cat("ぶち") // newなしで気楽に作れる
buchi match {
case Cat(name) => // パターンマッチで使える
"name of buchi is " = name
}
- 実装を追加できるインターフェース
- Scala では設計のベースになるクラスの構造を構築するのによく使われる
- Rubyのモジュールっぽいやつ
class Cat(n: String) {
val name = n
}
trait Flyable {
def fly: String = "I can fly"
}
// withで継承する/多重に継承できる
class FlyingCat(name: String) extends Cat(name) with Flyable
new FlyingCat("ちゃとら").fly
// Scalaで定義されているOrdered traitを実装すると比較できるように
class OrderedCat(name: String) extends Cat(name) with Ordered[Cat] {
def compare(that: Cat): Int = this.name.compare(that.name)
}
new OrderedCat("たま") > new OrderedCat("みけ")
new OrderedCat("たま") < new OrderedCat("みけ")
- 暗黙の型変換
- 暗黙のパラメータ
- 暗黙と聞いていいイメージはないが使いドコロをまちがわないことでいろいろできる
def stringToInt(s:String) : Int = {
s.toInt
}
"20" / 5 // 型エラーになる
stringToInt("20") / 5 // ok
implicit def stringToInt(s:String) : Int = { // implicit!!
Integer.parseInt(s, 10)
}
"20" / 5 // 計算できる!!
- 要求する型が得られない時、スコープ中のimplicit宣言を調べて自動で変換する
- / の右側ところには数値型しか現れないはずなのに文字列があるのでimplicitで定義した変換関数が呼ばれた
- とはいえこれは異常なパターンでこんなことはしない...
- 既存の型を拡張するように見せられる(pimp my libraryパターン)
class GreatString(val s: String) {
def bang :String = {
s + "!!!!"
}
}
implicit def str2greatStr(s: String) : GreatString = {
new GreatString(s)
}
"hello".bang // まるでStringに新しいメソッドが生えたように見える
- 予め暗黙のパラメータを受け取る関数を定義
- 呼び出し時にスコープ中のimplicit宣言を調べて自動的に引数として受け取る
def say(msg: String)(implicit suffix: String) =
msg + suffix
say("hello")("!!!!!") // => hello!!!!! 普通に読んだらこう
implicit val mySuffix = "!?!?!!1" // 暗黙のパラメータを供給
say("hello") // => hello!?!?!!1
- コンテキストオブジェクトを引き回す
def findById(dbContext: DBContext, id: Int) = ???
def findByName(dbContext: DBContext, name: String) = ???
val dbContext = new DBContext()
findById(1, dbContext)
findByName("hakobe", dbContext) // 毎回DBコンテキストを渡す必要があってだるい
def findById(id: Int)(implicit dbContext: DBContext) = ???
def findByName(name: String)(implicit dbContext: DBContext) = ???
implicit val dbContext = new DBContext()
findById(1)
findByName("hakobe") // dbContextは暗黙的に供給されるのでスッキリ
- 型クラスを実現できる
- アドホック多相な関数を実装するときに使う
- 関数の定義を型ごとに切り替えられる
- スコープごとに切り替えることができる (アドホック)
- 型のソースコードへのアクセス権限がなくても実装を提供できる (アドホック)
- くわしくは記事を読もう
// http://nekogata.hatenablog.com/entry/2014/06/30/062342 より引用
trait FlipFlapper[T] {
def doFlipFlap(x:T):T
}
implicit object IntFlipFlapper extends FlipFlapper[Int] { // ...(1)
def doFlipFlap(x:Int) = - x
}
implicit object StringFlipFlapper extends FlipFlapper[String] { // ...(2)
def doFlipFlap(x:String) = x.reverse
}
def flipFlap[T](x:T)(implicit flipFlapper: FlipFlapper[T])
= flipFlapper.doFlipFlap(x) // ...(3)
flipFlap(1) // => -1
flipFlap("string") // => "gnirts"
- 紹介した機能
- varとval
- パターンマッチ
- コレクション
- Option型
- for式
- クラス定義
- implicit
- 対象を正確にモデリングできる
- プログラマががんばってコードを読まなくても意図がわかる
- コンパイラがエラーをチェックできる範囲が増える
- 正直いろいろできすぎて、複雑な感じはある
- とはいえライブラリ作者くらいしか気にしなくて良い機能とかも結構あるので意外と普通に書いてたらいい
- まだまだおすすめポイントはあるぞ! 無限に勉強できる!!
- クロージャの便利な作り方
- 型の合成
- 型のパラメータ化
- 関数の部分適用
- 部分関数
- 便利なモナド
- 型の便利さ実感
- renameリファクタリングしてみる
- コンパイル時間体験
- mackerelのよくあるコードのご紹介
- playframeworkの世界をみる
- slick(ORM)の世界をみる
- scalazの闇の世界をみる