Scala School/Basics2 学習メモ

## はじめに

ふと思い立ち数年越しの続編...前編(2020年...)は[こちら](https://qiita.com/watopin/items/a4a2ba6b9363e441ae82)

[Scala School](https://twitter.github.io/scala_school/basics2.html)は古く、scala3に対応していないので今更続きをやる意味があるのか微妙なところですが、せめてBasics Continuedまでだけでもやっておくかと思い。
完全に自分用・自己満足。

## 本編

### apply methods

applyという名前でメソッドを定義しておくと、クラスやオブジェクトが一つの目的(メソッド)で利用されるときに便利なシンタックスシュガーが使える。

```scala
object Foo {
  def apply() = 100
}

class Bar {
  def apply() = 200
}

Foo() //結果は100. Foo.apply()とわざわざやらなくてもOK

val b = new Bar
b() //結果は200. 同様にbar.apply()とやらなくでもOK
```


### Objects

オブジェクトは一つのインスタンスを保持するために使われる。
また、クラスと同名のオブジェクトはCompanion Objectというらしい。


### Functions are objects

関数の正体はトレイト(の実装)で、applyメソッドのシンタックスシュガーによりそのオブジェクトを関数のように呼ぶことができる、ということらしい。

```scala
object addOne extends Function1[Int, Int] {
    def apply(m: Int): Int = m + 1
}

addOne(1) //結果は2
```

このFunctionトレイトはFunction0から22まであるらしい。22というのは完全にマジックナンバーで、単純に引数の数は22もあれば十分でしょってことみたい。
objectだけでなくclassもFunction*トレイトをextendsできます。

また、`extends Function1[Int, Int]` は `extends (Int => Int)` と省略して書けるらしい。


### Packages

ファイルの先頭でパッケージを宣言するとそのファイルの中身はすべてそのパッケージに所属する。
値や関数はobjectやclassの外には書けない。


### Pattern Matching

```scala
val times = 1

times match {
    case 1 => "one"
    case 2 => "two"
    case _ => "some other number"
}

times match {
    case i if i == 1 => "one"
    case i if i == 2 => "two"
    case _ => "some other number"
}
```

caseに直接値を書いてマッチングする方法と、値を変数iにとらえてifで条件を書く方法(matching with guards)があるみたい。
`_` はワイルドカードで上のケースでマッチしなかったすべての状態を扱う。

matchは異なる型の値も扱うことができる。

```scala
def bigger(o: Any): Any = {
    o match {
        case i: Int if i < 0 => i - 1
        case i: Int => i + 1
        case d: Double if d < 0.0 => d - 0.1
        case d: Double => d + 0.1
        case text: String => text + "s"
    }
}
```

こんな感じ。

クラスのメンバーでマッチングする場合

```scala
def calcType(calc: Calculator) = calc match {
  case _ if calc.brand == "HP" && calc.model == "20B" => "financial"
  case _ if calc.brand == "HP" && calc.model == "48G" => "scientific"
  case _ if calc.brand == "HP" && calc.model == "30B" => "business"
  case _ => "unknown"
}
```

これは"painful"な方法とのこと。もう少しスッキリ書くには後述のcase classを使うとよいらしい。


### Case Classes

ケースクラスはパターンマッチングで使うためにデザインされた値を保持するためのクラスらしい。
また、newを使わずに生成できる(Scala SchoolのBasicsでは特に言及されてないですが、ケースクラスには自動的にコンパニオンオブジェクトが作られ、applyメソッドがファクトリーになっているため)。

各メンバーの値が同じなら `==` でtrueになるようにできている。普通のクラスと同様にメソッドを持つことも可能。

先ほどの"painful"なコードが

```scala
case class Calculator(brand: String, model: String) //ケースクラスの定義

def calcType(calc: Calculator) = calc match {
  case Calculator("HP", "20B") => "financial"
  case Calculator("HP", "48G") => "scientific"
  case Calculator("HP", "30B") => "business"
  case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}
```

このようになる。
最後のマッチは

```scala
case Calculator(_, _) => "Calculator of unknown type" //パターン1

case _ => "Calculator of unknown type" //パターン2

case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c) //パターン3
```

このように様々な書き方ができるよと紹介されていた。パターン3の変数cに再束縛するやりかたは

```scala
case c: Calculator => "Calculator: %s is of unknown type".format(c)
```

としたほうがわかりやすくないか？と思った。全く同じように動くし。
型を書かずに

```scala
case c => "Calculator: %s is of unknown type".format(c)
```

でもいいし。

### Exceptions

javaのようにtry-catch-finallyで例外を使うことができるようです。
例外をパターンマッチングさせることができるという点とtryもifなどと同様「式」であるという点が異なります。

```scala
val result: Int = try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => {
    log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
    0
  }
} finally {
  remoteCalculatorService.close()
}
```

例外が発生しなければ `remoteCalculatorService.add(1, 2)` の結果が、例外が発生しcatchに入ったら0が `result` に代入されます。

## おわりに

またScalaちょっと触ろうかなという気持ちがわいてきたのでよい復習になりました。
今はJetBrains AcademyのFunctional Programming in Scalaをやっています。こちらはつい最近出たコースなのでScala3が前提になってます。