Slide 1

Slide 1 text

Scala 3 勉強会 #2 文法 (2), インデントルール, ワイルドカード型 2021年9月10日〜2021年9月17日 溝上 友貴

Slide 2

Slide 2 text

#1の振り返り ● Scala 3 コンパイラは、.class ファイルだけでなく .tasty ファイルも生成する ● .tasty は、プログラムの完全な情報を持つ TASTy を表現する ● TASTy-Reader 経由で、Scala 2.13.6 のコードは Scala 3 のコードを読み 込める ● プログラムをシンプルにするような工夫が文法に追加された

Slide 3

Slide 3 text

やること ● New in Scala 3 [1] のココ(文法) ○ new キーワードについて ○ Scala 3 で追加されたインデント規則について ○ ワイルドカード型について

Slide 4

Slide 4 text

文法 (2)

Slide 5

Slide 5 text

new キーワードについて ● Constructor Proxy [2] ○ 具象クラスに自動で apply メソッドが生えるようになった class ConstructorProxy(s: String): def this() = this("") def hello = println(s) val c1 = ConstructorProxy("abc") val c2 = ConstructorProxy() c1.hello // => abc c2.hello // => Scala 2 Scala 3 class ConstructorProxy(s: String) { def this() = this("") def hello = println(s) } val c1 = new ConstructorProxy("abc") val c2 = new ConstructorProxy() c1.hello // => abc c2.hello // =>

Slide 6

Slide 6 text

Constructor Proxy: その仕組み ● コンパニオンオブジェクトに apply メソッドが生える ○ https://github.com/lampepfl/dotty/blob/3.1.0-RC1/compiler/src/dotty/tools/dotc/core/NamerOps.scala#L97 // 生成されるコードのイメージ object ConstructorProxy: inline def apply(s: String): ConstructorProxy = new ConstructorProxy(s) inline def apply(): ConstructorProxy = new ConstructorProxy()

Slide 7

Slide 7 text

Constructor Proxy: 生成規則 1. A constructor proxy companion object object C is created for a concrete class C, provided the class does not have already a companion, and there is also no other value or method named C defined or inherited in the scope where C is defined. 2. Constructor proxy apply methods are generated for a concrete class provided a. the class has a companion object (which might have been generated in step 1), and b. that companion object does not already define a member named apply. --------------------------------------------------------------------------------------------------- 1. コンパニオンオブジェクトを持っておらず、かつそのクラスと同名の値やメソッドがスコープ内になければ、Constructor Proxy のコンパニオンオブジェクトが生成される 2. Constructor Proxy の apply メソッドは、以下を満たす具象クラスに対して生成される: a. クラスがコンパニオンオブジェクト (1 で生成されたものを含む) を持っており、かつ b. コンパニオンオブジェクトが apply という名前のメンバーを定義していないこと

Slide 8

Slide 8 text

Constructor Proxy: 生成規則 ● 以下の場合、Constructor Proxy が生成される // コンパニオンオブジェクト、 apply メソッドが生成される class CHaveConstructorProxies(v: Int): def hello = println("CHaveConstructorProxies class have constructor proxies (companion and apply)") // apply メソッドが生成される class CHaveConstructorProxy(v: Int): def hello = println("CHaveConstructorProxy class have a constructor proxy (apply)") object CHaveConstructorProxy: def zero = CHaveConstructorProxy(0) scala> CHaveConstructorProxies(3).hello CHaveConstructorProxies class have constructor proxies (companion and apply) scala> CHaveConstructorProxy(3).hello CHaveConstructorProxy class have a constructor proxy (apply)

Slide 9

Slide 9 text

Constructor Proxy: もっと詳しく ● Constructor Proxy は case class ではない ○ getter, equals, toString 等は使えない scala> val cp = ConstructorProxy("abc") val cp: ConstructorProxy = ConstructorProxy@34073423 scala> cp.s 1 |cp.s |^^^^ |value s cannot be accessed as a member of (cp : ConstructorProxy) from module class rs$line$4$. scala> cp == ConstructorProxy("abc") val res0: Boolean = false scala> cp.toString val res1: String = ConstructorProxy@34073423

Slide 10

Slide 10 text

インデント規則

Slide 11

Slide 11 text

