Scalaを書いてみる
Scalaを実際に書いてみたい
以下のチュートリアルを使用予定
↓このスクラップの続き
- Scalaは全ての関数が値なため、関数型言語である
- Scalaは全ての値がオブジェクトなため、純粋なオブジェクト指向言語である
式の結果=値には名前をつけられる。
名前をつける=束縛するにはval
を使う。
val sum = 10 + 12
再代入はできない。
(10/18 間違ってたので修正)
なんと、変数は値ではないらしい。
値と似てはいる、みたいな書き方してる。
変数を宣言するにはvar
を使う。
{}
でブロックを作れる。
ブロックの最後の値がブロックの結果になる。
ブロックは値を返す何かなのか...?
println({
val sum = 20 + 14
sum / 2
})
無名関数には、JSのアロー関数みたいなのが使える。
def
と何が違うんだ...?
val func = (x: Int) => x + 5
メソッドはパラメーターリストが使える。
def myMethod(x: Int, y: Int)(multi: Int) = (x + y) * multi
myMethod(5, 6)(2)
メソッドと関数には他にも違いがありますが、今のところは同じようなものと考えて大丈夫です。
return
は使われない。最後の値が最終的な結果になる仕様を使う。
- メソッドの中にメソッドを書くことができる
- メソッドを関数が必要な関数に渡すと、Scalaのコンパイラがメソッドを関数に変換する
コレクションのfoldLeft
にはパラメーターリストが使われている。
メソッドはJSのreduce
と似たような役割を持つ。
val numbers = List(10, 30, 15)
// (初期値)(実行する関数)
numbers.foldLeft(0)((b, a) => a + b) // 55
...なお、単純に合計を計算するだけならsum
メソッドでいい。
numbers.sum // 55
一つ目の_
は一つ目の引数、二つ目の_
は二つ目の引数が入る?
numbers.foldLeft(0)(_ + _)
なお、パラメーターリストを使わずに同じパラメーターに複数の引数を入れた場合、このようなことはできない。
なぜなら、Scalaのコンパイラが型推論できないから。
メソッドが少ない数のパラメータリストで呼び出された時、不足しているパラメータリストを引数として受け取る関数が生成されます。 これは一般的に部分適用として知られています。
例えばこんな感じ。
// 配列の合計を計算する、特に意味はない関数
// 合計するときに使う値を関数で指定できる
def calcListSum
(list: List[Double])
(func: Double => Double)
(implicit num: Numeric[Double]) = {
list.foldLeft(num.zero)((sum, current) => {
num.plus(sum, func(current))
})
}
val calcSalarySum = calcListSum(List(200, 100, 150)) // 関数が入る
// 多分税金を計算してる
calcSalarySum(_ * 0.95) // 427.5
(型定義は諦めた)
関数の呼び出しはapply
メソッドでもできそう
パラメーターリストでいろいろやることをカリー化と言う?
Unit
はvoid
みたいな型。
戻り値がない時とかに使う?
クラス
class Student (name: String) { // 引数がない時は()は省略可能
def greet(): Unit = {
println("Hi! My name is " + name + ".")
}
}
val student = new Student("Taro")
student.greet()
デフォルト引数は=
が使える。
また、var
を前につけると再代入可能になる。
class Point(var x: Int = 0, var y: Int = 0) {
def move(dx: Int, dy: Int): Unit = {
x = x + dx
y = y + dy
}
}
Scalaのコンストラクタは、初期化時に渡された引数らしい。
なんとここでフィールドの宣言ができる。
修飾子は一般的なprivate
とprotected
がある。
これは簡単な文字列のイテレータ。
class StringIterator(string: String) {
type T = Char
private var index: Int = 0
def hasNext = index < string.length
def next = {
val char = string.charAt(index)
index += 1
char
}
}
使用例:
val iterator = new StringIterator("Hello World!")
iterator.next // H
iterator.next // e
iterator.hasNext // true
ケースクラスはイミュータブルなクラス?
ケースクラスについて紹介すべきことはたくさんあり、あなたはケースクラスが大好きになると確信しています!
case
を使って作成できる。
また、new
を使用せずにインスタンスを生成できる。
case class Book(title: String) // パラメーターの()は必須
val book1 = Book("The C Programming Language")
book1.title
パラメーターはデフォルトでパブリックになる。
また、val
になるため不変。ケースクラスではvar
は推奨されない。
ケースクラスは構造で比較される。
val book1 = Book("book1")
val book2 = Book("book2")
book1 == book2 // true
copy
でコピーを作成できる。
なお、浅いコピー(シャローコピー)なのでそこは注意。
case to caseの継承は禁止されている。
...なんで?!
ケースクラスでパターンマッチングをすることができる。
この場合のパターンマッチングとは、インスタンスがどのケースクラスかどうかをmatch
を使って判定するというもの。
例えば、通知方法に以下の3種類があるとする。
abstract class Notification
case class SMS(caller: String, message: String) extends Notification {
def send(): Unit = println(s"Success! (to $caller)")
}
case class Slack() extends Notification
case class Email() extends Notification
そして、インスタンスがある。
あえてここではNotification
という型を書いている。
val notification: Notification = SMS("012-3456-7890", "This is a SMS message")
これを以下のように場合分けできる。
冗長なif
を書く必要はない。
notification match {
case n: SMS => {
println(s"message: [${n.message}] to ${n.caller}")
n.send()
}
case Slack() => ""
case _ => "Not Found"
}
ここではメソッドを呼び出すためインスタンスにn
という別名をつけている。
が、コンストラクタの値を使うだけなら、以下のようにも書くことができる。
case SMS(caller, message) => println(s"message: [$message] to $caller")
↑↑のように型でマッチングをする場合、変数名には型の最初の1文字を使う慣習がある。
オブジェクトはJavaScriptのオブジェクトと似てそう
違いのは文字ケースと修飾子の有無?
object Incrementer {
private var count = 0
def create() = {
count += 1
count
}
}
Incrementer.create() // 1
Incrementer.create() // 2
JSと違うのは、Scalaのオブジェクトはシングルトンであるということっぽい。
同じオブジェクトは何個も作らず、一つだけ。
JSのはScalaにおけるタプルやケースクラスの役割もありそう。
オブジェクトは値です。オブジェクトの定義はクラスのように見えますが、キーワード
object
を使います。
import
でオブジェクトの特定のメソッドのみをインポートできる。
package logging
object Logger {
def log(message: String): Unit = println(s"LOG: $message")
def info(message: String): Unit = println(s"INFO: $message")
}
import logging.Logger.info
info("This is an infomation message")
クラス(ケースクラス)と同名なオブジェクト、またはその逆は、コンパニオンと呼ばれる。
コンパニオンは自身のコンパニオンのプライベートメンバーにアクセスできる。
なお、使用にはimport
が必要。
Scalaではスコープを指定したimport
もできそう?
import scala.math._
case class Circle(radius: Double) {
import Circle._ // コンパニオンからimport
def area = calcArea(radius)
}
object Circle {
private def calcArea(radius: Double): Double = Pi * pow(radius, 2.0)
}
val circle = Circle(10)
circle.area
コンパニオンオブジェクトは、インスタンス変数やメソッドに依存しない、単体で動くメソッドに使える。
他言語のstatic
メソッドはこれに近いかもしれない。
コンパニオンオブジェクトには例えばファクトリーメソッドを入れられる。
case class Email(username: String, domainName: String)
object Email {
// ファクトリーメソッド
def fromString(emailString: String): Option[Email] = {
emailString.split("@") match {
case Array(a, b) => Some(Email(a, b))
case _ => None
}
}
}
使用例:
val email = Email.fromString("admin@example.com")
email match
case Some(Email(username, domainName)) =>
s"user: $username | domain: $domainName"
case None => "Error: could not parse email"
トレイトは他言語のinterface
みたいなやつ?
trait Incrementer {
def create(): Int
}
class IdFactory extends Incrementer {
private var count = 0
override def create() = {
count += 1
count
}
}
override
はなくても動くけど、あったほうがいいのかな?
[]
でジェネリック型を定義できる
// 配列のイメージ
trait List[T] {
def map(func: T => T): List[T]
// ...
}
トレイトはwith
を使ってミックスインできる。
継承とは何が違うんだ...?
トレイトには実行を書けるので、こんなこともできる。
abstract class BaseAbs {
val message: String
}
class BaseClass extends BaseAbs {
val message = "Hello World!"
}
// アッパーケースにするトレイト
trait UpperTrait extends BaseAbs {
def toUpper = message.toUpperCase()
}
// これだけでBaseClassにtoUpperを追加できる
class MyMessage extends BaseClass with UpperTrait
イテレーターにトレイトでforeach
を実装する例
abstract class BaseIterator {
type T
def hasNext: Boolean
def next: T
}
class StringIterator(string: String) extends BaseIterator {
type T = Char
private var index: Int = 0
def hasNext = index < string.length
def next = {
val char = string.charAt(index)
index += 1
char
}
}
trait IteratorForeach extends BaseIterator {
def foreach(func: T => Unit): Unit = {
while (hasNext) func(next)
}
}
class RichStringIterator(string: String)
extends StringIterator(string) with IteratorForeach
val iterator = new RichStringIterator("Hello World!")
iterator.foreach(char => println("char: " + char))
メソッドを一般公開はしたくないが、ミックスインや継承先で使いたい場合はprotected
が使える。
import scala.math._
case class Circle(radius: Double) extends CircleCalculator {
import Circle._
def area = calcArea(radius)
}
trait CircleCalculator {
protected def calcArea(radius: Double): Double = Pi * pow(radius, 2.0)
}
配列はList
型が使える
これはAnyRef
というJSのオブジェクト型みたいなものの一つ。
初期化にはList
を使う。これもしかしてケースクラス...?
val numbers: List[Int] = List(
10,
56
)
ちょっと、いやだいぶJavaScriptっぽい
numbers
.map(num => num * 2)
.foreach(println)
↑比較用JS:
numbers
.map(num => num * 2)
.forEach(num => console.log(num))
配列の内容を縛って、共通するプロパティにアクセスできる
trait Animal {
val name: String
}
class Dog(val name: String) extends Animal
class Cat(val name: String) extends Animal
val animals: List[Animal] = List(
new Dog("ポチ"),
new Cat("ねこ")
)
animals.foreach(animal => println(animal.name))
ちなみに上の配列の型は推論してくれる
val animals = List(
new Dog("ポチ"),
new Cat("ねこ")
) // List[Animal]
最後にString
型を入れたらObject
っていう別の型に推論された
val animals = List(
new Dog("ポチ"),
new Cat("ねこ"),
"string"
) // List[Object]
タプル(tuple
)を使うと、複数の値(式?)を格納できる
パターンマッチングを使うことで分解でき、型推論もされる
val myTuple = ("MyStr", 10)
val (str, num) = myTuple
タプル型は()
で表現できる。
基本的には推論されるので、わざわざ書く必要はあまりないかも?
val tuples: List[(String, Int)] = List(
("String", 349),
("MyStr", 12)
)
tuples
.foreach((str, num) => println(str + num))
タプルはケースクラスと使い方が似るらしい。
JSのオブジェクトもときどきクラスにした方がいいか迷うし、それと同じ感じ?
タプルはJavaScriptの配列に当たりそう。
ReactのuseState
とかがまさにそんな感じな気がする。
const [state, setState] = useState("init")
↓これは架空のScalaコード
val (state, setState) = useState("init")
文字列に変数を埋め込むにはs
を使う
val num = 10
val str = "Hello"
val concat = s"num: $num str: $str"
.
などの記号が必要な場合は、{}
で囲める
s"message: [${n.message}] to ${n.caller}"
if
には式を書けるので、三項演算子のような使い方ができる
val str = if (10 > 9) "Yes" else "No"
もちろん処理も書けるし、ブロックを使うこともできる
if (10 > 9) {
println("Yes")
}
match
キーワードでパターンマッチングができる
val m = random match {
case 0 => "zero"
case 1 => "one"
case _ => "other"
}
正規表現は文字列のr
メソッドを使う。
正規表現(Regex
)のfindFirstMatchIn
メソッドを使うと、最低1つでもマッチするものがあるかどうか確かめられる。
パターンマッチと併用する場合:
val numberPattern = "[0-9]".r
val password = "hello-124"
numberPattern.findFirstMatchIn(password) match {
case Some(_) => println("Password OK")
case None => println("Error: Invalid Password")
}
戻り値のisDefined
を使うと、マッチしているかどうかの真偽値が取得できる。
val numberPattern = "[0-9]".r
val password = "hello-124"
if (numberPattern.findFirstMatchIn(password).isDefined)
"OK"
else
"Invalid"
↑はOption
型のメソッドで、返り血がSome
ならtrue
、None
ならfalse
を返す。
正規表現でScalaの値(変数)宣言っぽい構文を解析
val syntax = "(va[l|r]) ([a-zA-Z0-9]+) = ([a-zA-Z0-9 \\t!\"]+)".r
val code = "val str = \"Hello World!\""
syntax.findFirstMatchIn(code) match {
case Some(pattern) => {
s"""OK 略""".stripMargin
}
case None => "Invalid Syntax"
}
文字列内の全てにマッチさせることもできる
それとfor
内包表記を組み合わせると、以下のようになる
val code = """// code
|val str = "Hello World!"
|val num = 123
""".stripMargin
for (pattern <- syntax.findAllMatchIn(code)) {
output(pattern.group(1), pattern.group(2), pattern.group(3))
}
// outputメソッドは略
出力:
OK
seg: val
variable-name: str
init: "Hello World!"
OK
seg: val
variable-name: num
init: 123
パターンマッチングを使ってキャプチャグループを分解することはできないっぽい?
Next: