Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Scala 3 勉強会#2 文法

Scala 3 勉強会#2 文法

社内で Scala 3 勉強会を実施した際の資料(公開用)。

目次
・Constructor Proxy
・インデント規則
・ワイルドカード型

Tomoki Mizogami

September 17, 2021
Tweet

More Decks by Tomoki Mizogami

Other Decks in Programming

Transcript

  1. #1の振り返り • Scala 3 コンパイラは、.class ファイルだけでなく .tasty ファイルも生成する • .tasty

    は、プログラムの完全な情報を持つ TASTy を表現する • TASTy-Reader 経由で、Scala 2.13.6 のコードは Scala 3 のコードを読み 込める • プログラムをシンプルにするような工夫が文法に追加された
  2. やること • New in Scala 3 [1] のココ(文法) ◦ new

    キーワードについて ◦ Scala 3 で追加されたインデント規則について ◦ ワイルドカード型について
  3. 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 // =>
  4. 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()
  5. 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 という名前のメンバーを定義していないこと
  6. 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)
  7. 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
  8. インデント規則 • インデントの規則が追加されたことにより、 { } が必要でなくなった [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
  9. インデント規則: 正しくインデントされたプログラム 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 コンパイル結果
  10. インデント規則: その仕組み • インデントの開始行末には <indent> ( { と同じ意味) が入る •

    インデントの終了行末には <outdent> ( } と同じ意味) が入る • 現在のインデント位置は IW スタックで管理されている object Hoge: // <indent> def hello(name: String): Unit = // <indent> if (name.nonEmpty) // <indent> println("Hello, %s".format(name)) println("How are you today?") // <outdent> else // <indent> println("Hello, world!") println("The blue sky is beautiful.") // <outdent> println("Good bye :)") // <outdent> // `IW` stack has this indent width
  11. インデント規則: その仕組み • 以下が成り立つとき、<indent> が行末に入る 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 } の後であること
  12. • a) extension のパラメータの後 • b) given インスタンスにおける with の後

    • c) 行末トークン : の後 extension (v: Seq[BigDecimal]) // a) <indent> def avg: BigDecimal = v.sum / v.length インデント規則: <indent> が入る例 given orderingLocalDateTime: Ordering[LocalDateTime] with // b) <indent> def compare(x: LocalDateTime, y: LocalDateTime) = // d) <indent> x.compareTo(y) object endOfLine: // c) <indent> val x = 1
  13. • 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") インデント規則: <indent> が入る例 ※ ?=> これが何を意味しているのかわからなかったので、わかる人いたら教えてくだ さい・・・関数型を表すらしいけど・・・ [4] 2021年11月3日追記:これは Context Function 型です。詳しくは #3 で。 FunType ::= FunTypeArgs (‘=>’ | ‘?=>’) Type | HKTypeParamClause '=>' Type
  14. インデント規則: <indent> が入る例 • e) if や while の条件式の閉じ括弧 )

    の後 • f) for ループの enumerator の閉じ括弧 ) or } の後 if (x > 0) // e) <indent> println("hello") var y = 0 while (y < 5) // e) <indent> println(y) y = y + 1 for (x <- 1 to 5) // f) <indent> yield println(x) for { x <- 1 to 5 } // f) <indent> yield println(x)
  15. インデント規則: その仕組み • 以下が成り立つとき、<outdent> が行末に入る a. 次の行の先頭トークンが現在のインデント幅より小さく b. 前の行の末尾トークンが、以下のトークンではないこと ▪

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

    yield match (これらのトークンは、文が続くことを表すため) インデント規則: <outdent> が入る例 if x < 0 then // <indent> println("parameter is negative.") // <outdent> else // <indent> println("parameter is positive.") // <outdent> extension (lhs: Option[Int]) // <indent> infix def + (rhs: Option[Int]): Option[Int] = // <indent> for // <indent> l <- lhs r <- rhs // <outdent> yield // <indent> l + r
  17. 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
  18. • インデント幅が「小さい」「大きい」という表現をしたが、スペースとタブが混在してい る場合、インデント幅を比較できるか? • 比較できる場合とできない場合がある ◦ 「2つのタブとそれに続く 4つのスペース」は、「 2つのタブとそれに続く 5つのスペース」よりも厳密に

    小さくなる ◦ 「2つのタブとそれに続く 4つのスペース」は、「 6つのタブ」や「4つのスペース」と比較できない • なるべくどちらかに統一しよう スペースとタブの混在 // 例 if (x < 0) { println(1) // スペース2つ println(2) // タブ1つ }
  19. インデントと中括弧 • インデントは、中括弧 {}、ブラケット []、括弧 () と自由に混在させることができ る • そのような範囲でのインデントの解釈は、以下の規則に基づく

    a. 中括弧で囲まれた範囲のインデント幅は、中括弧の後に開始された行の最初のトークンのインデン ト幅になる b. ブラケットまたは括弧で囲まれたインデント幅は以下のようになる ▪ 開き括弧 [ or ( が行末にある場合、それに続くトークンのインデント幅になる ▪ それ以外の場合は、囲んでいる括弧のインデント幅になる c. 閉じ括弧 }, ] or ) が検出されると、必要な数の <outdent> が挿入される
  20. • 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.
  21. 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 の場合、指定子トークンはそれと同じトークン
  22. 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
  23. 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
  24. 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
  25. 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
  26. End マーカー: 使い所 • end マーカーは、インデント範囲が一目でわからない場合にオススメ • 例えば ◦ インデント範囲が空白行を含む場合

    ◦ インデント範囲が15行〜20行以上の場合 ◦ 4つ以上のインデントレベル等、大きくインデントされている場合
  27. ワイルドカード型 • 型のワイルドカード引数の表記が _ から ? に変更された [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
  28. まとめ • 具象クラスを定義すると、自動的に Constructor Proxy (コンパニオンオブジェ クトと apply メソッド) が生成されるようになった

    • Scala 3 では、インデントでブロックを表せるようになった ◦ いろんなルールがある • 型引数のワイルドカードが _ から ? に変更になった
  29. 次回予告 • 次回は2021/10/01(金) 予定 • Contextual Abstractions を学んでいきます🙌 ◦ Using

    句 ◦ Given 句 ◦ extension methods ◦ 暗黙の型変換 (implicit conversion)
  30. [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 参考リンク