インデント規則 ● インデントの規則が追加されたことにより、 { } が必要でなくなった [3] ○ 正しくインデントされていない場合、 warning が出るようになった ○ 正しくインデントされたプログラムは { ... } で囲まれたプログラムと全く同じである object Hoge { def hello(name: String): Unit = { if (name.nonEmpty) { println("Hello, %s".format(name)) println("How are you today?") } else { println("Hello, world!") println("The blue sky is beautiful.") } println("Good bye :)") } } object Hoge: def hello(name: String): Unit = if (name.nonEmpty) println("Hello, %s".format(name)) println("How are you today?") else println("Hello, world!") println("The blue sky is beautiful.") println("Good bye :)") Scala 2 Scala 3

Slide 12

Slide 12 text

インデント規則: 正しくインデントされたプログラム 1. 中括弧で区切られた範囲において、ブロックの最初の文のインデントよりも左にイン デントしてはいけない def MaybeWarning(x: Int) = if (x < 0) { println(1) println(2) println(3) } ダメな例 [warn] -- Warning: /path/to/have-fun-scala3/chapters/src/main/scala/chapter 2/IndentRule.scala:9:4 [warn] 9 | println(3) [warn] | ^ [warn] | Line is indented too far to the left, or a `}` is missing [warn] one warning found [warn] one warning found コンパイル結果

Slide 13

Slide 13 text

インデント規則: 正しくインデントされたプログラム 2. Scala 2 や -no-indent のオプションをつけており、インデントされた式のサブ 部分が改行で終了しているとき、次の文はサブ部分より左にインデントがなければ いけない def MaybeError(x: Int) = if (x < 0) println(1) println(2) // error: missing `{` ダメな例(Scala 2 のコンパイラか、-no-indent をつけている場合)

Slide 14

Slide 14 text

インデント規則: その仕組み ● インデントの開始行末には ( { と同じ意味) が入る ● インデントの終了行末には ( } と同じ意味) が入る ● 現在のインデント位置は IW スタックで管理されている object Hoge: // def hello(name: String): Unit = // if (name.nonEmpty) // println("Hello, %s".format(name)) println("How are you today?") // else // println("Hello, world!") println("The blue sky is beautiful.") // println("Good bye :)") // // `IW` stack has this indent width

Slide 15

Slide 15 text

インデント規則: その仕組み ● 以下が成り立つとき、 が行末に入る a. インデント範囲が現在の位置から始まり、かつ b. 次行の最初のトークンのインデント幅が、現在のインデント幅より大きいこと ● なお、インデント範囲は、以下が成り立つときに始まる a. extension のパラメータの後であるか b. given インスタンスにおける with の後であるか c. 行末トークンの後であるか d. 以下のトークンのいずれかのあとであるか ■ = => ?=> <- catch do else finally for ■ if match return then throw try while yield e. if や while の条件式の閉じ括弧 ) の後であるか f. for ループの enumerator の閉じ括弧 ) or } の後であること

Slide 16

Slide 16 text

● a) extension のパラメータの後 ● b) given インスタンスにおける with の後 ● c) 行末トークン : の後 extension (v: Seq[BigDecimal]) // a) def avg: BigDecimal = v.sum / v.length インデント規則: が入る例 given orderingLocalDateTime: Ordering[LocalDateTime] with // b) def compare(x: LocalDateTime, y: LocalDateTime) = // d) x.compareTo(y) object endOfLine: // c) val x = 1

Slide 17

Slide 17 text

● d) 以下のトークンのいずれかの後 = => ?=> <- catch do else finally for if match return then throw try while yield def hello: Int => Unit = x => for y <- 0 to 5 do for z <- 6 to 10 yield if y % 2 == 0 then try throw new Exception("hoge error: " + y) catch case e => println(e.getMessage) finally println("z is divided by y") else if y % 3 == 0 then println("multiple number of three") else x match case _ if x % 2 == 0 => println("x is an even number") case _ => println("x is an odd number") インデント規則: が入る例 ※ ?=> これが何を意味しているのかわからなかったので、わかる人いたら教えてくだ さい・・・関数型を表すらしいけど・・・ [4] 2021年11月3日追記:これは Context Function 型です。詳しくは #3 で。 FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type | HKTypeParamClause '=>' Type

Slide 18

Slide 18 text

インデント規則: が入る例 ● e) if や while の条件式の閉じ括弧 ) の後 ● f) for ループの enumerator の閉じ括弧 ) or } の後 if (x > 0) // e) println("hello") var y = 0 while (y < 5) // e) println(y) y = y + 1 for (x <- 1 to 5) // f) yield println(x) for { x <- 1 to 5 } // f) yield println(x)

Slide 19

Slide 19 text

