CA Scala Study Part1

Wataru Fukunaga

Who am I?

Wataru Fukunaga

@wataru420 | amebaId:wataru420

Cyberagent Application Engineer



Scalaコップ本輪読会用の資料です。



Slides available at http://goo.gl/SVfmP

I use this: github.com/kig/BasicsOfThreeJS

コップ本輪読会

第一章〜第三章まで

Agenda



第一章スケーラブルな言語

A Scalable Language

A Scalable Language

Scalaの名前の由来

ユーザーの求めに応じて成長できるよう設計

Javaプラットフォーム上で動くので間口も広い



Scalaはオブジェクト指向と関数型プログラミングの概念を、静的な型付けを行う言語にまとめ上げたものである。



プログラマーとともに成長する言語

about Map


var capital = Map("US" -> "Washington", "France" -> "Paris")
capital += ("Japan" -> "Tokyo")
println(capital("France"))
	

Perl、Python、Rubyなどのスクリプト言語のような感じ



HashMap

さっきのをHashMapに書き換えてみます


import scala.collection.imutable.HashMap

var capital = HashMap("US" -> "Washington", "France" -> "Paris")
capital += ("Japan" -> "Tokyo")
println(capital("France"))
	

簡単ですね

TreeMap

さっきのをTreeMapに書き換えてみます


import scala.collection.immutable.TreeMap

var capital = TreeMap("US" -> "Washington", "France" -> "Paris")
capital += ("Japan" -> "Tokyo")
println(capital("France"))
	

簡単ですね

SynchronizedMap

SynchronizedMapトレイトでスレッドセーフに


import scala.collection.mutable.{ HashMap, SynchronizedMap }

var capital = new HashMap[String,String] with 
                             SynchronizedMap[String,String]
capital += ("US" -> "Washington", "France" -> "Paris")
capital += ("Japan" -> "Tokyo")
println(capital("France"))
	

このようにScalaは柔軟で書きやすく、そして必要に応じて細かく修正していける

新しい型を作れる言語

Type


Scalaは型を簡単に作れる

Scalaは型を使った条件分岐ができる

Scalaは型に関する機能が豊富

型に積極的な意味をもたせられる



メソッドやメンバ変数を束ねるだけのものではない!

BigInt


javaのBigIntegerはint型とはだいぶ異なる



ScalaではこのBigIntegerをラップしたBigIntクラスがある

*などの演算子があたかも予約語かのように使える

Pattern Match


Caseクラスとパターンマッチを使うと、
型に意味を持たせられる


def eval(e: Expr): Int = e match {
  case Number(x) => x
  case Sum(l, r) => eval(l) + eval(r)
}

Scalaのパターンマッチはかなり強力

新しい制御構造を作れる言語

Actor


recipient ! msg

val recipient = actor {
  loop {
    recive{
      case Msg1 => … //Msg1の処理
      case Msg2 => … //Msg2の処理
      // …
    }
  }
}
	

ScalaはErlangのアクターモデルを実装している

Actor

メッセージ送信(!)やactor、loop、receive

いずれも組み込み演算ではない

Scalaのアクターライブラリーで定義されている


Scalaの中で新しい言語を作ることすら可能

興味があったらScalaでパーサーを作ってみるを見てね

Scalaがスケーラブルな理由

またはオブジェクト指向と関数型プログラミングの融合

Scalaはオブジェクト指向

Java is OOP?



Javaにはプリミティブ型がある。



これはリフレクションを複雑にするなど、
スケーラビリティを下げている。



純粋なオブジェクト指向じゃないよね

Scala is Pure OOP



Scalaは純粋なオブジェクト指向


1 + 2
    

+ は演算子に見えるが、Intオブジェクトのメソッド

Composition


Scalaはオブジェクトの合成能力がすごい



Traitはメソッドとフィールドをカプセル化したもの。

Traitを使うとミックスインのような事が可能となる。

Scalaは関数型言語

first class values

Scalaでは関数もリテラル


//普通の関数
def 関数名(引数:型, …) = 本体
//関数リテラル
(引数:型, …) => 本体

//こんな風に使える
args.foreach(arg => println(arg))
    

Imutable

メソッドは副作用を持ってはいけない



Scalaは完璧ではない

イミュータブルなデータ構造多く用意している

つまり副作用のないメソッドを容易に作れる

Scalaを選ぶ理由

Compatibility

互換性(Javaとの共存)



ScalaはJavaのライブラリーを多用している。

ScalaからJavaを使うために特別な構文などはない。

実際MyBatisとかもさくっと使えました。

Elegant

簡潔性


//これはJava
class MyClass {
  private int index;
  private String name;
  public MyClass(int index, String name) {
    this.index = index;
    this.name = name;
  }
}
    

Elegant

簡潔性(Javaの半分以下のコード量)



// Scalaならこれだけ
class MyClass(index: Int, name: String)
    

行数が減れば入力が楽、そして理解する労力も減る?

high-level programming language

高水準

例えばStringの変数の中に大文字があるかどうかを知る関数


