2019-06-28: ScalaMatsuri 2019
やったこと
マギレコ
気の迷いで10連を回してしまった。
https://twitter.com/make_now_just/status/1144254417132281857
https://twitter.com/make_now_just/status/1144254512212914176
なんでやろなぁ‥‥。負の物欲センサーヤバい。
ミリシタ
3枚引き2枚抜き。
琴葉以外引いてしまった‥‥。
こいつはヤベえ。
https://twitter.com/make_now_just/status/1144262272958849024
ScalaMatsuri
英語の発表が多い。
Scala 3って、私にはどんな影響があるの?
NetflixはScalaの企業らしい。
Scala 3の変更で良くなるところ:
enum構文の追加 (Javaとの互換性もある)- 和型の追加(例外を投げるのではなく和型として貸したり)
nullが独立の型(Null型になる。和型と組み合わせて使う)opaque typeの追加(AnyValを継承するような感じ)- extension function(
implicit classが楽に書ける) delegateとgiven(implicitparameterを明示的に渡すのと通常の関数の呼び出しが被るのを回避できる?)==の型チェックの強化traitがimplicitパラメータなどを取れるようになった。(普通に引数を取れるようになった)- タプルの暗黙のデコンストラクト(
caseを書かなくてよくなった。便利) - TASTy(JVMビットコード以前の中間表現)を使ったクロスコンパイル。
全体的に良さそう。ただ、小耳に挟んだ話だとScala 3が出るのにあと5年はかかるとか‥‥。厳しい。
再帰:スキーム,代数,finally tagless,データ型. 統合されたビジョン
isomorphism: to: A -> B, from: B -> Aの組。from(to(a)) = a かつ to(from(b)) = bを満たす。
Scalaの型システムだと制約は表せない‥‥。
trait Iso[A, B] {
def to(a: A): B
def from(b: B): A
}
Leibnizの引用:
Iso[A, B]: AとBの個数が同じ。
個数の数え方:
Either[A, B]:(A, B):A => B:Nothing:Unit:Boolean:
Type Algebraという。
次の型のfoldrを考える。
sealed class Expr
case class Lit(value: Int) extends Expr
case class Add(l: Expr, r: Expr) extends Expr
case class Mul(l: Expr, r: Expr) extends Expr
def foldr[Z](this: Expr)(lit: Int => Z)(add: (Z, Z) => Z)(mul: (Z, Z) => Z): Z =
this match {
case Lit(v) => lit(v)
case Add(l, r) => add(foldr(l)(lit)(add)(mul), foldr(r)(lit)(add)(mul))
case Mul(l, r) => mul(foldr(l)(lit)(add)(mul), foldr(r)(lit)(add)(mul))
}
の型に注目:
の部分にExprの構造が反映されている。
この部分をExprF[Z]と置く。
sealed trait ExprF[+Z]
case class LitF[Z](v: Int) extends ExprF[Nothing]
case class Add[Z](l: Z, r: Z) extends ExprF[Z]
case class Mul[Z](l: Z, r: Z) extends ExprF[Z]
このときの部分をF-Algebraという。
一般化して型にするとtype Algebra[F[_], C] = F[C] => C。
つまり、foldrのAlgebra[ExprF, ?]を受け取るバージョンは次のように書ける。
def foldAlg[Z](this: Expr)(alg: Algebra[ExprF, Z]): Z = this match {
case Lit(v) => alg(LitF(v))
case Add(l, r) => alg(AddF(foldAlg(l)(alg), foldAlg(r)(alg)))
case Mul(l, r) => alg(MulF(foldAlg(l)(alg), foldAlg(r)(alg)))
}
なおIso[Expr, ExprF[Expr]]。
def embed(this: Expr): ExprF[Expr] = this match {
case Lit(v) => LitF(v)
case Add(l, r) => AddF(l, r)
case Mul(l, r) => MulF(l, r)
}
def project(this: ExprF[Expr]): Expr = this match {
case LitF(v) => Lit(v)
case AddF(l, r) => Add(l, r)
case MulF(l, r) => Mul(l, r)
}
object ExprIso extends Iso[Expr, ExprF[Expr]] {
def to(a: Expr): ExprF[Expr] = embed(a)
def from(b: ExprF[Expr]): Expr = project(b)
}
少し戻って、今度はの部分に注目する。
こちらにもExprの構造が反映されている。
これをRecursion Schemeという。
trait ExprA[Z] {
def lit(v: Int): Z
def add(l: Z, r: Z): Z
def mul(l: Z, r: Z): Z
}
def foldRC[Z](this: Expr)(rc: ExprA[Z]): Z = this match {
case Lit(v) => rc.lit(v)
case Add(l, r) => rc.add(foldRC(l)(rc), foldRC(r)(rc))
case Mul(l, r) => rc.mul(foldRC(l)(rc), foldRC(r)(rc))
}
さらにこれらを型クラスを使って実装することをTagless Finalという。
(おそらくRecursion Schme = final encodingでtaglessというのが型クラスを使うこと、みたいな。一番最初のExprみたいな型をinitial encodingというっぽい)
で、ここからさらにFreeモナドってtagless finalだよねっていうのを確認していたけど、微妙によく分かっていない。
難しい‥‥。
DOT計算をやさしく説明する
DOT計算: Scala 3.0の核言語(Core Language)。
Scalaの核言語の歴史:
- vObj計算 (2003-)
- Featherweight Scala (2006-)
- DOT計算 (2012-)
核言語に必要なもの:
- 抽象構文木
- 型付け規則
- 操作的意味論
DOT特徴:
- Union型、Intersection型
- 型メンバ
- Path-dependent type
- subtyping
DOT計算の定義: https://github.com/namin/dot
DOT言語においての値はオブジェクトしかない。
その他型付け規則などが定義されている‥‥。
——時間が足りてない。
ハイパフォーマンスScala
スレッドの枯渇。コンテキストスイッチのコスト。GCのStop the World‥などが大きな問題。
- I/Oバウンドな処理に対する効率的なCPUの使用
- GCの停止時間を減らす
IO対策: non-blocking IOの利用。
GC: 適切なGCを選択。不用意なメモリを減らす。
メモリ使用料の減らし方:
- 適切なコレクションを使う。
- 正しいコレクションを使うためにマイクロベンチマークを取る。
- オートボクシングに気を付ける
@specializeアノテーション
- キャッシュのメモリ管理
まあそうだよな、という感がつよい。
sbt コア・コンセプト
sbt by exampleから入ると良い。
"Build System a la carte"いわくビルドシステムとは"automates repeatable tasks"。
sbt→カジュアルに関数型なビルドツール。(purelyとはちょっと違う)
sbtでの状態(State): ビルド構造とディスクの状態
コマンド: State => State。
コマンドは逐次処理される。
コマンドは低レベルな概念。
コマンドの例: commands, help, tasks, projects, set name := value, <command1>; <command2> (セミコロン), ++2.13 (++コマンド)
あー、昔はセミコロンを最初に置かなきゃいけなかったのはセミコロンがコマンド扱いだったからなのか‥‥。
(1.xからは特殊扱いされるようになったっぽい?)
コマンドの分類:
- あまり変更しないもの:
help,tasks - ビルド構造を変更するもの:
project,set - ディスクを変更するもの:
act - どちらも変更するもの:
++
サイドエフェクトが無いフリをしているビルドツール。
人間に返るのは実行結果のログで、ディスクの変更ではない。
イベントと時間の関係: メッセージの送信→受信の順序がある
かつなイベントは互いに並列という
sbtの.valueは、このタスクの前に実行されるタスク or コマンドを宣言。
この合成はApplicative。
プラグイン開発者はタスクを使う。
タスクは自動合成。コマンド合成はsrareful (Stateの変化を伴う)。
タスクの前にはsettingsのreloadが行われる。
~コマンド: ファイル監視。~testQuickで変わったテストだけ実行する。
ビルド構造: サブプロジェクトの構造: Key-Valueストア。
設定キー。foo/Compile/console/scalacOptions:
foo: subprojectCompile: configuration (Test,Compile)console: in-task (タスクの中限定)
設定キーの移譲ルールはよく分からん‥‥。
カスタムキーは最も広いスコープで定義して、参照の際はなるべく狭くする。
面白かった‥‥。
総括
Scala書きてえ‥‥。
福岡に着いた
ミリオン6th行くぞ。
https://twitter.com/make_now_just/status/1144540904503406592
https://twitter.com/make_now_just/status/1144568804887822336
ちなみに夕飯は一双という店で食べた。
豚骨スープは美味いがやはり臭いと思う。