Tour of Scala で勉強してみた
公開: 2023-10-19 改訂: 2023-10-19
はじめに
scala を勉強することにおいて有用なことは何も書きませんよ。期待しないでください。
https://docs.scala-lang.org/tour/tour-of-scala.html
↑これを読みながら、意識的に覚えておきたいことをピックアップして残しておきます。
Basics
https://docs.scala-lang.org/tour/basics.html
- Functions, Method の違いがよくわからん。名前のあるなしの差しかここでは分からなかった。(class のところ読むと分かるが、class のメンバーになれるかどうかの違いもある)
- Class と Case Class は同一視の仕方に違いがある。Class は参照を比較、Case Class は値で比較。なので Pattern Matching で有用なのは case class の方。
- Object はシングルトン。デザインパターンちょっと知ってて良かった。
- Trait はフィールドとメソッドを抽象したデータ型。class は複数の trait を拡張(extends)できる。
- Scala 3 ではより簡素な構文が使えるっぽい。
Unified Types
https://docs.scala-lang.org/tour/unified-types.html
- Scala の型には階層構造がある。
Any
がトップ。その直下にAnyVal
とAnyRef
がある。どちらも名前そのまま。- Type casting は片方向。逆向きはできない。単純にデータサイズが理由と思う。例えば Byte は 8-bit signed integer で、Short は 16-bit signed integer なので、Byte -> Short は情報損失せずに変換できるけど、Short -> Byte は情報損失の可能性がある。
Null
は scala では使うな。(使うな、で思っていればいいはず。今時点では。)
Classes
https://docs.scala-lang.org/tour/classes.html
- Scala 3 では
new
がいらない(参照:Universal Apply Methods) - Class は methods, values, variables, types, objects, traits, classes を含むことができる。member と呼ぶ。
- Setter method の名前の末尾に
_=
をつけることで、値のバリデーションをすることができるらしい。(使うこと無いかなぁ、どうやろ。)
Default Parameter Values
https://docs.scala-lang.org/tour/default-parameter-values.html
- タイトルのまんまだった。今時点では細かすぎる気がした。
Named Arguments
https://docs.scala-lang.org/tour/named-arguments.html
- method を call するとき、パラメータの名前を指定して渡すことができる。ヘェ〜。
- なので、default parameter と組み合わせて使うことも良いし、呼び出し側で名前を明示してぱっと見の readablity を上げることはできる。(この辺、IDE あれば良くない?って思うところもあるけど実際どうなんかな)
Traits
https://docs.scala-lang.org/tour/traits.html
- trait はインターフェースとフィールドをいくつかのクラスで共有するために使われる。
- Java 8 の interface に似ている。
- Subtyping のところがちょっとよく分かりにくかったが、色々調べてみると、単に僕が型システム・プログラミング言語論に疎いだけっぽい。
- ここの例では、
trait Pet
が Super type で、それを実装したDog
,Cat
が subtype であり、 val animals.= ArrayBuffer.empty[Pet]
という宣言から分かる通り、ここの実装は super type の型を宣言している。- しかし実際には super type ではなく subtype の値を使っており、subtype で問題なくコードが動くでしょってことを言ってるだけだと思う。
- データ型の抽象化ができてるでしょ、って話だと理解した。話としては深いところだけど、一旦ここではその程度の理解で十分だと思う。
- ここの例では、
Tuples
https://docs.scala-lang.org/tour/tuples.html
- Tuple と Case Class の違いは、名前をつけられるかどうか。Tuple は名前が無い。
- よりリーダブルにするなら Case Class を使ったほうが良いのかもしれない。
Class Composition With Mixins
https://docs.scala-lang.org/tour/mixin-class-composition.html
- mixin はクラスを構成(compose)するために使われる trait とのこと。「mixin は trait」。
- class は最大1つの super class を継承(extends)できて、複数の trait を mixin することができる。(← mixin は動詞的に理解しておいた方がわかりやすいな)
- trait, abstract class を組み合わせて使うと柔軟な実装ができる。trait が abstract class を継承するような例も紹介されている。trait は class では無いので、abstract members を実装しなくて済むのが便利なのか。上のリンクからコードを見ましょう。
Higher-Order Functions
https://docs.scala-lang.org/tour/higher-order-functions.html
- method を higher-order functions の引数に渡すことが可能。Scala compiler は method を function に強制(coerce)することができる。
Nested Methods
https://docs.scala-lang.org/tour/nested-functions.html
- 読んで字の如くって感じです。
Multiple Parameter Lists
https://docs.scala-lang.org/tour/multiple-parameter-lists.html
- ユースケースを紹介してくれているので、ここは頭に入れておきたい。
- type inference を推進(drive)する。はて?
- そもそも、Scala の型推論(type inference)は1つのパラメータリストに対して一度に行われる。ヘェ〜、これは大事だ!
- なので、例えば
foldLeft
を以下のような定義すると、型パラメータ A, B の推論がうまくできない。
def foldLeft1[A, B](as: List[A], b0: B, op: (B, A) => B) = ???
- 具体的には、以下のコードはコンパイルできない。
val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
foldLeft1(numbers, 0, _ + _)
- なぜなら、
_ + _
を型推論しようとしても、この時点ではまだ型パラメータ A, B を期待しており、具体的な型の情報が無い。 - よって、引数
op
を別のパラメータリストで定義することで、最初のパラメータリストの型推論の結果を使って型が推論される。なるほどなぁ〜。 - つまり、Multilpe parameter lists を使わなくても動くコードは書けるんだけど、型推論の力を存分に発揮して抽象化やすっきりしたコーディングをしていくには、有用なアプローチっぽいのね。
- 後は、implicit paramter を使う場合。これは後述。
- もう1つは部分適用(Partial Application)を使いたい場合。
- Currying ととてもよく似ている。特に呼び出し方はほぼ一緒。
- ただし、Scala の multiple parameter lists を指す言葉として"curry"を使うのはやめておきましょうってことらしい。理由は、
- Scala では、multiple parameter , multiple parameter lists は言語の一部として直接指定・実装される。(Haskell は全部自動でカリー化されるから、そこは違うね)
- Scala の標準ライブラリ
curried
,uncurried
メソッドと紛らわしい。
部分適用・カリー化のところは、正直 Haskell が羨ましいなぁと思っちゃうかなぁ。今時点では。
重要な違いとして認識しておこう。
Case Classes
https://docs.scala-lang.org/tour/case-classes.html
- case class は immutable data をモデリングするのに良い。
- case class のパラメータは public
val
とのこと。イミュータブル!(var
にもできるけど、推奨されない) - 値で比較される。reference ではない。
copy
メソッドで shallow copy できる。
Pattern Matching
https://docs.scala-lang.org/tour/pattern-matching.html
- まあよく知っているパターンマッチングな気がする。
- Sealed types は知らない。ちゃんと読む。
- ベースになる型が sealed だと、match 式において網羅されているかをコンパイラーがチェックしてくれるらしい。めっちゃ重要じゃん!
- ただし、ベースになる方と subtypes を同じファイルで定義しないといけないっぽい。なんかもどかしいな。
Singleton Objects
https://docs.scala-lang.org/tour/singleton-objects.html
- Object は、ただ1つのインスタンスしか持たないクラス。まさに singleton だ。遅延生成(?)されるっぽい。
- ある class と同じ名前の object は Companion Object と呼ばれる。反対にそういった class は、その object の companion class である。
- companion class/object は他方の private members にアクセスできる。へぇ。
- Factory Method を実装するのに object を使ったりするらしい。
- ドキュメントの最後の方に、「 Java での
static
memberは Scala では companion object のメンバーとしてモデリングされる」と書いてあった。 - つまり、object は static メソッドのような、クラスのインスタンスたちに依らない実装を提供することに使って、インスタンスに依存するような振る舞いは通常の class で実装すべきなんかな。そんな気がする。
Regular Expression Patterns
https://docs.scala-lang.org/tour/regular-expression-patterns.html
- String は
.r
メソッドで正規表現に変換できるらしい。"[0-9]".r
のように。 - match 式でのパターンマッチングに正規表現が使えるって。すごい!パタンマッチングが強力だ。
Extractor Objects
https://docs.scala-lang.org/tour/extractor-objects.html
apply
メソッドは、コンストラクターのようなもの。引数を受け取ってインスタンスを返す。- その逆の、
unapply
メソッドを持つ object を extractor object というらしい。何に使えるんや? - パターンマッチングで
unapply
メソッドを呼び出しているパターンがあるらしい。なるほど。
For Comprehensions
https://docs.scala-lang.org/tour/for-comprehensions.html
- 好きなシンタックス。概念。モナドもきっちり復習しておきたいなぁ。
- for の返り値の型は、最初のgenerator の型で決まる。最初が List なら List を返す。
- ガードも書ける。良い。
- Range を生成するシンタックスで
until
メソッドがあるらしい。リーダブルだ。 - 値を返さなくていいなら
yield
を省略できる。その時はUnit
が返される。
Generic Classes
https://docs.scala-lang.org/tour/generic-classes.html
- いわゆるジェネリクスですかね。
- 最後のサブタイプに関する注意書きがよくわからなかった。
Variances
https://docs.scala-lang.org/tour/variances.html
- 型パラメータがサブタイプに関してどのように振る舞うかをコントロールすることができます。
- covariant, contravariant が選べる。指定しなかったら invariant 。
class Foo[+A] // A covariant class
class Bar[-A] // A contravariant class
class Baz[A] // An invariant class
- Invariance とは:
- 型パラメータ間におけるサブタイプの関連は反映されない。
- コードの例があるのでわかりやすい。型パラメータに渡されたクラスについてサブタイプの関係があっても、Generic Class にはサブタイプの関係がない(あるとは言えない?)とコンパイラが判断するのが Invariance ってことね。
- これがなぜ問題になり得るのか?コンパイルできないということは何か理由があるはず。
- これもコードがあるので具体例はそちらを参照。ざっくり言うと、subtype のレイヤーで見た時に異なるかたが混じってしまうって感じ?
- Covariance とは:
- 型パタメータの親子関係がそのまま generic class の親子関係にできるような宣言。っぽい。
- コード例では、クラスのメンバーと immutable にすることで、実際の問題が起きないような実装をしている。それをコンパイラーに教える方法が
+A
なのかな? - なんかもうちょい奥が深い気がするけど、一旦この辺にしておく。
- Contravariance とは:
- 意味合い的には covariance と真逆(そりゃ英語の意味としてもそうなんだから。懐かしいなぁ、反変・共変ベクトルとか。)
- シリアライザーの実装例が紹介されている。確かに covariance とは逆のコードが書かれている。
- Immutability and Variance
- covariance での例が特に印象的だが、covariant なミュータブルコレクションは型安全性を破壊しうる。なるほど。
- だから
List
は covariant だし、scala.collection.mutable.ListBuffer
は invariant なんだってさ。覚えやすいな。(注意:Scala の List は immutable)
- ここは初めて触れる概念だなぁ。少なくとも、Typescript では真面目に考えてなかった。単に僕の知識不足。
Upper Type Bounds
https://docs.scala-lang.org/tour/upper-type-bounds.html
- これも Generic Class の型パラメータについての話。
- 型パラメータに渡す具体的な型の継承関係というか、型の親子関係を縛ることができる。これは上から縛るって感じだね。
Lower Type Bounds
https://docs.scala-lang.org/tour/lower-type-bounds.html
- upper type bounds とは逆、下から制限をかける。
- 先ほどの Variance との兼ね合いで使うことがあるみたい。
trait List[+A]
の説明が書いてある。Class, Trait は covariant だが、メソッドが contravariant となっている。要は、prepend
の引数の型がA
そのものなのがまずい。全体で covariant にしたいなら、prepend
引き受ける型はA
を subtype にもつような型に制限をかけないとおかしいことになる。- このページの冒頭で説明があるが、この例のように
B >: A
とかいた時に、大体の場合で、A
はクラスの型パラメータでB
がメソッドの型パラメータ。
Inner Classes
https://docs.scala-lang.org/tour/inner-classes.html
- Scala では、クラスのメンバーにクラスを含めることができる。
- 内部のクラスは、インスタンスごとに異なるクラスとしてコンパイラーが判断するので注意が必要。
- インスタンスによらないクラスとして使うには、クラスのパスを指定する必要がある。
Graph#Node
のように。
Abstract Type Members
https://docs.scala-lang.org/tour/abstract-type-members.html
- trait や abstract class のメンバーとして abstract type を含むことができる。
- anonymous class のインスタンス化に使ったりできる。コード例を参照。
Intersection Types, a.k.a Compound Types
https://docs.scala-lang.org/tour/compound-types.html
- a.k.a = Also Known As らしいね。
- 複数の型からそれらを満たすより"狭い"型を作る記法。
- Scala 2 から 3 で結構書き方変わったみたい。あとは複数繋げた時の暗黙的な括弧の扱いが違うっぽい?
- まあ名前の通り。
Self-Type
https://docs.scala-lang.org/tour/self-types.html
- ある trait の中で別の trait を直接参照するシンタックス。
- 当然、あるクラスがこの trait を実装するとき、両方の trait を実装する必要がある。mixin!
- なんかえらい複雑じゃないかな・・・どうなんやろ。。
- あと、
this
って何者・・・?
Contextual Parameters, a.k.a Implicit Parameters
https://docs.scala-lang.org/tour/implicit-parameters.html
- なんか暗黙的な引数を宣言するのか?
- これも、Scala 2 から 3 になってキーワードが変わっている。(Scala 3:
using/given
, Scala 2:implicit
) - コード見た方が早い。
- 書くのは楽そうだしスッキリしそうだけど、これ呼び出し側を見てから、その定義をちゃんと追えるのかな?
Implicit Conversions
https://docs.scala-lang.org/tour/implicit-conversions.html
- 特定の条件を満たしたときに暗黙的に型変換が行われるってやつね。(scala.Int --> scala.Long とか)
Polymorphic Methods
https://docs.scala-lang.org/tour/polymorphic-methods.html
- Generic Class と同じように、メソッドにも型パラメータを定義できるよ〜ってだけ。
Type Inference
https://docs.scala-lang.org/tour/type-inference.html
- コンパイラが型を推論してくれるよ。
- public API については明示的に型宣言するのをお薦めするよ。
Operators
https://docs.scala-lang.org/tour/operators.html
- Scala では、operator(演算子)はメソッド。
- 優先順位があるよ〜
By-name Parameters
https://docs.scala-lang.org/tour/by-name-parameters.html
- By-name parameters は使用されるたびに評価される。逆に言うと、使われなければ評価されないのがメリット。
- パラメータを by-name にするには
=>
を型に前置する。 - 対して通常のパラメータは by-value と言う。
- パフォーマンスとか意識する時に有用かもしれない。無駄に計算走らせないようにできるかも。
Annotations
https://docs.scala-lang.org/tour/annotations.html
- 定義にメタ情報を付与することができる。
@deprecated
とかね。コンパイラーが warning 出してくれるらしい。@tailrec
は、末尾最適をコンパイラーに明示的に指示することができる。もしメソッドが末尾最適できない実装をされていればエラーになるらしい。へぇ〜。- 色々ありそうね。
Packages and Imports
https://docs.scala-lang.org/tour/packages-and-imports.html
- 特になし。
Top Level Definitions in Packages
https://docs.scala-lang.org/tour/package-objects.html
- 特になし。
まとめ
とりあえず超初歩的なシンタックスは押さえられたかなぁと。
知っていたこともあれば、知らない・馴染みがないこともあった。特に Variance かな。
読みながら別途調べたりする中で、ドキュメントも目を通したりできたので、ここからより Scala および JVM の理解を深めていこうと思います。
新しい言語を覚えるのはいつも新鮮でいいですね。