// Java
boolean nameHasUpperCase = false;
for (int i = 0; i < name.length(); ++i) {
    if (Character.isUpperCase(name.charAt(i))) {
        nameHasUpperCase = true;
        break;
    }
}
	

high-level programming language

抽象度の高いコード、新しい制御構造を定義できる表現力


val nameHasUpperCase = name.exists(_.isUpperCase)
	

文字列をより高い水準の存在として扱い、
複雑さを緩和している。

Javaでも似たような制御構造を作ることは可能だが面倒。

関数リテラルはプログラムは短くてキレイなものとする。

Static Typing

簡潔性とか大丈夫なの?

静的型付け言語は一般的には冗長だと言われている

型を指定しないとかけない。

コーディング量が増える。



Static Typing

柔軟性とかないよね?

型が静的である事自体が制限になる。

将来の変化の足かせになる。



Static Typing

検証可能性

真偽値が整数に加算されていない

文字列の集合に文字列以外が追加されない

コンパイル時に検出可能



Static Typing

安全なリファクタリング

コンパイル時に検出されるエラーがリファクタリング時のセーフティネットとなる。



Static Typing

ドキュメント性の向上

def f(x: String) = …

fの引数がStringでなければならないことがわかる。

ただ、これはやだ

val x: HashMap[Int, String] = new HashMap[Int, String]()
val x = new HashMap[Int, String]()
val x: Map[Int, String] = new HashMap()

型推論してくれるScalaならこれでいい

Scalaのさまざまなルーツ

Roots


まとめ

We will love Scala


Scalaプログラミングの第一歩

First Step

Scalaインタープリターの使い方を学ぶ

Step1

Step1

$scala

scala> 1 + 2

res0: Int = 3

計算された値を参照する自動生成またはユーザー定義で作られた名前

コロンと式の型

等号

式評価の結果

Step1

res0は実際に使える

scala> res0 * 3

res1: Int = 9

みんなの大好き「Hello world」

scala> println("Hello, world!")

Hello, world!

変数を定義する

Step2

Step2

val定義

scala> val msg = "Hello, world!"
msg: java.lang.String = Hello, world!

型を明示的に指定したいときは

scala> val msg2: java.lang.String = "Hello, world!"
msg2: java.lang.String = Hello, world!

scala> val msg3: String = "Hello, world!"
msg3: String = Hello, world!

こんな風に使えます

scala> println(msg)
Hello, world!

Step2

valは定数、Javaでいうfinal

scala> msg = "Goodbye cruel world!"
<console>:8: error: reassignment to val
       msg = "Goodbye cruel world!"

varが変数

scala> var greeting = "Hello, world!"
greeting: java.lang.String = Hello, world!

scala> greeting = "Leave me alone, world!"
greeting: java.lang.String = Leave me alone, world!

Step2

複数行にまたがる入力は、うまいことやってくれる

scala> val multilLine =
     |   "This is next line."
multilLine: java.lang.String = This is next line.

縦棒(|)が表示されて入力を続けられる。

間違えたら[Enter]2回

scala> val oops =
     |
     |
You typed two blank lines.  Starting a new command.

関数を定義する

Step3

Step3

関数はdef

scala> def max(x: Int, y: Int): Int = {
             if (x > y) x
             else y
           }
max: (Int, Int)Int

def 関数名(引数:型, …) = 本体)

関数が再帰的な場合などは結果型の指定が必要だが、今回は省略できる。

scala> def max2(x: Int, y: Int) = if (x > y) x else y

Step3

結果を返さない関数もある

scala> def greet() = println("Hello, world!")
greet: ()Unit

Utin型はJavaのvoid型みたいなもの

終了は

scala> :quit
$

簡単なScalaスクリプトを書く

Step4

Step4

hello.scala

println("Hello, world, from a script!")
$ scala hello.scala

もちろん引数も渡せる。

helloarg.scala

println("Hello, " + arg(0) + "!")
$ scala helloarg.scala planet

whileによるループ、ifによる分岐

Step5

Step5

while

printargs.scala

var i = 0
while (i < args.length) {
  println(args(i))
  i += 1
}

このプログラムはあまり良くないがとりあえずwhileの説明

Step5

if

echoargs.scala

var i = 0
while (i < args.length) {
  if (i != 0)
    print(" ")
  println(args(i))
  i += 1
}
println()

このプログラムはあまり良くないがとりあえずwhileの説明

foreachとforによる反復実行

Step6

Step6

whileは命令形、イケてない

foreachを使おう

args.foreach(arg => println(arg))
args.foreach((arg: String) => println(arg))
arg.foreach(println)

Step6

for式もある

for (arg <- args)
  println(arg)

しかしfor式はこんなもんじゃない

まとめ

Scala結構いけそうじゃね?





Scalaプログラミングの次の一歩

Next Step

配列を型でパラメータ化する

Step7

Step7

パラメータ化とは、インスタンスの構成を設定すること

Javaでいうジェネリクス

val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world¥n"
for (i <- 0 to 2)
  print(greetStrings(i))

