Closed74

Scalaを書いてみる

nanasinanasi
  • Scalaは全ての関数が値なため、関数型言語である
  • Scalaは全ての値がオブジェクトなため、純粋なオブジェクト指向言語である
nanasinanasi

式の結果=値には名前をつけられる。
名前をつける=束縛するにはvalを使う。

val sum = 10 + 12

再代入はできない。

(10/18 間違ってたので修正)

nanasinanasi

なんと、変数は値ではないらしい。
値と似てはいる、みたいな書き方してる。
変数を宣言するにはvarを使う。

nanasinanasi

{}でブロックを作れる。
ブロックの最後の値がブロックの結果になる。

ブロックは値を返す何かなのか...?

nanasinanasi

無名関数には、JSのアロー関数みたいなのが使える。
defと何が違うんだ...?

val func = (x: Int) => x + 5
nanasinanasi

無名関数の引数が自明な時は、_を使って引数にアクセスできる
条件はこんな感じ?

  • 引数が一つ or 最初の一つしか使わない
  • 型が自明
val numbers = Seq(100, 53, 133)
numbers.map(_ * 2)
nanasinanasi

メソッドはパラメーターリストが使える。

def myMethod(x: Int, y: Int)(multi: Int) = (x + y) * multi
myMethod(5, 6)(2)
nanasinanasi

メソッドと関数には他にも違いがありますが、今のところは同じようなものと考えて大丈夫です。

nanasinanasi
  • メソッドの中にメソッドを書くことができる
  • メソッドを関数が必要な関数に渡すと、Scalaのコンパイラがメソッドを関数に変換する
nanasinanasi

コレクションのfoldLeftにはパラメーターリストが使われている。
メソッドはJSのreduceと似たような役割を持つ。

val numbers = List(10, 30, 15)

// (初期値)(実行する関数)
numbers.foldLeft(0)((b, a) => a + b) // 55

...なお、単純に合計を計算するだけならsumメソッドでいい。

numbers.sum // 55
nanasinanasi

一つ目の_は一つ目の引数、二つ目の_は二つ目の引数が入る?

numbers.foldLeft(0)(_ + _)

なお、パラメーターリストを使わずに同じパラメーターに複数の引数を入れた場合、このようなことはできない。
なぜなら、Scalaのコンパイラが型推論できないから。

nanasinanasi

メソッドが少ない数のパラメータリストで呼び出された時、不足しているパラメータリストを引数として受け取る関数が生成されます。 これは一般的に部分適用として知られています。

例えばこんな感じ。

// 配列の合計を計算する、特に意味はない関数
// 合計するときに使う値を関数で指定できる
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

(型定義は諦めた)

nanasinanasi

パラメーターリストでいろいろやることをカリー化と言う?

nanasinanasi

Unitvoidみたいな型。
戻り値がない時とかに使う?

nanasinanasi

他の型も大文字から始まるみたい。
Int, String, etc...

nanasinanasi

Option型は値が存在するかもしれないことを示す型。
Nullの代わりにこちらを使うといいっぽい。

Option[T]型は以下の型の値を取りうる。

  • Some[T]: 値が存在する
  • None: 値が存在しない
nanasinanasi

クラス

class Student (name: String) { // 引数がない時は()は省略可能
  def greet(): Unit = {
    println("Hi! My name is " + name + ".")
  }
}

val student = new Student("Taro")
student.greet()
nanasinanasi

デフォルト引数は=が使える。
また、varを前につけると再代入可能になる。

class Point(var x: Int = 0, var y: Int = 0) {
  def move(dx: Int, dy: Int): Unit = {
    x = x + dx
    y = y + dy
  }
}
nanasinanasi

Scalaのコンストラクタは、初期化時に渡された引数らしい。
なんとここでフィールドの宣言ができる。

nanasinanasi

これは簡単な文字列のイテレータ。

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
nanasinanasi

ケースクラスはイミュータブルなクラス?

ケースクラスについて紹介すべきことはたくさんあり、あなたはケースクラスが大好きになると確信しています!

nanasinanasi

caseを使って作成できる。
また、newを使用せずにインスタンスを生成できる。

case class Book(title: String) // パラメーターの()は必須

val book1 = Book("The C Programming Language")
book1.title
nanasinanasi

パラメーターはデフォルトでパブリックになる。
また、valになるため不変。ケースクラスではvarは推奨されない。

nanasinanasi

ケースクラスは構造で比較される。

val book1 = Book("book1")
val book2 = Book("book2")

book1 == book2 // true
nanasinanasi

copyでコピーを作成できる。
なお、浅いコピー(シャローコピー)なのでそこは注意。

nanasinanasi

case to caseの継承は禁止されている。
...なんで?!

nanasinanasi

ケースクラスでパターンマッチングをすることができる。
この場合のパターンマッチングとは、インスタンスがどのケースクラスかどうかを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"
}
nanasinanasi

ここではメソッドを呼び出すためインスタンスにnという別名をつけている。
が、コンストラクタの値を使うだけなら、以下のようにも書くことができる。

case SMS(caller, message) => println(s"message: [$message] to $caller")
nanasinanasi

↑↑のように型でマッチングをする場合、変数名には型の最初の1文字を使う慣習がある。

nanasinanasi

オブジェクトはJavaScriptのオブジェクトと似てそう
違いのは文字ケースと修飾子の有無?