インデント規則: その仕組み ● 以下が成り立つとき、 が行末に入る a. 次の行の先頭トークンが現在のインデント幅より小さく b. 前の行の末尾トークンが、以下のトークンではないこと ■ then else do catch finally yield match c. 次の行の先頭トークンが infix 演算子の場合で、そのインデント幅が現在のインデント幅より小 さく、前のインデント幅と一致するか、終了インデント幅より小さいこと ● あるいは、以下が成り立つときも が入る a. 次のいずれかのトークンが で始まる文に続いており、そのインデント範囲を閉じること ■ then, else, do, catch, finally, yield, }, ), ], case b. インデント範囲が括弧によって閉じている場合、 から始まる文に続くコンマの前に入る

Slide 20

Slide 20 text

● a, b) 次の行の先頭トークンが現在のインデント幅より小さく、前の行の末尾トー クンが、以下のトークンではないこと then else do catch finally yield match (これらのトークンは、文が続くことを表すため) インデント規則: が入る例 if x < 0 then // println("parameter is negative.") // else // println("parameter is positive.") // extension (lhs: Option[Int]) // infix def + (rhs: Option[Int]): Option[Int] = // for // l <- lhs r <- rhs // yield // l + r

Slide 21

Slide 21 text

● c) 次の行の先頭トークンが infix 演算子の場合、そのインデント幅が現在のイ ンデント幅より小さく、前のインデント幅と一致するか、インデント範囲が終了するイ ンデント幅より小さいこと インデント規則: が入る例 if x > 0 then // Some(x) // + Some(2) + Some(3) // else None

Slide 22

Slide 22 text

Template body ● クラス、トレイト、オブジェクトを定義する template body に、括弧 { } が必要 なくなった ● 代わりに、template body の前の行末に : を入れる必要がある ● Enum の定義、package の定義にも同様の規則が導入された package chapter2: object TemplateBody: trait A: def f: Int class C(x: Int) extends A: def f = x object O: def f = 3 enum Color: case Red, Green, Blue

Slide 23

Slide 23 text

● インデント幅が「小さい」「大きい」という表現をしたが、スペースとタブが混在してい る場合、インデント幅を比較できるか? ● 比較できる場合とできない場合がある ○ 「2つのタブとそれに続く 4つのスペース」は、「 2つのタブとそれに続く 5つのスペース」よりも厳密に 小さくなる ○ 「2つのタブとそれに続く 4つのスペース」は、「 6つのタブ」や「4つのスペース」と比較できない ● なるべくどちらかに統一しよう スペースとタブの混在 // 例 if (x < 0) { println(1) // スペース2つ println(2) // タブ1つ }

Slide 24

Slide 24 text

インデントと中括弧 ● インデントは、中括弧 {}、ブラケット []、括弧 () と自由に混在させることができ る ● そのような範囲でのインデントの解釈は、以下の規則に基づく a. 中括弧で囲まれた範囲のインデント幅は、中括弧の後に開始された行の最初のトークンのインデン ト幅になる b. ブラケットまたは括弧で囲まれたインデント幅は以下のようになる ■ 開き括弧 [ or ( が行末にある場合、それに続くトークンのインデント幅になる ■ それ以外の場合は、囲んでいる括弧のインデント幅になる c. 閉じ括弧 }, ] or ) が検出されると、必要な数の が挿入される

Slide 25

Slide 25 text

● case に関するインデント規則 ○ match や catch の後に、match と同じインデント幅を持つ case が続いた場合、インデント範 囲が開始する ○ 上記の場合、case でない同じインデント幅のトークンにおいて、またはより小さいインデント幅の トークンにおいてインデント範囲が終了する Case 句の特別な取り扱い def f(x: Int) = // Indent region starts after this `match` because // the following `case` appears at the indentation width // that’s current for the match itself. x match case 1 => println("I") case 2 => println("II") case 3 => println("III") case 4 => println("IV") case 5 => println("V") case _ => println(x) println("indent region ended.") scala> chapter2.IndentRuleForCase.f(1) I indent region ended. scala> chapter2.IndentRuleForCase.f(5) V indent region ended. scala> chapter2.IndentRuleForCase.f(100) 100 indent region ended.

Slide 26

Slide 26 text

End マーカー ● end マーカーは、インデント範囲の終了を表すトークンである def largeMethod(...) = ... if ... then ... else ... // a large block end if ... // more code end largeMethod

Slide 27

Slide 27 text