Step7

丁寧に書くと

val greetStrings: Array[String]= new Array[String](3)
greetStrings(0)

valの中身を書き換えている

0 to 2

内部的には

(0).to(2)

パラメータが1つならドットと括弧を省略できる

Step7

Scalaは全ての演算がメソッド

greetString(0) = "Hello"
greetStrings.update(0, "Hello")
val numNames = Array("zero", "one", "two")
val numNames2 = Array.apply("zero", "one", "two")

上下で同じ意味

applyはArrayコンパニオンオブジェクトで定義されている

リストを使う

Step8

Step8

メソッドは副作用を持ってはいけない

Array[String]は中身が変更できるからミュータブル



ScalaのListはイミュータブル

val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour
println(" " + oneTwo + " and " + threeFour + " were not mutated.")
println("Thus, " + oneTwoThreeFour + " is a new list.")

Step8

よく使うCons



val twoThree = List(2, 3)
val oneTwoThree = 1 :: twoThree
println(oneTwoThree)

メソッド名末尾がコロンの場合は右被演算子

タプルを使う

Step9

Step9

それは便利なコンテナオブジェクト



val pair = (99, "Luftballons")
println(pair._1)
println(pair._2)

1から始まるのは静的に型付されたタプルの伝統

集合とマップを使う

Step10

Step10 Set

imutableなSet

var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"
println(jetSet.contains("Cessna"))

内部的にはこうなってる

jetSet = jetSet + "Lear"

Step10 Set

mutableなSet

import scala.collection.mutable.Set
val movieSet = Set("Hitch", "Poltergeist")
movieSet += "Shrek"
println(movieSet)

HushSetが必要なら

import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")

Step10 Map

mutableなMap

import scala.collection.mutable.Map
val treasureMap = Map[Int, String]()
treasureMap += (1 -> "Go to island.")
treasureMap += (2 -> "Find big X on ground.")
treasureMap += (1 -> "Dig.")
println(treasureMap(2))

下の二つは同じ意味

1 -> "Go to island."
(1).->("Go to island.")

(1,"Go to island.")というタプルを返す

Step10 Map



imutableなMap

val romanNumeral = Map (
  1 -> "I", 2 -> "II", 3 -> "III", 4 -> "IV", 5 -> "V"
)
println(romanNumeral(4))

関数型のスタイルを見分ける

Step11

Step11

varをなくそう

def printArgs(args: Array[String]): Unit = {
  var i = 0
  while (i < args.length){
    println(args(i))
    i += 1
  }
}

どうする?

Step11

varをなくした

def printArgs(args: Array[String]): Unit = {
  for (arg -> args)
    println(arg)
}

まだいける

def printArgs(args: Array[String]): Unit = {
  args.foreach(println)
}

Step11

副作用をなくせ

真の関数型は意味のある何かを産み出す

def formatArgs(args: Array[String]) = args.mkString("¥n")

println(formatArgs(args))

副作用を持つコードを最小化することでテストしやすく



ただ、varが正しい場合もあるのでそのへんはバランス

ファイルから行を読み出す

Step12

Step12

とにかく読んでみる

import scala.io.Source

if (args.length > 0) {
  for (line <- Source.fromFile(args(0)).getLines)
    println(line.length + " " + line)
}
else 
  Console.err.println("Please enter filename")

Step12

こんな風に出力したい

22 | import scala.io.Source
 0 |
22 | if (args.length > 0) {
49 |   for (line <- Source.fromFile(args(0)).getLines)
39 |       println(line.length + " " + line)
 1 | }
 5 | else
46 |   Console.err.println("Please enter filename")

Step12

じゃーーん

import scala.io.Source
def widthOfLength(s: String) = s.length.toString.length
if (args.length > 0) {
  val lines = Source.fromFile(args(0)).getLines.toList
  var maxWidth = 0
  for (line <- lines)
    maxWidth = maxWidth.max(widthOfLength(line))
  for (line <- lines) {
    val numSpaces = maxWidth - widthOfLength(line) //プリミス
    val padding = " " * numSpaces
    println(padding + line.length + " | " + line)
  }
}
else
  Console.err.println("Please enter filename")

Step12

もっと関数型に

import scala.io.Source
def widthOfLength(s: String) = s.length.toString.length
if (args.length > 0) {
  val lines = Source.fromFile(args(0)).getLines.toList
  val longestLine = lines.reduceLeft(
    (a, b) => if (a.length > b.length) a else b
  )
  val maxWidth = widthOfLength(longestLine)
  for (line <- lines) {
    val numSpaces = maxWidth - widthOfLength(line) 
    val padding = " " * numSpaces
    println(padding + line.length + " | " + line)
  }
}
else
  Console.err.println("Please enter filename")

まとめ

難しいけど面白い

魔法使い目指してがんばろう!

The End

Wataru Fukunaga

@wataru420 | amebaId:wataru420

Slides available at http://goo.gl/SVfmP

Repo at github.com/wataru420/EffectiveJava