object Incrementer {
  private var count = 0
  def create() = {
    count += 1
    count
  }
}

Incrementer.create() // 1
Incrementer.create() // 2
nanasinanasi

JSと違うのは、Scalaのオブジェクトはシングルトンであるということっぽい。
同じオブジェクトは何個も作らず、一つだけ。
JSのはScalaにおけるタプルやケースクラスの役割もありそう。

nanasinanasi

オブジェクトは値です。オブジェクトの定義はクラスのように見えますが、キーワードobjectを使います。

nanasinanasi

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")
nanasinanasi

クラス(ケースクラス)と同名なオブジェクト、またはその逆は、コンパニオンと呼ばれる。
コンパニオンは自身のコンパニオンのプライベートメンバーにアクセスできる。

なお、使用には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
nanasinanasi

コンパニオンオブジェクトは、インスタンス変数やメソッドに依存しない、単体で動くメソッドに使える。
他言語のstaticメソッドはこれに近いかもしれない。

nanasinanasi

コンパニオンオブジェクトには例えばファクトリーメソッドを入れられる。

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"
nanasinanasi

トレイトは他言語のinterfaceみたいなやつ?

trait Incrementer {
  def create(): Int
}

class IdFactory extends Incrementer {
  private var count = 0
  override def create() = {
    count += 1
    count
  }
}
nanasinanasi

overrideはなくても動くけど、あったほうがいいのかな?

nanasinanasi

[]でジェネリック型を定義できる

// 配列のイメージ
trait List[T] {
  def map(func: T => T): List[T]
  // ...
}
nanasinanasi

トレイトは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
nanasinanasi

イテレーターにトレイトで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))
nanasinanasi

メソッドを一般公開はしたくないが、ミックスインや継承先で使いたい場合は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)
}
nanasinanasi

配列はList型が使える
これはAnyRefというJSのオブジェクト型みたいなものの一つ。

初期化にはListを使う。これもしかしてケースクラス...?

val numbers: List[Int] = List(
  10,
  56
)
nanasinanasi

ちょっと、いやだいぶJavaScriptっぽい

numbers
  .map(num => num * 2)
  .foreach(println)
nanasinanasi

↑比較用JS:

numbers
  .map(num => num * 2)
  .forEach(num => console.log(num))
nanasinanasi

配列の内容を縛って、共通するプロパティにアクセスできる

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))
nanasinanasi

ちなみに上の配列の型は推論してくれる

val animals = List(
  new Dog("ポチ"),
  new Cat("ねこ")
) // List[Animal]

最後にString型を入れたらObjectっていう別の型に推論された

val animals = List(
  new Dog("ポチ"),
  new Cat("ねこ"),
  "string"
) // List[Object]
nanasinanasi

タプル(tuple)を使うと、複数の値(式?)を格納できる
パターンマッチングを使うことで分解でき、型推論もされる

val myTuple = ("MyStr", 10)
val (str, num) = myTuple
nanasinanasi

タプル型は()で表現できる。
基本的には推論されるので、わざわざ書く必要はあまりないかも?

val tuples: List[(String, Int)] = List(
  ("String", 349),
  ("MyStr", 12)
)

tuples
  .foreach((str, num) => println(str + num))
nanasinanasi

タプルはケースクラスと使い方が似るらしい。
JSのオブジェクトもときどきクラスにした方がいいか迷うし、それと同じ感じ?

nanasinanasi

タプルはJavaScriptの配列に当たりそう。
ReactのuseStateとかがまさにそんな感じな気がする。

const [state, setState] = useState("init")

↓これは架空のScalaコード

val (state, setState) = useState("init")
nanasinanasi

文字列に変数を埋め込むにはsを使う

val num = 10
val str = "Hello"

val concat = s"num: $num str: $str"
nanasinanasi

.などの記号が必要な場合は、{}で囲める

s"message: [${n.message}] to ${n.caller}"
nanasinanasi

ifには式を書けるので、三項演算子のような使い方ができる

val str = if (10 > 9) "Yes" else "No"
nanasinanasi

もちろん処理も書けるし、ブロックを使うこともできる

if (10 > 9) {
  println("Yes")
}
nanasinanasi

matchキーワードでパターンマッチングができる

val m = random match {
  case 0 => "zero"
  case 1 => "one"
  case _ => "other"
}
nanasinanasi

_は書かれている以外の全てのケースにマッチする

nanasinanasi

Scalaのパターンマッチング文はケースクラスで表現される代数型のマッチングに最も役立ちます。

Scalaでは抽出子オブジェクトのunapplyメソッドを使うと、ケースクラスのパターンを独自に定義することもできます。

nanasinanasi

正規表現は文字列の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")
}
nanasinanasi

戻り値のisDefinedを使うと、マッチしているかどうかの真偽値が取得できる。

val numberPattern = "[0-9]".r
val password = "hello-124"

if (numberPattern.findFirstMatchIn(password).isDefined)
  "OK"
else
  "Invalid"
nanasinanasi

↑はOption型のメソッドで、返り血がSomeならtrueNoneならfalseを返す。

nanasinanasi

正規表現で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"
}
nanasinanasi

文字列内の全てにマッチさせることもできる
それと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
nanasinanasi

パターンマッチングを使ってキャプチャグループを分解することはできないっぽい?

このスクラップは1ヶ月前にクローズされました