End マーカー ● end マーカーのあとに指定可能な指定子トークンは以下の通り a. if while for match try new this val given ● end マーカー指定子トークンはその前にある文に対応している必要がある a. 文がメンバー x を定義する場合、指定子トークンは x b. 文がコンストラクターを定義する場合、指定子トークンは this c. 文が匿名 given を定義する場合、指定子トークンは given d. 文が匿名拡張メソッドを定義する場合、指定子トークンは extension e. 文が匿名クラスを定義する場合、指定子トークンは new f. 文が val 定義の場合、指定子トークンは val g. 文が package 句の場合、指定子トークンは package の識別子 h. 文が if, while, for, try, あるいは match の場合、指定子トークンはそれと同じトークン

Slide 28

Slide 28 text

End マーカー: 例 ● a) 文がメンバー x を定義する場合、指定子トークンは x ● b) 文がコンストラクターを定義する場合、指定子トークンは this // a) member object Member: // ... end Member // a) member abstract class Member(): // b) constructor def this(x: Int) = this() // ... end this def f: String end Member

Slide 29

Slide 29 text

End マーカー: 例 ● c) 文が匿名 given を定義する場合、指定子トークンは given ● e) 文が匿名クラスを定義する場合、指定子トークンは new // a) member object Member: // c) anonymous given given Member = // e) anonymous class new Member: // a) member def f = "!" end f end new end given end Member

Slide 30

Slide 30 text

End マーカー: 例 ● d) 文が匿名拡張メソッドを定義する場合、指定子トークンは extension ● e) 文が val 定義の場合、指定子トークンは val ● f) 文が package 句の場合、指定子トークンは package の識別子 // d) anonymous extension extension (x: Member) def ff: String = x.f ++ x.f end extension // f) val definition val a :: b = x :: Nil end val // f) package clause package chapter2.end.marker: // ... end marker

Slide 31

Slide 31 text

End マーカー: 例 ● h) 文が if, while, for, try, あるいは match の場合、指定子トークンは それと同じトークン // h) if statement if x > 0 then // h) while statement while y > 0 do println(y) y -= 1 end while // h) try statement try // h) match statement x match case 0 => println("0") case _ => end match finally println("done") end try end if

Slide 32

Slide 32 text

End マーカー: 使い所 ● end マーカーは、インデント範囲が一目でわからない場合にオススメ ● 例えば ○ インデント範囲が空白行を含む場合 ○ インデント範囲が15行〜20行以上の場合 ○ 4つ以上のインデントレベル等、大きくインデントされている場合

Slide 33

Slide 33 text

ワイルドカード型

Slide 34

Slide 34 text

ワイルドカード型 ● 型のワイルドカード引数の表記が _ から ? に変更された [5] ● 匿名の型パラメータの表記は _ のまま (def f: Int => Int = _ + 1) ● 型コンストラクターの表記は F[_] のまま (trait Functor[F[_]]) scala> def f(list: List[_]): Option[_] = | list.headOption def f(list: List[_]): Option[_] scala> f(List(1,2,3,4,5)) val res0: Option[Any] = Some(1) scala> def f(list: List[?]): Option[?] = | list.headOption | def f(list: List[?]): Option[?] scala> f(List(1,2,3,4,5)) val res0: Option[?] = Some(1) Scala 2 Scala 3

Slide 35

Slide 35 text

まとめ ● 具象クラスを定義すると、自動的に Constructor Proxy (コンパニオンオブジェ クトと apply メソッド) が生成されるようになった ● Scala 3 では、インデントでブロックを表せるようになった ○ いろんなルールがある ● 型引数のワイルドカードが _ から ? に変更になった

Slide 36

Slide 36 text

共有事項 ● #2 のサンプルプログラムは以下においています ○ https://github.com/taretmch/have-fun-scala3/tree/master/chapters/src/main/scala/chapter2

Slide 37

Slide 37 text

次回予告 ● 次回は2021/10/01(金) 予定 ● Contextual Abstractions を学んでいきます🙌 ○ Using 句 ○ Given 句 ○ extension methods ○ 暗黙の型変換 (implicit conversion)

Slide 38

Slide 38 text

[1] New in Scala 3, https://docs.scala-lang.org/scala3/new-in-scala3.html [2] Universal Apply Methods, https://docs.scala-lang.org/scala3/reference/other-new-features/creator-applications.html [3] Optional Braces, https://docs.scala-lang.org/scala3/reference/other-new-features/indentation.html [4] Scala 3 Syntax Summary, https://docs.scala-lang.org/scala3/reference/syntax.html [5] Wildcard Arguments in Types, https://docs.scala-lang.org/scala3/reference/changed-features/wildcards.html 参考リンク