Scala
概要
Scalaはオブジェクト指向言語と関数型の特徴を合わせたProgramming Language。 Clojureと同様にJava仮想マシン上で動作するため、Javaのコードベース流用ができる。
Memo
ammonite
replがあると気軽に試せる。 実行にはammoniteを使う。
$ sudo sh -c '(echo "#!/usr/bin/env sh" && curl -L https://github.com/com-lihaoyi/Ammonite/releases/download/2.4.0/2.13-2.4.0) > /usr/local/bin/amm && chmod +x /usr/local/bin/amm'
$ amm
ファイル実行が便利。
amm test.sc amm -w test.sc # 変更を検知して自動コンパイル
ファイルの関数を読み込んだ状態でreplをスタートする。すぐに実行できる。
amm --predef test.sc
sbt
sbtはScalaのビルドツールのこと。
O’Reilly Japan - プログラミングScala
_
scalaの入門本。 のはずだが、後半は全くわからなかった。経験積んで戻る必要がある。
Hello, world
class Upper { def upper(strings: String*): Seq[String] = { strings.map((s:String) => s.toUpperCase()) } } val up = new Upper Console.println(up.upper("A", "First", "Scala", "Program"))
ArraySeq(A, FIRST, SCALA, PROGRAM)
object Upper { def upper(strings: String*) = strings.map(_.toUpperCase()) } Console.println(Upper.upper("A", "First", "Scala", "Program")) // new Upper によってインスタンスを生成する代わりに、単にUpperオブジェクトのUpperメソッドを直接呼ぶ
ArraySeq(A, FIRST, SCALA, PROGRAM)
object Upper { def main(args: Array[String]) = { args.map(_.toUpperCase()).foreach(printf("%s ",_)) println("") } } Upper.main(Array("Hello", "World"))
HELLO WORLD
非同期実行
- valキーワードは、同名の読み取り専用のフィールドと同名の公開読み取りメソッドに自動的に変換される
- Pointをインスタンス化した場合、そのフィールドをpoint.xやpoint.yで読み取ることができる
- scalaでは、具象メソッドをオーバーライドする場合、overrideキーワードが必要
- Shapeは抽象クラス。つまりインスタンス化できない
- Shapeのメソッドは抽象メソッド。本体をもたない
- drawメソッドが返すUnitは、C言語などにおけるvoidに相当する
package shapes { class Point (val x: Double, val y: Double) { override def toString() = "Point(" + x + "," + y + ")" } abstract class Shape() { def draw() : Unit } class Circle(val center: Point, val radius: Double) extends Shape { def draw() = println("Circle.draw: " + this) override def toString() = "Circle(" + center + "," + radius + ")" } class Rectangle(val lowerLeft: Point, val height: Double, val width: Double) extends Shape { def draw() = println("Rectangle.draw: " + this) override def toString() = "Rectangle(" + lowerLeft + "," + height + "," + width + ")" } class Triangle(val point1: Point, val point2: Point, val point3: Point) extends Shape { def draw() = println("Triangle.draw: " + this) override def toString() = "Triangle(" + point1 + "," + point2 + "," + point3 + ")" } }
- importでの
_
は、すべての型をインポートすることを示す(*
は関数名として有効だから) - actメソッドは抽象メソッドなので、オーバーライドするのに明示的なoverrideキーワードは不要
packages shapes { import scala.actors._ import scala.actors.Actor._ object ShapeDrawingActor extends Actor { def act() { loop { receive { case s: Shape => s.draw() case "exit" => println("exiting..."); exit case x: Any => println("Error: Unknown message! " + x) } } } } }
import shapes._ ShapeDrawingActor.start() ShapeDrawingActor | new Circle(new Point(0.0,0.0), 1.0) ShapeDrawingActor | new Rectangle(new Point(0.0,0.0), 2, 5) ShapeDrawingActor | new Triangle(new Point(0.0,0.0), new Point(1.0,0.0), new Point(0.0,1.0)) ShapeDrawingActor | 3.14159 ShapeDrawingActor | "exit"
変数宣言
- 不変の変数は、キーワード
val
を使う。
val array: Array[String] = new Array(5) array
[Ljava.lang.String;@6d304f9d
array参照を変更できないことを確かめる。
val array: Array[String] = new Array(5) array = new Array(2)
/tmp/babel-LymR61/scala-PllbQI:7: error: reassignment to val array = new Array(2)
参照は変更できないが、配列そのものは変更できる。
val array: Array[String] = new Array(5) array(0) = "Hello" array
[Ljava.lang.String;@51cd7ffc
- 不変: val
- 可変: var
紛らわしいな。
var stockPrice: Double = 100.0 stockPrice = 10.0 stockPrice
10.0
デフォルト引数
object StringUtil { def joiner(strings: List[String], separator: String = " "): String = strings.mkString(separator) } import StringUtil._ println(joiner(List("Programming", "Scala")))
Programming Scala
名前付き引数
名前付き引数。引数が何かわかりやすい。
object StringUtil { def joiner(strings: List[String], separator: String = " "): String = strings.mkString(separator) } import StringUtil._ println(joiner(List("Programming", "Scala"))) println(joiner(strings = List("Programming", "Scala"))) println(joiner(List("Programming", "Scala"), " ")) println(joiner(List("Programming", "Scala"), separator = " ")) println(joiner(strings = List("Programming", "Scala"), separator = " "))
Programming Scala Programming Scala Programming Scala Programming Scala Programming Scala
入れ子のメソッド定義
def factorial(i: Int): Int = { def fact(i: Int, accumulator: Int): Int = { if (i <= 1) accumulator else fact(i - 1, i * accumulator) } fact(i, 1) } println(factorial(0)) println(factorial(1)) println(factorial(2)) println(factorial(3)) println(factorial(4)) println(factorial(5))
1 1 2 6 24 120
def countTo(n: Int):Unit = { def count(i: Int):Unit = { if (i <= n) { // nをcountメソッドから参照できる println(i) count(i + 1) } } count(1) } countTo(5)
1 2 3 4 5
型推論
import java.util.Map import java.util.HashMap val intToStringMap: Map[Integer, String] = new HashMap
import java.util.Map import java.util.HashMap val intToStringMap2 = new HashMap[Integer, String]
- Scalaの場合、純粋関数言語(Haskellとか)より多くの型アノテーションを付ける必要がある。オブジェクト指向の型付けと関数型の片付けをサポートしないといけないから。
メソッドの戻り値の型に対して明示的な宣言が必要な例。
def upCase(s: String) = { if (s.length == 0) return s else s.toUpperCase() } println(upCase("")) println(upCase("Hello"))
/tmp/babel-LymR61/scala-TQZ3UB:8: error: method upCase has return statement; needs result type return s
def upCase(s: String): String = { if (s.length == 0) return s else s.toUpperCase() } println(upCase("")) println(upCase("Hello"))
HELLO
def makeList(strings: String*) = { if (strings.length == 0) List(0) else strings.toList } val list: List[String] = makeList()
/tmp/babel-LymR61/scala-x73fET:13: error: type mismatch; found : List[Any] required: List[String] val list: List[String] = makeList()
strings.lengthが0のときにList(0)…List[Int]を返す。正しくはList()。 メソッドの推論された戻り値の型は、List[Int]とList[String]のもっとも近い共通のスーパー型、List[Any]になる。
別の例。
val map = Map() map.update("book", "Programming Scala")
/tmp/babel-LymR61/scala-JI2yYc:7: error: value update is not a member of scala.collection.immutable.Map[Nothing,Nothing]
Mapにおける型パラメータは[Nothing, Nothing]と推論された。なのでStringを入れるとエラー。
等号
def double(i: Int) { 2 * i } println(double(2))
()
- 本体の前に等号を持つメソッドを関数の定義とみなす
- 先頭に等号がないメソッドの本体を見つけると、プログラマが「手続き」の定義としてメソッドを書いたとみなす。手続きは、Unit型の戻り値しか持たず副作用を伴う処理を実行するためのもの
def double(i: Int) = { 2 * i } println(double(2))
4
タプル
t._NはN番目の項目を取得するが、1始まりなのに注意。
def tupleator(x1: Any, x2: Any, x3: Any) = (x1, x2, x3) val t = tupleator("Hello", 1, 2.3) println("Print the whole tuple: " + t) println("Print the first item: " + t._1) println("Print the second item: " + t._2) println("Print the third item: " + t._3) val (t1, t2, t3) = tupleator("World", '!', 0x22) println(t1 + " " + t2 + " " + t3)
Print the whole tuple: (Hello,1,2.3) Print the first item: Hello Print the second item: 1 Print the third item: 2.3 World ! 34
Option型は、nullを返すことがあることを明示する。
def get(key: A): Option[B] = { if (contains(key)) new Some(getValue(key)) else None }
名前空間
package com { package example { package pkg1 { class Class11 { def m = "m11" } class Class12 { def m = "m12" } } package pkg2 { class Class21 { def m = "m21" def makeClass11 = { new pkg1.Class11 } def makeClass12 = { new pkg1.Class12 } } } package pkg3.pkg31.pkg311 { class Class311 { def m = "m21" } } } }
インポート
import java.awt._ import java.io.File import java.io.File._ import java.util.{Map, HashMap}
def writeAboutBigInteger() = { import java.math.BigInteger.{ ONE => _, // インポートしたスコープのスコープ内から隠し、利用できなくする TEN, ZERO => JAVAZERO } // 別名をつけてインポート // println("ONE: " +ONE) // 未定義 println("TEN: " +TEN) println("ZERO: " +JAVAZERO)} writeAboutBigInteger
TEN: 10 ZERO: 0
抽象型
import java.io._ abstract class BulkReader { type In val source: In def read: String } class StringBulkReader(val source: String) extends BulkReader { type In = String def read = source } class FileBulkReader(val source: File) extends BulkReader { type In = File def read = { val in = new BufferedInputStream(new FileInputStream(source)) val numBytes = in.available() val bytes = new Array[Byte](numBytes) in.read(bytes, 0, numBytes) new String(bytes) } } println(new StringBulkReader("Hello Scala!").read) println(new FileBulkReader(new File(".gitattributes")).read)
Hello Scala! public/* linguist-vendored public/*/* linguist-vendored
- インスタンスは具象クラスからのみ生成できる
- 具象クラスにはすべてのメンバが定義されていなければならない
- StringBulkReader, FileBulkReaderという派生クラスは、BulkReaderで定義した抽象メンバに対する具体的な定義を与える
基本文法
- もっとも重要で基本的なコンセプト: 演算子に見えるものは実際にはすべてメソッド
1 + 2
は、 1.+(2)
と同じ。
- 慣習的に、副作用のないメソッドの呼び出しには()を使う
- 引数なしのメソッドや引数が1つだけのメソッドを呼び出すときには、ドットを省略することができる
val list = List('b', 'c', 'd') println(list) println('a' :: list) println(list.::('a')) // ↑と同じ意味
List(b, c, d) List(a, b, c, d) List(a, b, c, d)
if式やその他のほとんどの文が実際には式である。そのため、if式の結果を代入できる。
val configFile = new java.io.File(".myapprc") val configFilePath = if(configFile.exists()) { configFile.getAbsolutePath() } else { configFile.createNewFile() configFile.getAbsolutePath() }
for式
基本的なfor式。
val dogBreeds = List("Doberman", "Yorkshire Terrirorf", "Dachshund", "Scottish Terrir", "Creat Dane", "Portuguess Water Dog") for (breed <- dogBreeds) println(breed)
Doberman Yorkshire Terrirorf Dachshund Scottish Terrir Creat Dane Portuguess Water Dog
<-演算子はジェネレータという。 for式の内側で参照される一時変数に、コレクションの要素を1つずつ代入する矢印。
フィルタリングの結果をプログラムの別の部分に渡すとき、yieldを使う。
val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund", "Scottish Terrier", "Great Dane", "Portuguese Water Dog") val filteredBreeds = for { breed <- dogBreeds if breed.contains("Terrier") if !breed.startsWith("Yorkshire") } yield breed println(filteredBreeds)
List(Scottish Terrier)
val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund", "Scottish Terrier", "Great Dane", "Portuguese Water Dog") for { breed <- dogBreeds upcasedBreed = breed.toUpperCase() } println(upcasedBreed)
DOBERMAN YORKSHIRE TERRIER DACHSHUND SCOTTISH TERRIER GREAT DANE PORTUGUESE WATER DOG
do-whileループ
var count = 0 do { count += 1 println(count) } while (count < 10)
1 2 3 4 5 6 7 8 9 10
ジェネレータ式
for (i <- 1 to 10) println(i)
1 2 3 4 5 6 7 8 9 10
- コンパイラは暗黙の型変換を呼び出し、Intの1をRichIntに変換する。toメソッドを呼び出し、Range.Inclusiveのインスタンスを返す。
パターンマッチ
val bools = List(true, false) for (bool <- bools) { bool match { case true => println("heads") case false => println("tails") case _ => println("something other than heads or tails (yikes!)") } }
heads tails
import scala.util.Random val randomInt = new Random() .nextInt(10) randomInt match { case 7 => println("lucky seven!") case otherNumber => println("boo, got boring ol' " + otherNumber) }
boo, got boring ol’ 2
型に対するマッチ
val sundries = List(23, "Hello", 8.5, 'q') for (sundry <- sundries) { sundry match { case i: Int => println("got an Integer: " + i) case s: String => println("got an String: " + s) case f: Double => println("got an Double: " + f) case other =>println("got soumething else: " + other) } }
got an Integer: 23 got an String: Hello got an Double: 8.5 got soumething else: q
シーケンスに対するマッチ
val willWork = List(1, 3, 23, 90) val willNotWork = List(4, 18, 52) val empty = List() for (l <- List(willWork, willNotWork, empty)) { l match { case List(_, 3, _, _) => println("Four elements, with the 2nd being '3'.") case List(_*) => println("Any other list with 0 or more elements.") } }
Four elements, with the 2nd being ’3’. Any other list with 0 or more elements. Any other list with 0 or more elements.
val willWork = List(1, 3, 23, 90) val willNotWork = List(4, 18, 52) val empty = List() def processList(l: List[Any]): Unit = l match { case head :: tail => printf("%s ", head) processList(tail) case Nil => println("") } for (l <- List(willWork, willNotWork, empty)) { print("List: ") processList(l) }
List: 1 3 23 90 List: 4 18 52 List:
タプルに対するマッチ
val tupA = ("Good", "Morning!") val tupB = ("Guten", "Tag!") for (tup <- List(tupA, tupB)) { tup match { case (thingOne, thingTwo) if thingOne == "Good" => println("A two-tuple starting with 'Good'.") case (thingOne, thingTwo) => println("This has two things: " + thingOne + " and " + thingTwo) }}
A two-tuple starting with ’Good’. This has two things: Guten and Tag!
ケースクラスに対するマッチ
中身を調べる深いマッチ。
case class Person(name: String, age: Int) // ケースクラス val alice = new Person("Alice", 25) val bob = new Person("Bob", 32) val charlie = new Person("Charlie", 32) for (person <- List (alice, bob, charlie)) { person match { case Person("Alice", 25) => println("Hi Alice!") case Person("Bob", 32) => println("Hi Bob!") case Person(name, age) => println("Who are you, " + age + " year-old person named " + name + "?") } }
Hi Alice! Hi Bob! Who are you, 32 year-old person named Charlie?
- ケースクラスを他のケースクラスから継承するのは避ける
正規表現
val BookExtractorRE = """Book: title=([^,]+),\s+authors=(.+)""".r val MagazineExtractorRE = """Magazine: title=([^,]+),\s+issue=(.+)""".r val catalog = List ( "Book: title=Programming Scala, authors=Dean Wampler, Alex Payne", "Magazine: title=The New Yorker, issue=January 2009", "Book: title=War and Peace, authors=Leo Tolstoy", "Magazine: title=The SAtlantic, issue=February 2009", "BadData: text=Who put this here??" ) for (item <- catalog) { item match { case BookExtractorRE(title, authors) => println("Book \"" + title + "\", written by " + authors) case MagazineExtractorRE(title, issue) => println("Magazine \"" + title + "\", issue " + issue) case entry => println("Unrecognized entry: " + entry) } }
Book “Programming Scala”, written by Dean Wampler, Alex Payne Magazine “The New Yorker”, issue January 2009 Book “War and Peace”, written by Leo Tolstoy Magazine “The SAtlantic”, issue February 2009 Unrecognized entry: BadData: text=Who put this here??
enum
object Breed extends Enumeration { val doberman = Value("Doberman Pinscher") val yorkie = Value("Yorkshire Terrier") val scottie = Value("Scottish Terrier") val dane = Value("Great Dane") val portie = Value("Portuguese Water Dog") } println("ID\tBreed") for (breed <- Breed.values) println(breed.id + "\t" + breed) println("\nJust Terriers:") Breed.values.filter(_.toString.endsWith("Terrier")).foreach(println)
ID Breed 0 Doberman Pinscher 1 Yorkshire Terrier 2 Scottish Terrier 3 Great Dane 4 Portuguese Water Dog
Just Terriers: Yorkshire Terrier Scottish Terrier
トレイト
トレイトは関心事の分離を保ちながらも、ミックスインによって要求に応じて振る舞いを変えられる。
package ui class Button(val label: String) extends Widget { def click() = { // クリックされたボタンの見た目を変更する… } }
package ui abstract class Widget
package observer trait Subject { type Observer = { def receiveUpdate(subject: Any) } private var observers = List[Observer]() def addObserver(observer:Observer) = observers ::= observer def notifyObservers = observers foreach (_.receiveUpdate(this)) }
定義したSubjectトレイトを使ってみる。
package ui import observer._ class ObservableButton(name: String) extends Button(name) with Subject { override def click() = { super.click() notifyObservers } }
クラスと同じように、トレイトを使うインスタンスが生成されるたびにトレイトの本体が実行される。
trait T1 { println(" in T1: x = " + x) val x=1 println(" in T1: x = " + x) } trait T2 { println(" in T2: y = " + y) val y="T2" println(" in T2: y = " + y) } class Base12 { println(" in Base12: b = " + b) val b="Basel12" println(" in Base12: b = " + b) } class C12 extends Base12 with T1 with T2 { println(" in C12: c = " + c) val c="C12" println(" in C12: c = " + c) } println("Creating C12:") new C12 println("After Creating C12")
Creating C12: in Base12: b = null in Base12: b = Basel12 in T1: x = 0 in T1: x = 1 in T2: y = null in T2: y = T2 in C12: c = null in C12: c = C12 After Creating C12
- トレイトのコンストラクタに引数を渡すことはできない
- フィールドをデフォルト値で上書きすることや、抽象フィールドにしておくことは可能
- クラスかトレイトのどちらかにすべきかを考えるとき、ミックスインとしてのトレイトは「付加的な」ふるまいにもっともふさわしい
公開可視性
デフォルトで公開(public)。つまりどこからでも参照できる。
オーバーライド
package ui3 abstract class Widget { def draw(): Unit override def toString() = "(widget)" }
package ui3 class Button(val label: String) extends Widget with Clickable { def click() = { // ... } def draw() = { // ... } override def toString() = "(button: label=" + label + ", " + super.toString() + ")" }
apply
appleは新たなインスタンスを返すファクトリメソッドとして使われるのが慣例になっている。
type Pair[+A, +B] = Tuple2[A, B] object Pair { def apply[A, B] (x: A, y: B) = Tuple2(x, y) def unapply[A, B] (x: Tuple2[A, B]): Option[Tuple2[A, B]] = Some(x) } val p = Pair(1, "one") println(p)
(1,one)
暗黙のうちにList.applyが使われるスクリプトの例。
val list1 = List() val list2 = List(1, 2.2, "three", 'four) val list3 = List("1", "2.2", "three", "four") println("1: "+list1) println("2: "+list2) println("3: "+list3)
1: List() 2: List(1, 2.2, three, Symbol(four)) 3: List(1, 2.2, three, four)
関数型プログラミング言語
- 不変なものこそが値である
- すでに値を持った変数に新しい値を代入することはできない
Scalaはどの書き方も強制しないが、できるだけ関数言語のスタイルを使うほうがわかりやすい。
println(List(1, 2, 3, 4, 5) map { _ * 2 })
List(2, 4, 6, 8, 10)
println(List(1, 2, 3, 4, 5) reduceLeft { _ * _ })
120
var factor = 3 val multiplier = (i: Int) => i * factor val l1 = List(1, 2, 3, 4, 5) map multiplier factor = 5 val l2 = List(1, 2, 3, 4, 5) map multiplier println(l1) println(l2)
List(3, 6, 9, 12, 15) List(5, 10, 15, 20, 25)
再帰を使う。
def factorial (i: BigInt): BigInt = i match { case _ if i == 1 => i case _ => i * factorial(i - 1) } for (i <- 1 to 10) printf("%s: %s\n", i, factorial(i))
1: 1 2: 2 3: 6 4: 24 5: 120 6: 720 7: 5040 8: 40320 9: 362880 10: 3628800
末尾再帰
再帰にはパフォーマンスのオーバーヘッドとスタックオーバーフローの危険性がある。 末尾再帰はループに変換はループに変換することで最適化できる。
末尾再帰でない例。
def factorial(i: BigInt): BigInt = i match { case _ if i == 1 => i case _ => i * factorial(i - 1) } for (i <- 1 to 10) printf("%s: %s\n", i, factorial(i))
1: 1 2: 2 3: 6 4: 24 5: 120 6: 720 7: 5040 8: 40320 9: 362880 10: 3628800
末尾再帰の例。
def factorial(i: BigInt): BigInt = { def fact(i: BigInt, accumulator: BigInt): BigInt = i match { case _ if i == 1 => accumulator case _ => fact(i - 1, i * accumulator) } fact(i, 1) } for (i <- 1 to 10) printf("%s: %s\n", i , factorial(i))
1: 1 2: 2 3: 6 4: 24 5: 120 6: 720 7: 5040 8: 40320 9: 362880 10: 3628800
走査
List(1, 2, 3, 4, 5) foreach { i => println("Int: " + i) } val stateCapitals = Map( "Alabama" -> "Montgomery", "Alaska" -> "Janeau", "Wyoming" -> "Cheyenne") stateCapitals foreach { kv => println(kv._1 + ": " + kv._2) }
Int: 1 Int: 2 Int: 3 Int: 4 Int: 5 Alabama: Montgomery Alaska: Janeau Wyoming: Cheyenne
フィルタリング
val stateCapitals = Map( "Alabama" -> "Montgomery", "Alaska" -> "Juneau", "Wyoming" -> "Cheyenne") val map2 = stateCapitals filter { kv => kv._1 startsWith "A" } println(map2)
Map(Alabama -> Montgomery, Alaska -> Juneau)
畳み込み
List(1, 2, 3, 4, 5, 6).foldLeft(List[String] ()) { (list, x) => ("<" + x + ">") :: list }.reverse
Iterableの畳み込みと簡約の操作のシグネチャ。
trait Iterable[+A] { def foldLeft [B] (z : B) (op : (B, A) => B) : B def foldRight [B] (z : B) (op : (A, B) => B) : B def /: [B] (z : B) (op : (B, A) => B) : B def :\ [B] (z : B) (op : (A, B) => B) : B def reduceLeft [B >: A] (op : (B, A) => B) : B def reduceRight [B >: A] (op : (A, B) => B) : B }
List(1, 2, 3, 4, 5, 6).foldRight(List[String] ()) { (x, list) => ("<" + x + ">") :: list }
部分適用
def concatUpper(s1: String, s2: String) : String = (s1 + " " + s2).toUpperCase val c = concatUpper _ println(c("short", "pants")) val c2 = concatUpper("short", _: String) println(c2("pants"))
SHORT PANTS SHORT PANTS
val pantsTest: PartialFunction[String, String] = { case "pants" => "yes, we have pants!" } println(pantsTest.isDefinedAt("pants")) println(pantsTest.isDefinedAt("skort"))
true false
カリー化
def multiplier(i: Int) (factor: Int) = i * factor val byFive = multiplier(5) _ val byTen = multiplier(10) _ println(byFive(2)) println(byTen(2))
10 20
名前渡しパラメータ
def whileAwesome(conditional: => Boolean) (f: => Unit) { if (conditional) { f whileAwesome(conditional) (f) } } var count = 0 whileAwesome(count < 5) { println("still awesome") count += 1 }
still awesome still awesome still awesome still awesome still awesome