├── .gitignore ├── .sbtopts ├── README.md ├── aliases.sbt ├── build.sbt ├── docs ├── README.md ├── step01 │ ├── 01_環境構築.md │ ├── 02_Scala3のドキュメントを俯瞰する.md │ ├── 03_Referenceを俯瞰してみる.md │ ├── 04_Migration_guideを俯瞰してみる.md │ └── 05_インデントベースと新しい制御構造.md ├── step02 │ ├── 01_Scala3で追加された地味に嬉しい系の機能追加.md │ └── 02_Scala3で廃止または非推奨になった機能たち.md ├── step03 │ ├── 01_EnumsとADTs.md │ ├── 02_IntersectionTypesとUnionTypes.md │ └── 03_OpaqueTypeAliases.md ├── step04 │ ├── 01_CompatibilityReference.md │ ├── 02_TourOfTheMigrationTools.md │ ├── 03_Scala3MigrationMode.md │ ├── 04_Prerequisites.md │ ├── 05_PortingAnSbtProject.md │ ├── 06_Scala3MigratePlugin.md │ └── 07_IncompatibilityTable.md ├── step05 │ ├── 01_Referenceその他の機能たち.md │ └── 02_AnOverviewOfTASTY.md ├── step06 │ ├── 01_Referenceを俯瞰してみる.md │ ├── 02_ContextualAbstractionsの概要.md │ ├── 03_ContextualAbstractionsの4つの基本的な変更点.md │ └── 04_型クラスの実装.md └── step07 │ ├── 01_TypeClassDerivation.md │ ├── 02_MultiversalEquality.md │ ├── 03_ContextFunctions.md │ ├── 04_By-NameContextParameters.md │ └── 05_RelationshipWithScala2Implicits.md ├── project ├── Util.scala ├── build.properties └── plugins.sbt ├── sbt.sbt ├── step01 └── src │ └── main │ └── scala │ └── com │ └── github │ └── shinharad │ └── gettingStartedWithScala3 │ ├── NewControlSyntax.scala │ ├── OptionalBraces.scala │ └── Scala3SyntaxRewriting.scala ├── step02 └── src │ └── main │ └── scala │ └── com │ └── github │ └── shinharad │ └── gettingStartedWithScala3 │ ├── DeprecatedNonlocalReturns.scala │ ├── DroppedAutoApplication.scala │ ├── DroppedClassShadowing.scala │ ├── DroppedDoWhile.scala │ ├── DroppedLimit22.scala │ ├── DroppedPackageObjects.scala │ ├── DroppedProcedureSyntax.scala │ ├── DroppedSymbolLiterals.scala │ ├── DroppedWeakConformance.scala │ ├── DroppedWildcardInitializer.scala │ ├── ExtensionMethods.scala │ ├── MainMethods.scala │ ├── MatchExpressions.scala │ ├── ParameterUntupling.scala │ ├── RulesForOperators.scala │ ├── TraitParameters.scala │ ├── TraitParameters2.scala │ ├── UniversalApplyMethods.scala │ └── VarargSplices.scala ├── step03 └── src │ └── main │ └── scala │ └── com │ └── github │ └── shinharad │ └── gettingStartedWithScala3 │ ├── AlgebraicDataTypes.scala │ ├── Enumerations.scala │ ├── IntersectionTypes.scala │ ├── OpaqueTypeAliases1.scala │ ├── OpaqueTypeAliases2.scala │ ├── OpaqueTypeAliases3.scala │ ├── UnionTypes.scala │ ├── UnionTypes2.scala │ └── UnionTypes3.scala ├── step04 └── src │ └── main │ └── scala │ └── com │ └── github │ └── shinharad │ └── gettingStartedWithScala3 │ ├── 01_syntacticChanges │ ├── 01_RestrictedKeywords.scala │ ├── 02_ProcedureSyntax.scala │ ├── 03_ParenthesesAroundLambdaParameter.scala │ ├── 04_OpenBraceIndentationForPassingAnArgument.scala │ ├── 05_WrongIndentation.scala │ ├── 06__AsATypeParameter.scala │ └── 07_+and-AsTypeParameter.scala │ ├── 02_droppedFeatures │ ├── 01_SymbolLiterals.scala │ ├── 02_DoWhileConstruct.scala │ ├── 03_AutoApplication.scala │ └── 04_ValueEtaExpansion.scala │ └── 03_otherChangedFeatures │ └── 01_InheritanceShadowing.scala ├── step05 ├── explicit-nulls │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── shinharad │ │ │ └── explicitnulls │ │ │ └── JavaClass.java │ │ └── scala │ │ └── com │ │ └── github │ │ └── shinharad │ │ └── gettingStartedWithScala3 │ │ ├── ExplicitNulls1.scala │ │ ├── ExplicitNulls2.scala │ │ └── ExplicitNulls3.scala ├── import-export │ └── src │ │ └── main │ │ └── scala │ │ └── com │ │ └── github │ │ └── shinharad │ │ └── gettingStartedWithScala3 │ │ ├── ChangesInOverloadResolution.scala │ │ ├── ExportClauses.scala │ │ ├── Imports.scala │ │ ├── PackageObjectMigrationWithExportClauses.scala │ │ ├── after │ │ └── ToplevelDefinitions.scala │ │ └── before │ │ └── package.scala └── open-class │ └── src │ └── main │ └── scala │ └── com │ └── github │ └── shinharad │ └── gettingStartedWithScala3 │ ├── OpenClassEncryptedWriter.scala │ ├── OpenClassWriter.scala │ └── PatternBindings.scala └── step06 └── src └── main └── scala └── com └── github └── shinharad └── gettingStartedWithScala3 ├── ContextBounds.scala ├── Functors1.scala ├── Functors2.scala ├── GivenInstance.scala ├── ImplementingTypeClasses.scala ├── ImplicitConversions.scala ├── ImportingGivens.scala ├── Monads.scala ├── SemigroupsAndMonoids.scala ├── TypeLambdas.scala └── UsingClauses.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode/ 3 | .bloop/ 4 | .bsp/ 5 | .dotty-ide* 6 | .idea/ 7 | .metals/ 8 | *.worksheet.sc 9 | metals.sbt 10 | target/ -------------------------------------------------------------------------------- /.sbtopts: -------------------------------------------------------------------------------- 1 | -J-Xmx4g 2 | -J-XX:ReservedCodeCacheSize=1g 3 | -J-XX:MaxMetaspaceSize=2g -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [WIP] getting-started-with-scala3 2 | 3 | **:construction: このリポジトリは作成中です :construction:** 4 | 5 | ## 概要 6 | 7 | このリポジトリは、Scala 3 の公式ドキュメントを散策しながら Scala 3 への理解を少しずつ深めていくことを目的としています。 8 | 9 | 基本的に、自分が誰かに Scala 3 の良さを伝えたり、議論の材料として利用するために作成しています。 10 | 11 | ## 今後の更新について 12 | 13 | Scala 3 が正式リリースされたこともあり、[Documentation for Scala 3](https://docs.scala-lang.org/scala3) の整備が進んでいます。一応、このリポジトリは細々と更新は続けますが、今後はそちらを見るのが良さそうです。 14 | 15 | 以前は Scala 3 のドキュメントが、下記のような状況だったため、それらをガイドする位置づけとしてこのリポジトリを作成していました。 16 | 17 | - [Documentation for Scala 3](https://docs.scala-lang.org/scala3) は整備されていないページが多かった 18 | - [Documentation for Scala 3](https://docs.scala-lang.org/scala3) と [EPFLのドキュメント](https://dotty.epfl.ch)、[Scala 3 Migration guide](https://scalacenter.github.io/scala-3-migration-guide) は、相互にリンクが張られていたものの、それぞれが独立して更新されていたため、内容の重複が多かった 19 | 20 | しかし、今はこのように整備が進んでいるため、[Documentation for Scala 3](https://docs.scala-lang.org/scala3) がドキュメントの入り口として良さそうです。 21 | 22 | - [Documentation for Scala 3](https://docs.scala-lang.org/scala3) の整備が進んでいる 23 | - [Scala 3 Migration guide](https://scalacenter.github.io/scala-3-migration-guide) が [Documentation for Scala 3](https://docs.scala-lang.org/scala3) に統合された 24 | 25 | このリポジトリとしては、Scala 3 の各機能について、日本語で要約していることと、サンプルコードを手元の環境ですぐに試すことができるという点では、まだまだ利用価値がありそうなので、細々と更新を続けます。 26 | 27 | ## 対象者 28 | 29 | - Scala 2 には触れたことがあるけど、Scala 3 はこれからという方 30 | 31 | ## Scalaのバージョン 32 | 33 | `3.0.2` を対象としています。 34 | 35 | ## 環境 36 | 37 | このリポジトリは、以下の環境で作成しています。 38 | 39 | - JDK 40 | - [Eclipse Adoptium (AdoptOpenJDK) 8](https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot) 41 | - [sbt](https://www.scala-sbt.org/download.html) 42 | - [Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code/) 43 | - [Scala (Metals)](https://marketplace.visualstudio.com/items?itemName=scalameta.metals) 44 | - [Markdown Preview Enhanced](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced) 45 | - [Graphviz](https://www.graphviz.org/) : PlantUML の描画用に 46 | 47 | ドキュメントは、Markdown 内に PlantUML を書いてるので、[Markdown Preview Enhanced](https://shd101wyy.github.io/markdown-preview-enhanced/#/) で閲覧することをおすすめします。(Markdown から HTML の変換は追々やります) 48 | 49 | ## 公式ドキュメントの散策 50 | 51 | [こちらへ](docs) 52 | -------------------------------------------------------------------------------- /aliases.sbt: -------------------------------------------------------------------------------- 1 | import Util._ 2 | 3 | addCommandAlias("l", "projects") 4 | addCommandAlias("ll", "projects") 5 | addCommandAlias("ls", "projects") 6 | addCommandAlias("cd", "project") 7 | addCommandAlias("root", "cd root") 8 | addCommandAlias("c", "compile") 9 | addCommandAlias("t", "test") 10 | addCommandAlias("r", "run") 11 | addCommandAlias("m", "show discoveredMainClasses") 12 | 13 | onLoadMessage += 14 | s"""| 15 | |╭──────────────────────────────────────────╮ 16 | |│ List of defined ${styled("aliases")} │ 17 | |├─────────────┬────────────────────────────┤ 18 | |│ ${styled("l")} | ${styled("ll")} | ${styled("ls")} │ projects │ 19 | |│ ${styled("cd")} │ project │ 20 | |│ ${styled("root")} │ cd root │ 21 | |│ ${styled("m")} │ show discoveredMainClasses │ 22 | |│ ${styled("c")} │ compile │ 23 | |│ ${styled("t")} │ test │ 24 | |│ ${styled("r")} │ run │ 25 | |╰─────────────┴───────────────────────────-╯""".stripMargin -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalaVersion := "3.0.2" 2 | ThisBuild / version := "0.0.1-SNAPSHOT" 3 | 4 | ThisBuild / scalacOptions ++= Seq( 5 | "-encoding", "utf8", 6 | "-deprecation", 7 | "-unchecked" 8 | ) 9 | 10 | lazy val root = 11 | project 12 | .in(file(".")) 13 | .aggregate( 14 | step01, 15 | step02, 16 | step03, 17 | step04, 18 | `step05-import-export`, 19 | `step05-open-class`, 20 | `step05-explicit-nulls`, 21 | step06 22 | ) 23 | 24 | lazy val step01 = 25 | project 26 | .in(file("step01")) 27 | .settings(commonSettings) 28 | .settings( 29 | scalacOptions ++= Seq( 30 | // Scala 3 Syntax Rewriting 31 | 32 | // 1行ずつコメントアウトを外すことで、 33 | // Scala3SyntaxRewriting.scala の内容が書き換わることを確認してみてください 34 | 35 | // "-rewrite", "-indent" 36 | // "-rewrite", "-new-syntax" 37 | // "-rewrite", "-no-indent" 38 | // "-rewrite", "-old-syntax" 39 | ) 40 | ) 41 | 42 | lazy val step02 = 43 | project 44 | .in(file("step02")) 45 | .settings(commonSettings) 46 | 47 | lazy val step03 = 48 | project 49 | .in(file("step03")) 50 | .settings(commonSettings) 51 | 52 | lazy val step04 = 53 | project 54 | .in(file("step04")) 55 | .settings(commonSettings) 56 | .settings( 57 | scalacOptions ++= Seq( 58 | // Scala 3.0 Migration mode 59 | 60 | // 以下のコンパイラオプションを有効にすることで、 61 | // Scala 2.13 と Scala 3.0 の非互換性に関する詳細なエラーを表示したり、 62 | // コードが自動的に書き換わります 63 | 64 | // 以下を有効にすると詳細なエラーが表示されます 65 | // "-explain", 66 | 67 | // 以下を有効にすると、非互換性のエラーが警告に変わります 68 | // "-source:3.0-migration" 69 | 70 | // 以下を有効にすると、非互換性の警告箇所が自動的に書き換わります 71 | // "-source:3.0-migration", "-rewrite" 72 | ) 73 | ) 74 | 75 | lazy val `step05-import-export` = 76 | project 77 | .in(file("step05/import-export")) 78 | .settings(commonSettings) 79 | 80 | lazy val `step05-open-class` = 81 | project 82 | .in(file("step05/open-class")) 83 | .settings(commonSettings) 84 | .settings( 85 | scalacOptions ++= Seq( 86 | "-source", "future" 87 | ) 88 | ) 89 | 90 | lazy val `step05-explicit-nulls` = 91 | project 92 | .in(file("step05/explicit-nulls")) 93 | .settings(commonSettings) 94 | .settings( 95 | scalacOptions ++= Seq( 96 | "-Yexplicit-nulls", 97 | "-Ysafe-init", 98 | // "-language:unsafeNulls" 99 | ) 100 | ) 101 | 102 | lazy val step06 = 103 | project 104 | .in(file("step06")) 105 | .settings(commonSettings) 106 | .settings( 107 | scalacOptions ++= Seq( 108 | "-Ykind-projector", 109 | "-explain", 110 | "-explain-types", 111 | // "-Xprint:typer", 112 | // "-Xprint:parser" 113 | ) 114 | ) 115 | 116 | lazy val commonSettings = Seq( 117 | libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % "test" 118 | ) -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | Scala 3 のドキュメントの散策は、このような流れで進めたいと思います。 4 | 5 | ## Part 1 6 | 7 | ### Step1 8 | 9 | 最初に Scala 3 を書くための環境構築をします。次に、Scala 3 の公式ドキュメントを俯瞰し、最後にインデントベースのシンタックスと新しい制御構造について触れます。 10 | 11 | ### Step2 12 | 13 | 前半は Scala 3 で追加された「地味に嬉しい系の機能追加」について、後半は Scala 3 で廃止または非推奨になった機能について取り上げていきます。 14 | 15 | ### Step3 16 | 17 | Enums や Algebraic Data Types (ADTs) で新しいシンタックスが追加されたことで、今までの冗長な記述が簡潔に書けるようになったことや、新しく追加された Intersection Types、Union Types、Opaque Type Aliases で型の表現がより豊かになったことで、設計の幅が広がることを確認します。 18 | 19 | ### Step4 20 | 21 | Migration guide より、Scala 2.13 と Scala 3 の互換性について触れた後、Scala 3 へ移行するための前提条件や各種ツール、移行チュートリアルを確認します。そして最後に、Scala 2.13 と Scala 3 の非互換性についても確認します。 22 | 23 | ### Step5 24 | 25 | Step1 では、この Part で見ておきたいものを抽出しましたが、ここまででまだ取り上げていない Scala 3 の新機能がいくつか残っています。具体的には、Imports、Export Clauses、Open Classes、Explicit Nulls ですが、このStepではまずそれらを見ていきたいと思います。そして最後に TASTy についても簡単に触れたいと思います。 26 | 27 | ## Part 2 28 | 29 | ### Step6 30 | 31 | 最初に Scala 3 の機能について改めて Reference を俯瞰し、Part 2 で確認するものを決めたいと思います。その後、Scala 2 の implicit の再設計である Contextual Abstractions の概要と4つの基本的な変更点を確認し、それらを使用した型クラスの実装例を見ていきます。 32 | 33 | ### Step7 34 | 35 | 前の Step では、Contextual Abstractions の前半部分を確認しました。この Step では、Contextual Abstractions の後半として、Type Class Derivation や Multiversal Equality などに触れ、最後に Scala 2 の implicit との関連性を確認します。(仮) 36 | 37 | ### Step8 38 | 39 | :construction: 考え中 :construction: -------------------------------------------------------------------------------- /docs/step01/01_環境構築.md: -------------------------------------------------------------------------------- 1 | # 環境構築 {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | 8 | - [前提](#前提) 9 | - [プロジェクトの雛形を作成してみる](#プロジェクトの雛形を作成してみる) 10 | - [実行してみる](#実行してみる) 11 | - [生成されたMain.scalaの確認](#生成されたmainscalaの確認) 12 | - [エディタやIDE](#エディタやide) 13 | 14 | 15 | 16 | 公式ドキュメントを見ていく前に、Scala 3 を動かす環境を準備しましょう。 17 | 18 | ## 前提 19 | 20 | この辺がインストールされていること。 21 | 22 | - JDK 23 | - [Eclipse Adoptium (AdoptOpenJDK) 8](https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot) など 24 | - [sbt](https://www.scala-sbt.org/download.html) 25 | 26 | ## プロジェクトの雛形を作成してみる 27 | 28 | sbt new でプロジェクトの雛形を作成します。 29 | 30 | ``` 31 | $ sbt new scala/scala3.g8 32 | ``` 33 | 34 | :exclamation: 生成されたプロジェクトの Scala のバージョンが求めているものか確認してください。 35 | 36 | プロジェクト名を求められたら任意の名前を入力します。 37 | 38 | ``` 39 | name [Scala 3 Project Template]: xxx 40 | ``` 41 | 42 | 完了すると、このような構成のプロジェクトが作成されるはずです。 43 | 44 | ``` 45 | $ tree --noreport 46 | . 47 | ├── README.md 48 | ├── build.sbt 49 | ├── project 50 | │   ├── build.properties 51 | │   └── plugins.sbt 52 | └── src 53 | ├── main 54 | │   └── scala 55 | │   └── Main.scala 56 | └── test 57 | └── scala 58 | └── Test1.scala 59 | ``` 60 | 61 | ## 実行してみる 62 | 63 | sbt のインタラクティブモードに入ります。 64 | 65 | ``` 66 | $ cd [project name] 67 | $ sbt 68 | sbt:scala3-simple> 69 | ``` 70 | 71 | `run` と打って実行してみましょう。 72 | 73 | このような表示が出たら成功です。 74 | 75 | ``` 76 | Hello world! 77 | I was compiled by Scala 3. :) 78 | ``` 79 | 80 | ## 生成されたMain.scalaの確認 81 | 82 | `src/main/scala/Main.scala` のコードを見てみましょう。 83 | Scala 2 との違いを感じるはずです。 84 | 85 | ## エディタやIDE 86 | 87 | 現状は、このどちらかの選択になりそうなので、お好きな方をインストールしてください。 88 | 個人的には Visual Studio Code と Metals の方が使いやすい印象です(人によって感じ方が違うかも) 89 | 90 | - [Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code/) などのテキストエディタ & [Scala (Metals)](https://marketplace.visualstudio.com/items?itemName=scalameta.metals) 91 | - [IntelliJ IDEA](https://www.jetbrains.com/ja-jp/idea/) & [IntelliJ Scala Plugin](https://blog.jetbrains.com/scala/) 92 | 93 | **:tada::tada::tada: これで Scala 3 を書くための環境が整いました。 :tada::tada::tada:** 94 | -------------------------------------------------------------------------------- /docs/step01/02_Scala3のドキュメントを俯瞰する.md: -------------------------------------------------------------------------------- 1 | # Scala 3 のドキュメントを俯瞰する {ignore=true} 2 | 3 | それでは、Scala 3 の公式ドキュメントを俯瞰してみましょう。 4 | 5 | 参照するのは以下になります。 6 | 7 | 1. [Documentation for Scala 3](https://docs.scala-lang.org/scala3) 8 | 1. ~~[EPFLのドキュメント](https://dotty.epfl.ch)~~ 9 | 1. ~~[Scala 3 Migration guide](https://scalacenter.github.io/scala-3-migration-guide)~~ (今は [Documentation for Scala 3](https://docs.scala-lang.org/scala3) に統合されています) 10 | 11 | ## 1. Documentation for Scala 3 12 | 13 | https://docs.scala-lang.org/scala3 14 | 15 | 各種 Scala 3 のドキュメントは、ここからリンクされています。 16 | 17 | サイトの構成はこのようになっています。 18 | 19 | - [New in Scala 3](https://docs.scala-lang.org/scala3/new-in-scala3.html) 20 | - [Getting started](https://docs.scala-lang.org/scala3/getting-started.html) 21 | - [Scala 3 Book](https://docs.scala-lang.org/scala3/book/introduction.html) 22 | - [Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) 23 | - [Guides](https://docs.scala-lang.org/scala3/guides.html) 24 | - [API](https://dotty.epfl.ch/api/index.html) 25 | - [Language Reference](https://dotty.epfl.ch/docs/reference/overview.html) 26 | - [All New Scaladoc for Scala 3](https://docs.scala-lang.org/scala3/scaladoc.html) 27 | 28 | この内、[Scala 3 Book](https://docs.scala-lang.org/scala3/book/introduction.html) は、Scala の言語仕様を Scala 3 で学びたいという初学者向けのコンテンツとなっており、ページによっては Scala 3 公式サイトの [Reference](https://dotty.epfl.ch/docs/reference/overview.html) よりも分かりやすい解説がされているものもあります。そのため、こちらは補足的に見ていこうかなと思ってます。 29 | 30 | また、[Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) には、Scala 2 から Scala 3 へのマイグレーション方法や、互換性・非互換性、各種ツールなどが紹介されているので、こちらも見ていくことにします。(以前は Scala Center の [Scala 3 Migration guide](https://scalacenter.github.io/scala-3-migration-guide/) 置かれていましたが、こちらに統合されたようです) 31 | 32 | ## ~~2. EPFLのドキュメント~~ 33 | 34 | **[Documentation for Scala 3](https://docs.scala-lang.org/scala3) に統合されました** 35 | 36 | ~~https://dotty.epfl.ch~~ 37 | 38 | ~~EPFLのScala 3 (Dotty) のドキュメントが配置されています。~~ 39 | トップからたどれるページはこのような構成になってます。 40 | 41 | - ~~[Blog](https://dotty.epfl.ch/blog/index.html)~~ 42 | - ~~[Usage](https://dotty.epfl.ch/docs/Usage/index.html)~~ 43 | - ~~[Reference](https://dotty.epfl.ch/docs/reference/overview.html)~~ 44 | - ~~[Contributing](https://dotty.epfl.ch/docs/Contributing/index.html)~~ 45 | - ~~[Internals](https://dotty.epfl.ch/docs/Internals/index.html)~~ 46 | - ~~[Resources](https://dotty.epfl.ch/docs/Resources/index.html)~~ 47 | - ~~[API](https://dotty.epfl.ch/api/index.html)~~ 48 | 49 | ~~この内、[Reference](https://dotty.epfl.ch/docs/reference/overview.html) が、Scala 3 の新しい仕様について詳しく書かれているので、ここを中心に見ていくことにします。~~ 50 | 51 | ## ~~3. Scala 3 Migration guide~~ 52 | 53 | **[Documentation for Scala 3](https://docs.scala-lang.org/scala3) に統合されました** 54 | 55 | ~~https://scalacenter.github.io/scala-3-migration-guide~~ 56 | 57 | ~~[Scala Center](https://scala.epfl.ch/) で作成されている、Scala 2 から Scala 3 へのマイグレーションガイドなどが掲載されています。特に、Scala 2 と Scala 3 の互換性については非常に参考になります。~~ 58 | 59 | - ~~[Scala 3 Migration guide](https://scalacenter.github.io/scala-3-migration-guide/)~~ 60 | 61 | ~~こちらも、[Reference](https://dotty.epfl.ch/docs/reference/overview.html) と併せて見ていきたいと思います。~~ 62 | 63 | -------------------------------------------------------------------------------- /docs/step01/03_Referenceを俯瞰してみる.md: -------------------------------------------------------------------------------- 1 | # Referenceを俯瞰してみる {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [New Types](#new-types) 11 | - [Enums](#enums) 12 | - [Contextual Abstractions](#contextual-abstractions) 13 | - [Metaprogramming](#metaprogramming) 14 | - [Other New Features](#other-new-features) 15 | - [Other Changed Features](#other-changed-features) 16 | - [Dropped Features](#dropped-features) 17 | 18 | 19 | 20 | ## 概要 21 | 22 | Scala 3 の新しい機能を確認していくのは、やはり [Reference](https://dotty.epfl.ch/docs/reference/overview.html) が良さそうです。 23 | ただ、結構なページ数があるのと、難易度がバラバラなので、上から順番に見ていくのはしんどいかもしれません。そこで、まずはこの Part で見ておきたいものをあらかじめ抽出しておいて、それらを順番に見ていきたいと思います。 24 | 25 | まず、Reference はこのようなカテゴリに分かれています。 26 | 27 | ```plantuml 28 | @startmindmap 29 | * Reference 30 | ** New Types 31 | ** Enums 32 | ** Contextual Abstractions 33 | ** Metaprogramming 34 | ** Other New Features 35 | ** Other Changed Features 36 | ** Dropped Features 37 | @endmindmap 38 | ``` 39 | 40 | それぞれのカテゴリには、Scala 3 の様々な機能についてのページがありますが、 41 | 取りあえずカテゴリごとにこの Part で見ておきたいページに色付けしてみましょう。 42 | 43 | 44 | ## New Types 45 | 46 | https://dotty.epfl.ch/docs/New%20Types/ 47 | 48 | ```plantuml 49 | @startmindmap 50 | *[#fff] New Types 51 | **[#38c0c4] Intersection Types 52 | **[#38c0c4] Union Types 53 | **[#fff] Type Lambdas 54 | **[#fff] Match Types 55 | **[#fff] Dependent Function Types 56 | **[#fff] Polymorphic Function Types 57 | @endmindmap 58 | ``` 59 | 60 | ## Enums 61 | 62 | https://dotty.epfl.ch/docs/Enums/ 63 | 64 | ```plantuml 65 | @startmindmap 66 | *[#fff] Enums 67 | **[#38c0c4] Enumerations 68 | **[#38c0c4] Algebraic Data Types 69 | **[#fff] Translation of Enums and ADTs 70 | @endmindmap 71 | ``` 72 | 73 | ## Contextual Abstractions 74 | 75 | https://dotty.epfl.ch/docs/Contextual%20Abstractions/ 76 | 77 | Scala 2 では様々な役割を持っていた `implicit` が、Scala 3 では機能毎に書き方が分かれました。implicit parameter や型クラスの新しい書き方などは今後見るとして、最初は `Extension Methods` を見ていきましょう。 78 | 79 | ```plantuml 80 | @startmindmap 81 | *[#fff] Contextual Abstractions 82 | **[#fff] Overview 83 | **[#fff] Given Instances 84 | **[#fff] Using Clauses 85 | **[#fff] Context Bounds 86 | **[#fff] Importing Givens 87 | **[#38c0c4] Extension Methods 88 | **[#fff] Implementing Type classes 89 | **[#fff] Type Class Derivation 90 | **[#fff] Multiversal Equality 91 | **[#fff] Context Functions 92 | **[#fff] Implicit Conversions 93 | **[#fff] By-Name Context Parameters 94 | **[#fff] Relationship with Scala 2 Implicits 95 | @endmindmap 96 | ``` 97 | 98 | ## Metaprogramming 99 | 100 | https://dotty.epfl.ch/docs/Metaprogramming/ 101 | 102 | Metaprogramming は必要に応じて見れば良いので、今回は除外します。 103 | 104 | ```plantuml 105 | @startmindmap 106 | *[#999] Metaprogramming 107 | **[#999] Overview 108 | **[#999] Inline 109 | **[#999] Compile-time operations 110 | **[#999] Macros 111 | **[#999] Runtime Multi-Stage Programming 112 | **[#999] Reflection 113 | **[#999] TASTy Inspection 114 | @endmindmap 115 | ``` 116 | 117 | ## Other New Features 118 | 119 | https://dotty.epfl.ch/docs/Other%20New%20Features/ 120 | 121 | ```plantuml 122 | @startmindmap 123 | *[#fff] Other New Features 124 | **[#38c0c4] Trait Parameters 125 | **[#fff] Transparent Traits 126 | **[#38c0c4] Universal Apply Methods 127 | **[#38c0c4] Export Clauses 128 | **[#38c0c4] Opaque Type Aliases 129 | **[#38c0c4] Open Classes 130 | **[#38c0c4] Parameter Untupling 131 | **[#fff] Kind Polymorphism 132 | **[#fff] The Matchable Trait 133 | **[#fff] The @threadUnsafe annotation 134 | **[#fff] The @targetName annotation 135 | **[#38c0c4] New Control Syntax 136 | **[#38c0c4] Optional Braces 137 | **[#38c0c4] Explicit Nulls 138 | **[#fff] Safe Initialization 139 | **[#fff] TypeTest 140 | @endmindmap 141 | ``` 142 | 143 | ## Other Changed Features 144 | 145 | https://dotty.epfl.ch/docs/Other%20Changed%20Features/ 146 | 147 | ```plantuml 148 | @startmindmap 149 | *[#fff] Other Changed Features 150 | **[#fff] Numeric Literals 151 | **[#fff] Programmatic Structural Types 152 | **[#38c0c4] Rules for Operators 153 | **[#fff] Wildcard Arguments in Types 154 | **[#38c0c4] Imports 155 | **[#fff] Changes in Type Checking 156 | **[#fff] Changes in Type Inference 157 | **[#fff] Changes in Implicit Resolution 158 | **[#fff] Implicit Conversions 159 | **[#38c0c4] Changes in Overload Resolution 160 | **[#38c0c4] Match Expressions 161 | **[#38c0c4] Vararg Splices 162 | **[#38c0c4] Pattern Bindings 163 | **[#fff] Option-less pattern matching 164 | **[#fff] Automatic Eta Expansion 165 | **[#fff] Changes in Compiler Plugins 166 | **[#fff] Lazy Vals initialization 167 | **[#38c0c4] Main Methods 168 | @endmindmap 169 | ``` 170 | 171 | ## Dropped Features 172 | 173 | https://dotty.epfl.ch/docs/Dropped%20Features/ 174 | 175 | ```plantuml 176 | @startmindmap 177 | *[#fff] Dropped Features 178 | **[#38c0c4] Dropped: Delayedinit 179 | **[#38c0c4] Dropped: Scala 2 Macros 180 | **[#fff] Dropped: Existential Types 181 | **[#fff] Dropped: General Type Projection 182 | **[#38c0c4] Dropped: Do-While 183 | **[#38c0c4] Dropped: Procedure Syntax 184 | **[#38c0c4] Dropped: Package Objects 185 | **[#38c0c4] Dropped: Early Initializers 186 | **[#38c0c4] Dropped: Class Shadowing 187 | **[#38c0c4] Dropped: Limit 22 188 | **[#38c0c4] Dropped: XML Literals 189 | **[#38c0c4] Dropped: Symbol Literals 190 | **[#38c0c4] Dropped: Auto-Application 191 | **[#38c0c4] Dropped: Weak Conformance 192 | **[#38c0c4] Deprecated: Nonlocal Returns 193 | **[#38c0c4] Dropped: private[this] and protected[this] 194 | **[#38c0c4] Dropped: wildcard initializer 195 | @endmindmap 196 | ``` 197 | -------------------------------------------------------------------------------- /docs/step01/04_Migration_guideを俯瞰してみる.md: -------------------------------------------------------------------------------- 1 | # Migration Guide を俯瞰してみる {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [Compatibility Reference](#compatibility-reference) 9 | - [Tour of the Migration Tools](#tour-of-the-migration-tools) 10 | - [Scala 3 Migration Mode](#scala-3-migration-mode) 11 | - [Migration Tutorial](#migration-tutorial) 12 | - [Scala 3 Syntax Rewriting](#scala-3-syntax-rewriting) 13 | - [Incompatibility Table](#incompatibility-table) 14 | - [Compiler Options](#compiler-options) 15 | - [Compiler Plugins](#compiler-plugins) 16 | 17 | 18 | 19 | ## 概要 20 | 21 | Scala 3 へのマイグレーション方法や Scala 2 との互換性・非互換性を確認するために、[Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) の内容も俯瞰してみましょう。こちらも情報量が多いので、見るものを選別します。 22 | 23 | まず、Migration Guide はこのようなカテゴリに分かれています。 24 | 25 | ```plantuml 26 | @startmindmap 27 | * Migration guide 28 | ** Compatibility Reference 29 | ** Tour of the Migration Tools 30 | ** Scala 3 Migration Mode 31 | ** Migration Tutorial 32 | ** Scala 3 Syntax Rewriting 33 | ** Incompatibility Table 34 | ** Compiler Options 35 | ** Compiler Plugins 36 | @endmindmap 37 | ``` 38 | 39 | この内、カテゴリごとにこの Part で見ておきたいページに色付けしてみましょう。 40 | 41 | ## Compatibility Reference 42 | 43 | https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html 44 | 45 | ```plantuml 46 | @startmindmap 47 | *[#fff] Compatibility Reference 48 | **[#38c0c4] Source Level 49 | **[#38c0c4] Classpath Level 50 | **[#38c0c4] Runtime 51 | **[#38c0c4] Metaprogramming 52 | @endmindmap 53 | ``` 54 | 55 | ## Tour of the Migration Tools 56 | 57 | https://docs.scala-lang.org/scala3/guides/migration/tooling-tour.html 58 | 59 | 60 | ```plantuml 61 | @startmindmap 62 | *[#38c0c4] Tour of the Migration Tools 63 | @endmindmap 64 | ``` 65 | 66 | ## Scala 3 Migration Mode 67 | 68 | https://docs.scala-lang.org/scala3/guides/migration/tooling-migration-mode.html 69 | 70 | 71 | ```plantuml 72 | @startmindmap 73 | *[#38c0c4] Scala 3 Migration Mode 74 | @endmindmap 75 | ``` 76 | 77 | ## Migration Tutorial 78 | 79 | https://docs.scala-lang.org/scala3/guides/migration/tutorial-intro.html 80 | 81 | 82 | ```plantuml 83 | @startmindmap 84 | *[#fff] Migration Tutorial 85 | **[#38c0c4] Project Prerequisites 86 | **[#38c0c4] Porting an sbt Project 87 | **[#fff] Cross-Building a Macro Library 88 | **[#fff] Mixing Scala 2.13 and Scala 3 Macros 89 | @endmindmap 90 | ``` 91 | 92 | ## Scala 3 Syntax Rewriting 93 | 94 | https://docs.scala-lang.org/scala3/guides/migration/tooling-syntax-rewriting.html 95 | 96 | ```plantuml 97 | @startmindmap 98 | *[#38c0c4] Scala 3 Syntax Rewriting 99 | @endmindmap 100 | ``` 101 | 102 | ## Incompatibility Table 103 | 104 | https://docs.scala-lang.org/scala3/guides/migration/incompatibility-table.html 105 | 106 | ```plantuml 107 | @startmindmap 108 | *[#fff] Incompatibility Table 109 | **[#38c0c4] Incompatibility Table 110 | **[#38c0c4] Syntactic Changes 111 | **[#38c0c4] Dropped Features 112 | **[#fff] Contextual Abstractions 113 | **[#38c0c4] Other Changed Features 114 | **[#fff] Type Checker 115 | **[#fff] Type Inference 116 | @endmindmap 117 | ``` 118 | 119 | ## Compiler Options 120 | 121 | https://docs.scala-lang.org/scala3/guides/migration/options-intro.html 122 | 123 | 124 | ```plantuml 125 | @startmindmap 126 | *[#fff] Compiler Options 127 | **[#38c0c4] Compiler Options Lookup Table 128 | **[#38c0c4] New Compiler Options 129 | **[#38c0c4] Scaladoc settings compatibility between Scala2 and Scala3 130 | @endmindmap 131 | ``` 132 | 133 | ## Compiler Plugins 134 | 135 | https://docs.scala-lang.org/scala3/guides/migration/plugin-intro.html 136 | 137 | ```plantuml 138 | @startmindmap 139 | *[#fff] Compiler Plugins 140 | **[#fff] External Resources 141 | **[#fff] Kind Projector Migration 142 | @endmindmap 143 | ``` 144 | -------------------------------------------------------------------------------- /docs/step01/05_インデントベースと新しい制御構造.md: -------------------------------------------------------------------------------- 1 | # インデントベースと新しい制御構造 {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [ドキュメント参照先](#ドキュメント参照先) 10 | - [Optional Braces](#optional-braces) 11 | - [New Control Syntax](#new-control-syntax) 12 | - [Scala 3 Syntax Rewriting](#scala-3-syntax-rewriting) 13 | 14 | 15 | 16 | ## ドキュメント参照先 17 | 18 | ここからは基本的にドキュメントの参照先を記載するので、 19 | そのドキュメントと、このリポジトリの対応するサンプルコードを併せて見ながら進めたいと思います。 20 | 21 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) からはこちらを 22 | - [Optional Braces](https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html) 23 | - [New Control Syntax](https://dotty.epfl.ch/docs/reference/other-new-features/control-syntax.html) 24 | 25 | [Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) からはこちらを参照します 26 | - [Scala 3 Syntax Rewriting](https://docs.scala-lang.org/scala3/guides/migration/tooling-syntax-rewriting.html) 27 | 28 | ## Optional Braces 29 | 30 | https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html 31 | 32 | - 中括弧 `{...}` はオプションになった 33 | - 不正なインデントはコンパイラが warning を出力する 34 | - インデントブロックのすべての式や文が正確に並ぶことを要求しているわけではない 35 | - コンパイラは内部的に、特定の予約語や改行時に `` または `` トークンを自動的に挿入。文法的には、`` トークンと `` トークンのペアは、中括弧 `{` と `}` のペアと同じ効果を持つ 36 | - class、trait、object などに `:` をつけると次の行からボディを定義できる 37 | - 同じソースファイル内でスペースとタブを混在させるのは避けるべき 38 | - `match` や `catch` の 中括弧も省略可能 39 | - インデントの範囲が「一目見て」すぐには分からないコードには、`end` マーカーを付ける 40 | - [ガイドライン](https://dotty.epfl.ch/docs/reference/other-new-features/indentation.html#the-end-marker) 参照 41 | - コンパイラオプションの `rewrite` を使用することでコンパイラが自動的に書き換えてくれる 42 | - `-indent`、`-no-indent` を組み合わせる 43 | - メソッドの引数は例外で、中括弧を省略することはできない 44 | ```scala 45 | xs.map { x => 46 | ... 47 | } 48 | ``` 49 | - ただし、`language.experimental.fewerBraces` をインポートすることで、この制限を解除することができる 50 | - 例えば [この例](https://gist.github.com/shinharad/3bab6ba5d939f71266bb32ed8f7e9600) のように、テストコードをインデートベースで書くこともできてしまう 51 | - experimental な機能なので、積極的な使用は避けたい 52 | 53 | 54 | :memo: [OptionalBraces.scala](/step01/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OptionalBraces.scala) 55 | 56 | ## New Control Syntax 57 | 58 | https://dotty.epfl.ch/docs/reference/other-new-features/control-syntax.html 59 | 60 | - "quiet" シンタックスが追加された 61 | - `if` 式は、`then` を書く場合は条件に括弧を付けなくても良い 62 | - `while` loop の条件は、`do` の後に続く場合は括弧を付けずに書くことができる 63 | - `for` 式の列挙子は、`yield` や `do` の後に続く場合は、括弧や中括弧を入れずに書くことができる 64 | - `for` 式の `do` は for-loop を表現している 65 | - `catch` と同一行に 1つの `case` を書ける 66 | - `catch` の `case` が複数の場合は、中括弧に入れるか、インデントブロックにしなければならない 67 | - コンパイラオプションの `rewrite` を使用することでコンパイラが自動的に書き換えてくれる 68 | - `-new-syntax`、`-old-syntax` を組み合わせる 69 | 70 | :memo: [NewControlSyntax.scala](/step01/src/main/scala/com/github/shinharad/gettingStartedWithScala3/NewControlSyntax.scala) 71 | 72 | ## Scala 3 Syntax Rewriting 73 | 74 | https://docs.scala-lang.org/scala3/guides/migration/tooling-syntax-rewriting.html 75 | 76 | - Syntax 77 | - インデントベース : `significant indentation based syntax (SIB syntax)` 78 | - Scala 2 の書き方 : `Classic syntax` 79 | - Control structures 80 | - `if` や `for` などの新しい書き方 : `New control structures` 81 | - Scala 2 の書き方 : `Classic control structures` 82 | - 1つのコードベースでこれらが混在するのは良くない 83 | - Scala 3 コンパイラには、コンパイラオプションの `-rewrite` と以下を組み合わせることで、ソースコードを自動で書き換えてくれる機能がある 84 | - `-indent` : Significant Indentation 85 | - `-noindent` : Classical Braces 86 | - `-new-syntax` : New Control Structure 87 | - `-old-syntax` : Old Control Structure 88 | 89 | :memo: [Scala3SyntaxRewriting.scala](/step01/src/main/scala/com/github/shinharad/gettingStartedWithScala3/NewControlSyntax.scala) / [buils.sbt](/build.sbt) 90 | -------------------------------------------------------------------------------- /docs/step02/01_Scala3で追加された地味に嬉しい系の機能追加.md: -------------------------------------------------------------------------------- 1 | # Scala 3で追加された地味に嬉しい系の機能追加 {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [ドキュメント参照先](#ドキュメント参照先) 11 | - [Main Methods](#main-methods) 12 | - [Match Expressions](#match-expressions) 13 | - [Trait Parameters](#trait-parameters) 14 | - [Universal Apply Methods](#universal-apply-methods) 15 | - [Parameter Untupling](#parameter-untupling) 16 | - [Vararg Splices](#vararg-splices) 17 | - [Rules for Operators](#rules-for-operators) 18 | - [The infix Modifier](#the-infix-modifier) 19 | - [The @targetName Annotation](#the-targetname-annotation) 20 | - [Syntax Change](#syntax-change) 21 | - [Extension Methods](#extension-methods) 22 | 23 | 24 | 25 | ## 概要 26 | 27 | Scala 3 で新しく追加された機能の中から「今まで冗長だった記述が短くなる!地味に嬉しい!」という機能を集めてみました。本リポジトリの対応するサンプルコードと併せて見ていきましょう。 28 | 29 | 30 | ## ドキュメント参照先 31 | 32 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) からこちらを参照します。 33 | 34 | - [Main Methods](https://dotty.epfl.ch/docs/reference/changed-features/main-functions.html) 35 | - [Match Expressions](https://dotty.epfl.ch/docs/reference/changed-features/match-syntax.html) 36 | - [Trait Parameters](https://dotty.epfl.ch/docs/reference/other-new-features/trait-parameters.html) 37 | - [Universal Apply Methods](https://dotty.epfl.ch/docs/reference/other-new-features/creator-applications.html) 38 | - [Parameter Untupling](https://dotty.epfl.ch/docs/reference/other-new-features/parameter-untupling.html) 39 | - [Vararg Splices](https://dotty.epfl.ch/docs/reference/changed-features/vararg-splices.html) 40 | - [Rules for Operators](https://dotty.epfl.ch/docs/reference/changed-features/operators.html) 41 | - [Extension Methods](https://dotty.epfl.ch/docs/reference/contextual/extension-methods.html) 42 | 43 | ## Main Methods 44 | 45 | https://dotty.epfl.ch/docs/reference/changed-features/main-functions.html 46 | 47 | - メソッドに `@main` アノテーションを付けると、そのメソッドは実行可能なプログラムとなる 48 | - `@main` 付きのメソッドは、 49 | - トップレベルでも `object` の中でも書くことができる 50 | - コンパイラは内部的に、`def main(args: Array[String]): Unit` を持つクラスに変換する 51 | - 任意の数のパラメータを持つことができる 52 | - Scala 2 みたいに `extends App` で書くこともできるが、 Scala 3 的には `@main` を推奨している 53 | 54 | :memo: [MainMethods.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/MainMethods.scala) 55 | 56 | ## Match Expressions 57 | 58 | https://dotty.epfl.ch/docs/reference/changed-features/match-syntax.html 59 | 60 | `match` 式の構文的な優先順位が変更された。それにより、 61 | 1. `match` 式を連鎖できるようになった 62 | 2. `match` 式をピリオドの後に書くことができるようになった 63 | 3. `match` 式の被検査体(scrutinee)は、`InfixExpr` でなければならない。そのため、 `x: T match { ... }` はサポートされなくなったので、`(x: T) match { ... }` と書く 64 | 65 | :memo: [MatchExpressions.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/MatchExpressions.scala) 66 | 67 | ## Trait Parameters 68 | 69 | https://dotty.epfl.ch/docs/reference/other-new-features/trait-parameters.html 70 | 71 | - `class` のパラメータと同じように `trait` にもパラメータを持たせられるようになった 72 | - `trait` への引数は、`trait` が初期化される直前に評価される 73 | - 同一 `trait` の `extends` など、曖昧な定義をするとコンパイルエラー 74 | 75 | :memo: [TraitParameters.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/TraitParameters.scala) / [TraitParameters2.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/TraitParameters2.scala) 76 | 77 | ## Universal Apply Methods 78 | 79 | https://dotty.epfl.ch/docs/reference/other-new-features/creator-applications.html 80 | 81 | - 元々 `case class` はコンパニオンオブジェクトに `apply` メソッドが暗黙的に追加されるので、インスタンス生成時に `new` を書く必要はなかったが、Scala 3 ではこのスキームをすべての具象クラスに一般化した 82 | - `class` を定義すると、コンパニオンオブジェクトに対して、コンストラクタに対応した `apply` メソッドが自動的に追加される(constructor proxy と呼ばれている) 83 | - その結果、`class` のインスタンス生成時でも `new` が不要になった 84 | - Javaのクラスも `new` を書かずにインスタンス生成できるようになった 85 | - `new` を省略することで実装の詳細が隠され、コードがより読みやすくなる 86 | 87 | :memo: [UniversalApplyMethods.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UniversalApplyMethods.scala) 88 | 89 | ## Parameter Untupling 90 | 91 | https://dotty.epfl.ch/docs/reference/other-new-features/parameter-untupling.html 92 | 93 | - タプルのリストを `map` する際、これまでは、パターンパッチで分解しなければならなかったが、それが不要になった 94 | ```scala 95 | xs map { 96 | (x, y) => x + y 97 | } 98 | 99 | // Scala 2 まではこう書いていた 100 | xs map { 101 | case (x, y) => x + y 102 | } 103 | ``` 104 | 105 | :memo: [ParameterUntupling.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ParameterUntupling.scala) 106 | 107 | ## Vararg Splices 108 | 109 | https://dotty.epfl.ch/docs/reference/changed-features/vararg-splices.html 110 | 111 | - vararg splices の構文が変更になった 112 | - 今までは、`: _*` と書いていたけど、 `*` のみで良くなった 113 | 114 | :memo: [VarargSplices.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/VarargSplices.scala) 115 | 116 | ## Rules for Operators 117 | 118 | https://dotty.epfl.ch/docs/reference/changed-features/operators.html 119 | 120 | ### The infix Modifier 121 | 122 | - メソッドや型がどのように適用されるかについて、コードベース間の一貫性を実現するためのもの 123 | - メソッド作成者が、そのメソッドを `infix` 演算子として適用するのか、通常の方法で適用するのかを決定できる 124 | 125 | ### The @targetName Annotation 126 | 127 | シンボリック演算子に対して、 `@targetName` で任意の名前を付けられる。これにより、 128 | - 他の言語から target name を使って呼び出すことができる。シンボリック名の低レベルエンコーディングを覚える必要がなくなる 129 | - スタックトレースやその他の実行時診断の読みやすさが向上する 130 | - ドキュメンテーションツールとしての役割を果たせる 131 | 132 | ### Syntax Change 133 | 134 | - 複数行の場合に `infix` 演算子を行末ではなく行頭で書けるようになった 135 | ```scala 136 | val str = "hello" 137 | ++ " world" 138 | ++ "!" 139 | ``` 140 | - Scala 2 のセミコロンによる推論では、`++ " world"` や `++ "!"` は、別々のステートメントとして扱われていた 141 | ```scala 142 | // Scala 2 ではコンパイルエラー 143 | val str = "hello" 144 | ++ " world" 145 | ++ "!" 146 | ``` 147 | - この構文を機能させるために、先頭に `infix` 演算子がある場合は、その前のセミコロンを推論しないようにルールが変わった 148 | - 行頭に `infix` 演算子を書く場合、その後の式の前にスペースを少なくとも1つ入れる必要がある 149 | ```scala 150 | // これは一つのステートメントとして扱われるけど、 151 | freezing 152 | | boiling 153 | 154 | // これは別々のステートメントとして扱われる 155 | freezing 156 | !boiling 157 | ``` 158 | 159 | 160 | :memo: [RulesForOperators.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/RulesForOperators.scala) 161 | 162 | ## Extension Methods 163 | 164 | https://dotty.epfl.ch/docs/reference/contextual/extension-methods.html 165 | 166 | - これまで `implicit class` で実現していた拡張メソッドに専用の構文が追加された 167 | - extension methods は、通常のメソッドと同じように呼び出せる 168 | - extension methods は、シンボリック演算子(`<` や `+:` など)も定義できる 169 | - extension methods を参照可能にするためには4つの方法がある 170 | 1. スコープ内で定義、またはそれを継承、インポートしている 171 | 2. 参照先のスコープ内で型クラスのインスタンスが定義されている 172 | 3. extension method `m` を `r.m` で参照する場合、`r` の暗黙のスコープで定義されている 173 | 4. extension method `m` を `r.m` で参照する場合、型クラスのインスタンスが `r` の暗黙のスコープで定義されている 174 | 175 | :memo: [ExtensionMethods.scala](/step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ExtensionMethods.scala) 176 | -------------------------------------------------------------------------------- /docs/step03/01_EnumsとADTs.md: -------------------------------------------------------------------------------- 1 | # EnumsとADTs {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [ドキュメント参照先](#ドキュメント参照先) 11 | - [Enumerations](#enumerations) 12 | - [Algebraic Data Types](#algebraic-data-types) 13 | 14 | 15 | 16 | ## 概要 17 | 18 | Scala 3 では、Enumerations や Algebraic Data Types (ADTs, 代数的データ型) を定義するための専用のシンタックスが追加されました。Step3 はまずそこから見ていきましょう。(本リポジトリの対応するサンプルコードも併せて参照してください) 19 | 20 | ## ドキュメント参照先 21 | 22 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) からこちらを参照します。 23 | 24 | - [Enumerations](https://dotty.epfl.ch/docs/reference/enums/enums.html) 25 | - [Algebraic Data Types](https://dotty.epfl.ch/docs/reference/enums/adts.html) 26 | 27 | ## Enumerations 28 | 29 | https://dotty.epfl.ch/docs/reference/enums/enums.html 30 | 31 | - Scala 3 では、`enum` で Enumerations(列挙型)を定義できるようになった 32 | ```scala 33 | enum Color: 34 | case Red, Green, Blue 35 | ``` 36 | - 列挙した型は、`enum` のコンパニオンオブジェクトとして定義される。つまり、このように使える 37 | ```scala 38 | Color.Red 39 | ``` 40 | - 列挙した型は、一意の `Int` の値に対応していて、`ordinal` で取得ができる 41 | - Enums のコンパニオンオブジェクトには、ユーティリティメソッドとして、`valueOf`、`values`、`fromOrdinal` が用意されている 42 | - Enums には独自のメソッドを定義できる 43 | - Scala で定義した Enums は、 `java.lang.Enum` を継承することで、Java の Enums として使用することができる 44 | 45 | :memo: [Enumerations.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Enumerations.scala) 46 | 47 | ## Algebraic Data Types 48 | 49 | https://dotty.epfl.ch/docs/reference/enums/adts.html 50 | 51 | - `enum` で、Algebraic Data Types (ADTs、代数的データ型)を表現できる 52 | - `enum` の型パラメータの変位指定が covariant(共変)の場合は、コンパイラの型推論により列挙した型の `extends` を省略できるが、変位指定が covariant 以外の場合は明示的に書く必要がある 53 | - Enums と同様、ADTs は独自のメソッドを定義することができる 54 | - ADTs は、Enums と混在させることができる 55 | - ADTs の `case` の型パラメータは、親である `enum` の変位指定を引き継ぐ 56 | 57 | :memo: [AlgebraicDataTypes.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/AlgebraicDataTypes.scala) -------------------------------------------------------------------------------- /docs/step03/02_IntersectionTypesとUnionTypes.md: -------------------------------------------------------------------------------- 1 | # Intersection TypesとUnion Types {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [ドキュメント参照先](#ドキュメント参照先) 11 | - [Intersection Types](#intersection-types) 12 | - [Union Types](#union-types) 13 | 14 | 15 | 16 | ## 概要 17 | 18 | 次に、Scala 3 で新しく追加された型、Intersection Types (交差型) と Union Types (合併型) を見てみましょう。(本リポジトリの対応するサンプルコードも併せて参照してください) 19 | 20 | ## ドキュメント参照先 21 | 22 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) からこちらを参照します。 23 | 24 | - [Intersection Types](https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html) 25 | - [Union Types](https://dotty.epfl.ch/docs/reference/new-types/union-types.html) 26 | 27 | また、[Documentation for Scala 3](https://docs.scala-lang.org/scala3) の [Scala 3 Book](https://docs.scala-lang.org/scala3/book/introduction.html) より、こちらも補足的に参照します。 28 | 29 | - [Union Types](https://docs.scala-lang.org/scala3/book/types-union.html) 30 | 31 | 32 | ## Intersection Types 33 | 34 | https://dotty.epfl.ch/docs/reference/new-types/intersection-types.html 35 | 36 | - `&` 演算子で、Intersection Types (交差型)を作成できるようになった 37 | - `A & B` は、`A` 型でもあるし `B` 型でもあるという意味になる 38 | - `A & B` のメンバは、`A` 型のすべてのメンバと `B` 型のすべてのメンバを持っている 39 | - `A` と `B` に同一のメンバが存在する場合、`A & B` のメンバの型は、それぞれのメンバの型の Intersection Types になる 40 | - `&` 演算子は可換的で、`A & B` と `B & A` は等価 41 | 42 | :memo: [IntersectionTypes.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/IntersectionTypes.scala) 43 | 44 | ## Union Types 45 | 46 | https://dotty.epfl.ch/docs/reference/new-types/union-types.html 47 | 48 | - `|` 演算子で、Union Types(合併型)を作成できるようになった 49 | - `A | B` は、`A` 型か `B` 型のどちらかという意味になる 50 | - `|` 演算子は可換的で、`A | B` と `B | A` は等価 51 | 52 | :memo: [UnionTypes.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UnionTypes.scala) / [UnionTypes2.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UnionTypes2.scala) / [UnionTypes3.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UnionTypes3.scala) -------------------------------------------------------------------------------- /docs/step03/03_OpaqueTypeAliases.md: -------------------------------------------------------------------------------- 1 | # Opaque Type Aliases {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [ドキュメント参照先](#ドキュメント参照先) 11 | - [Opaque Type Aliases](#opaque-type-aliases-1) 12 | 13 | 14 | 15 | ## 概要 16 | 17 | 次に、Opaque Type Aliases を見てみます。この Opaque Type Aliases は、個人的にかなり嬉しい機能追加だと思ってます! 18 | 19 | ## ドキュメント参照先 20 | 21 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) からこちらを参照します。 22 | 23 | - [Opaque Type Aliases](https://dotty.epfl.ch/docs/reference/other-new-features/opaques.html) 24 | 25 | ## Opaque Type Aliases 26 | 27 | https://dotty.epfl.ch/docs/reference/other-new-features/opaques.html 28 | 29 | - Opaque types aliases は、実体となる型を限定されたスコープの中でのみメンバ型エイリアスとして参照できるようにして、スコープの外からは opaque(不透明)とすることで、型の抽象化を行うもの 30 | - スコープの外からは公開したメソッドからのみアクセスが可能 31 | - コンパイルすると実体の型に変換されるのでオーバーヘッドを気にしなくても良い 32 | - Scala 2 では、このような実装を [Value classes](https://docs.scala-lang.org/ja/overviews/core/value-classes.html) で実現していたが、[制約が多かった](https://docs.scala-lang.org/ja/overviews/core/value-classes.html) 33 | - Value classes は削除されることはなくて、[project Valhalla](https://openjdk.java.net/projects/valhalla/) で計画されているような JVM でネイティブにサポートされるようになれば、状況が変わるかもしれない 34 | 35 | :memo: [OpaqueTypeAliases1.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpaqueTypeAliases1.scala) / [OpaqueTypeAliases2.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpaqueTypeAliases2.scala) / [OpaqueTypeAliases3.scala](/step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpaqueTypeAliases3.scala) -------------------------------------------------------------------------------- /docs/step04/02_TourOfTheMigrationTools.md: -------------------------------------------------------------------------------- 1 | # Tour of the Migration Tools {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [The Scala Compilers](#the-scala-compilers) 10 | - [The Scala 2.13 compiler](#the-scala-213-compiler) 11 | - [The Scala 3 compiler](#the-scala-3-compiler) 12 | - [Build tools](#build-tools) 13 | - [sbt](#sbt) 14 | - [Mill](#mill) 15 | - [Maven](#maven) 16 | - [Code editors and IDEs](#code-editors-and-ides) 17 | - [Metals](#metals) 18 | - [IntelliJ IDEA](#intellij-idea) 19 | - [Formatting Tools](#formatting-tools) 20 | - [Scalafmt](#scalafmt) 21 | - [Migration Tools](#migration-tools) 22 | - [Scalafix](#scalafix) 23 | - [Scala 3 migrate Plugin](#scala-3-migrate-plugin) 24 | - [Scaladex](#scaladex) 25 | 26 | 27 | 28 | 29 | ## 概要 30 | 31 | Scala 2.13 から Scala 3 への移行をサポートするいくつかのツールを確認しておきましょう。 32 | 33 | ## ドキュメント参照先 34 | 35 | [Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) からこちらを参照します。 36 | 37 | - [Tour of the Migration Tools](https://docs.scala-lang.org/scala3/guides/migration/tooling-tour.html) 38 | 39 | 40 | ## The Scala Compilers 41 | 42 | ### The Scala 2.13 compiler 43 | 44 | - Scala 2.13 で、コンパイラオプション `-Xsource:3` を有効にすると、いくつかの Scala 3 のシンタックスと動作を有効にできる 45 | - ほとんどの非推奨のシンタックスはエラーを出力する 46 | - 廃止されたシンタックスの多くはエラーを出力する 47 | - infix 演算子は、複数行からなる式の途中で行を開始できる 48 | - 暗黙的な検索とオーバーロードの解決は、特別なチェックのときにScala 3 の反変性(contravariance)のハンドリングに従う 49 | - `-Xsource:3` は、早期の移行を促すためのもの 50 | 51 | ### The Scala 3 compiler 52 | 53 | - Scala 3 で、コンパイラオプション `-source:3.0-migration` を有効にすると、コンパイラは Scala 2.13 のシンタックスの一部を受け入れ、変更が必要な場合は警告メッセージを出力する 54 | - 更に `-rewrite` と組み合わせることで、コードを自動的に書き換えることができる 55 | - 詳細は、この後の Scala 3 Migration Mode で確認する 56 | 57 | ## Build tools 58 | 59 | ### sbt 60 | 61 | - sbt 1.5 以降は Scala 3 をサポートしている 62 | - sbt 1.4 で Scala 3 をサポートするには、`sbt-dotty` プラグインが必要だった 63 | - 一般的な task や setting、多くのプラグインは同じように動作する 64 | - 移行を助けるために、sbt 1.5 は新しい Scala 3 のクロスバージョンを導入している 65 | 66 | ```scala 67 | // Scala 3 で Scala 2.13 のライブラリを使用する 68 | libraryDependency += ("org.foo" %% "foo" % "1.0.0").cross(CrossVersion.for3Use2_13) 69 | 70 | // Scala 2.13 で Scala 3 のライブラリを使用する 71 | libraryDependency += ("org.bar" %% "bar" % "1.0.0").cross(CrossVersion.for2_13Use3) 72 | ``` 73 | 74 | ### Mill 75 | 76 | - Mill 0.9.x は Scala 3 をサポートしている 77 | 78 | ### Maven 79 | 80 | - [scala-maven-plugin](https://github.com/davidB/scala-maven-plugin) は、間もなく Scala 3 をサポートする予定 81 | 82 | 83 | ## Code editors and IDEs 84 | 85 | ### Metals 86 | 87 | - Metals は、VS Code、Vim、Emacs、Sublime Text、Eclipse で動作する Scala language server 88 | - Metals は、Scala 3 の非常に多くの機能をサポートしている 89 | - 新しいシンタックスの変更や新機能はこれから対応される予定 90 | 91 | ### IntelliJ IDEA 92 | 93 | - IntelliJ の Scala plugin は、Scala 3 を暫定的にサポートしていて、本格的なサポートは、JetBrains 社のチームが取り組んでいる 94 | 95 | ## Formatting Tools 96 | 97 | ### Scalafmt 98 | 99 | - [Scalafmt](https://scalameta.org/scalafmt/) は、v3.0.0-RCx は、Scala 2.13 と Scala 3 の両方をサポートしている 100 | - Scala 3 のフォーマットを有効にするには、`.scalafmt.conf` ファイルで `runner.dialect = scala3` を設定する 101 | - 選択的に有効にしたい場合は、`fileOverride` の設定を行う 102 | ``` 103 | //.scalafmt.conf 104 | fileOverride { 105 | "glob:**/scala-3*/**" { 106 | runner.dialect = scala3 107 | } 108 | } 109 | ``` 110 | 111 | ## Migration Tools 112 | 113 | ### Scalafix 114 | 115 | https://scalacenter.github.io/scalafix/ 116 | 117 | - Scalafix は、Scala のリファクタリングツール 118 | - 現時点では Scala 2.13 でしか動作しないが、Scala 3 へ移行する前にソースコードを準備するのに役立つ 119 | - 非互換性は、対応する Scalafix のルールで解決できる 120 | - ルールは、sbt-scalafix プラグインを使って適用することもできるし、後述するオールインワンの sbt-scala3-migrate を使うこともできる 121 | 122 | ### Scala 3 migrate Plugin 123 | 124 | https://github.com/scalacenter/scala3-migrate 125 | 126 | Scala 3 migrate は、Scala 3 への移行を容易にするための sbt plugin で、次のようなインクリメンタルなアプローチを提案している。 127 | 128 | - ライブラリの依存関係の移行 129 | - すべてのライブラリの依存関係に対して、Scala 3.0 で利用可能なバージョンであるかをチェックする 130 | - コンパイラオプション(scalacOptions)の移行 131 | - Scala 2 のコンパイラオプションは、Scala 3 ではそのまま使えるものもあれば、削除されたり、リネームされたりしている 132 | - このステップでは、コンパイラオプションを適応させるのに役立つ 133 | - シンタックスの移行 134 | - このステップでは、Scalafix と既存のルールに依存して、非推奨のシンタックスを修正する 135 | - コードの移行 136 | - Scala 3 には新しい型推論アルゴリズムが導入され、Scala 2 コンパイラが推論した型とは異なる型を推論する可能性がある 137 | - 最後のステップでは、プロジェクトがランタイムの動作を変更することなく Scala 3 でコンパイルできるように、最小限の型のセットを明示する 138 | 139 | ### Scaladex 140 | 141 | - [Scaladex](https://index.scala-lang.org/search?q=*&scalaVersions=scala3) では、Scala 3.0 をサポートするオープンソースライブラリを探すことができる 142 | -------------------------------------------------------------------------------- /docs/step04/03_Scala3MigrationMode.md: -------------------------------------------------------------------------------- 1 | # Scala 3 Migration Mode {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Migration mode](#migration-mode) 10 | - [Automatic rewrites](#automatic-rewrites) 11 | - [Error explanations](#error-explanations) 12 | 13 | 14 | 15 | ## 概要 16 | 17 | Scala 3 コンパイラは、Scala 2.13 から Scala 3 への移行を容易にするたの Migration Mode を提供しています。ここでは、この Migration Mode について確認しましょう。 18 | 19 | ## ドキュメント参照先 20 | 21 | [Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) からこちらを参照します。 22 | 23 | - [Scala 3 Migration Mode](https://docs.scala-lang.org/scala3/guides/migration/tooling-migration-mode.html) 24 | 25 | 26 | ## Migration mode 27 | 28 | - Scala 3 でコンパイラオプションの `source:3.0-migration` を指定すると、コンパイラは Scala 3 で廃止された機能のほとんどに寛容になり、エラーの代わりに警告を表示するようになる 29 | - それぞれの警告は、Scala 3 で廃止されたコードをクロスコンパイル対応のコードに安全に書き換える能力があることを示している 30 | - これを、Scala 3 migration compilation と呼んでいる 31 | 32 | ## Automatic rewrites 33 | 34 | - Migration Mode でコードをコンパイルすると、ほとんどすべての警告はコンパイラ自身によって自動的に解決してくれる 35 | - そのためには、`-source:3.0-migration` の他に `-rewrite` オプションを指定して再度コンパイルする必要がある 36 | - `-rewrite` は、本リポジトリの Step1 で確認した通り、コードを自動的に書き換えてくれるコンパイラオプション 37 | - コンパイラによる自動書き換えの注意点 38 | - コードを書き換えてしまうので、書き換え前のコードをコミットしておき、コンパイラが適用した差分が分かるようにしておく 39 | - コンパイルエラーの場合は書き換えが適用されない 40 | - どのルールで書き換えを行うかは選択できないので、すべてのルールに対して実行される 41 | - どのような警告に対して自動的に書き換えをしてくれるかは、 [Incompatibility Table](https://docs.scala-lang.org/scala3/guides/migration/incompatibility-table.html) を参照する 42 | 43 | ## Error explanations 44 | 45 | - `-source:3.0-migration` モードでは、すべての機能を処理できるわけではなくて、場合によっては Scala 2.13 と Scala 3.0 の非互換性のためにエラーが残ることもある 46 | - `-explain` または `-explain-types` オプションを指定すると、残りのエラーについての詳細を表示する 47 | - `-explain` と `-explain-types` オプションは、Migration Mode に限定されるものではなくて、Scala 3 の学習やコーディングを支援してくれるコンパイラオプション 48 | -------------------------------------------------------------------------------- /docs/step04/04_Prerequisites.md: -------------------------------------------------------------------------------- 1 | # Prerequisites {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Prerequisites](#prerequisites-1) 10 | - [Macro dependencies](#macro-dependencies) 11 | - [Compiler plugins](#compiler-plugins) 12 | - [SemanticDB](#semanticdb) 13 | - [Scala.js](#scalajs) 14 | - [Scala Native](#scala-native) 15 | - [Kind Projector](#kind-projector) 16 | - [Run-time reflection](#run-time-reflection) 17 | 18 | 19 | 20 | ## 概要 21 | 22 | 次に、Scala 2.13 のプロジェクトを Scala 3 へ移行する前に満たさなければならない前提条件を確認しておきましょう。 23 | 24 | ## ドキュメント参照先 25 | 26 | [Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) の Migration Tutorial からこちらを参照します。 27 | 28 | - [Prerequisites](https://docs.scala-lang.org/scala3/guides/migration/tutorial-prerequisites.html) 29 | 30 | 31 | ## Prerequisites 32 | 33 | https://docs.scala-lang.org/scala3/guides/migration/tutorial-prerequisites.html 34 | 35 | Scala 2.13 と Scala 3 の間には相互運用性があるので、Scala 3 への移行は容易です。しかし、Scala 2.13 のプロジェクトを Scala 3 へ移行する前に満たさなければならない前提条件がいくつかあります。 36 | 37 | - Scala 3 をまだサポートしていないマクロライブラリに依存してはいけない 38 | - マクロライブラリとは、マクロを使用したメソッドを公開している Scala ライブラリのことを指す 39 | - Scala 3 で同等のものがないコンパイラプラグインを使用してはいけない 40 | - `scala-reflect` に依存してはいけない 41 | 42 | ここでは、これらの前提条件をチェックする方法と、もしそれらが満たされていない場合の対処法について見ていきます。 43 | 44 | 45 | ### Macro dependencies 46 | 47 | - Scala 3 のコンパイラは、Scala 2.13 のマクロを展開することができない 48 | - そのため、自分のプロジェクトがまだ Scala 3 へ移行していないマクロライブラリに依存していないかどうかを確認する必要がある 49 | - 多くのマクロライブラリの移行状況は、[Scala Macro Libraries](https://scalacenter.github.io/scala-3-migration-guide/docs/macros/macro-libraries.html) で確認ができる 50 | - 自分のプロジェクトが依存しているマクロライブラリのそれぞれが、クロスビルドバージョン(Scala 2.13 と Scala 3 の両方で利用可能なバージョン)へアップデートする必要がある 51 | - 例えば、scalatest の場合 52 | ```scala 53 | // 移行前の scalatest 3.0.9 だった場合、Scala 3 をサポートしていないので、 54 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.7" 55 | 56 | // Scala 2.13 と Scala 3 を cross-published している scalatest 3.2.7 に上げる 57 | libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.7" 58 | ``` 59 | 60 | ### Compiler plugins 61 | 62 | - Scala 2 のコンパイラプラグインは、Scala 3 とは互換性がない 63 | - コンパイラプラグインは通常、`build.sbt` の中で、以下のいずれかの設定を行う 64 | ```scala 65 | // build.sbt 66 | libraryDependencies += 67 | compilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full) 68 | 69 | addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full) 70 | ``` 71 | - コンパイラプラグインの中には、sbt プラグインによって自動的に追加されるものがある 72 | - 設定されているすべてのコンパイラープラグインは、このようにして確認ができる 73 | ``` 74 | sbt:example> show example / Compile / scalacOptions 75 | [info] * -Xplugin:target/compiler_plugins/wartremover_2.13.5-2.4.12.jar 76 | [info] * -Xplugin:target/compiler_plugins/semanticdb-scalac_2.13.5-4.3.20.jar 77 | [info] * -Yrangepos 78 | [info] * -P:semanticdb:targetroot:/example/target/scala-2.13/meta 79 | ``` 80 | - 上の例では、wartremover と semanticdb という2つのコンパイラプラグインが使用されていることがわかる。それぞれのプラグインについて、代替案があるかどうか確認するか、無効にする必要がある 81 | 82 | 以降は比較的ポピュラーなコンパイラプラグインの代替案について確認します。 83 | 84 | #### SemanticDB 85 | 86 | Scala 3 コンパイラに SemanticDB のサポートが追加されたので、以下のコンパイラオプションで有効にすることができる。 87 | 88 | - `-Ysemanticdb` オプションは、semanticDB ファイルの生成を有効にする 89 | - `-semanticdb-target` オプションは、semanticDB ファイルの出力先ディレクトリを指定することができる 90 | 91 | なお、sbt は、`semanticdbEnabled := true` という設定だけで、SemanticDBを設定することができます。 92 | 93 | #### Scala.js 94 | 95 | - Scala.js のプロジェクトをコンパイルするには、`sbt-scalajs` プラグインの 1.5.0 以上を使用する 96 | ```scala 97 | // project/plugins.sbt 98 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") 99 | ``` 100 | 101 | #### Scala Native 102 | 103 | - Scala Native はまだ Scala 3 をサポートしていない 104 | - もしもプロジェクトが Scala Native にクロスビルドしている場合、Scala 3 に移行することはできるが、Scala Native プラットフォーム用にコンパイルすることはできない 105 | 106 | #### Kind Projector 107 | 108 | - kind-projector のシンタックスは、Scala 3 では `-Ykind-projector` オプションでサポートされている 109 | - ただし、多くの場合は Scala 3 の新機能で代替することができる 110 | - Type Lambdas 111 | - Polymorphic Functions 112 | - Kind Polymorphism 113 | - Kind Projector の移行については、[Kind Projector Migration](https://docs.scala-lang.org/scala3/guides/migration/plugin-kind-projector.html) で詳しく解説されている 114 | 115 | ### Run-time reflection 116 | 117 | - `scala-reflect` は、Scala 3 には存在しない Scala 2 コンパイラの内部構造を公開しているため、Scala 3 には移行できない 118 | - 自分のプロジェクトが、`scala-reflect` に依存していたり、`Manifest` クラスのインスタンスを使用している場合は、Scala 3 コンパイラではコンパイルできない 119 | - 代替手段としては、Java のリフレクションや Scala 3 のメタプログラミング機能を使って、対象のコードを再実装する必要がある 120 | - `scala-reflect` がクラスパスに一時的に追加されている場合は、恐らくそれをもたらす依存関係をアップグレードする必要がある 121 | 122 | -------------------------------------------------------------------------------- /docs/step04/05_PortingAnSbtProject.md: -------------------------------------------------------------------------------- 1 | # Porting an sbt Project {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Porting an sbt Project](#porting-an-sbt-project-1) 10 | - [1. Check the project prerequisites](#1-check-the-project-prerequisites) 11 | - [2. Choose a module](#2-choose-a-module) 12 | - [3. Set up cross-building](#3-set-up-cross-building) 13 | - [4. Prepare the dependencies](#4-prepare-the-dependencies) 14 | - [5. Configure the Scala 3 Compiler](#5-configure-the-scala-3-compiler) 15 | - [6. Solve the Incompatibilities](#6-solve-the-incompatibilities) 16 | - [7. Validate the migration](#7-validate-the-migration) 17 | - [8. Finalize the migration](#8-finalize-the-migration) 18 | 19 | 20 | 21 | ## 概要 22 | 23 | Porting an sbt Project では、sbt プロジェクトを Scala 3 へ移行していく様子をステップ・バイ・ステップで解説されています。ここでは、どのような点に気をつけながら Scala 3 に移行するのかを確認しておきましょう。 24 | 25 | ## ドキュメント参照先 26 | 27 | [Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) の Migration Tutorial からこちらを参照します。 28 | 29 | - [Porting an sbt Project](https://docs.scala-lang.org/scala3/guides/migration/tutorial-sbt.html) 30 | 31 | ## Porting an sbt Project 32 | 33 | https://docs.scala-lang.org/scala3/guides/migration/tutorial-sbt.html 34 | 35 | sbt プロジェクトを Scala 3 へ移行する際、移行元のプロジェクトは以下の前提条件を満たしている必要があります。 36 | 37 | - 最新の Scala 2.13.x 38 | - 最新の sbt 1.5.x 39 | 40 | それでは、プロジェクト全体を Scala 3 に移行するために必要な手順を見ていきましょう。 41 | 42 | ### 1. Check the project prerequisites 43 | 44 | - [Prerequisites](https://docs.scala-lang.org/scala3/guides/migration/tutorial-prerequisites.html) に記載されている前提条件を満たしていることを確認する 45 | 46 | ### 2. Choose a module 47 | 48 | - Scala 2.13 と Scala 3 はある程度互換性があるので、どのモジュールからでも始めることができる 49 | - 依存関係の少ないモジュールから始めるのがシンプル 50 | - マクロ定義やマクロアノテーションを内部で使用している場合は、まずそれらを移行する必要がある 51 | 52 | ### 3. Set up cross-building 53 | 54 | - コードベースの移行には、2つの大きな課題がある 55 | - ソースコードをコンパイルする 56 | - ランタイムの動作が変わらないことを確認する 57 | - このチュートリアルでは、コードを Scala 3 と Scala 2.13 でコンパイルする、クロスビルド戦略(cross-building strategy)を推奨している 58 | - その理由は、各修正の後に Scala 2.13 でテストを実行し、ランタイムの動作が変更されていないことを確認するため 59 | - これは、非互換性を修正する際に発生する可能性のあるバグを避けるために非常に重要 60 | - クロスビルドの設定はこのようにする 61 | ```scala 62 | // デフォルトは、3.0.1 63 | scalaVersion := "3.0.1" 64 | 65 | // 2.13.6 は、`++2.13.6` コマンドでロードすることができる 66 | // 3.0.1 は、`++3.0.1` コマンドでロードすることができる 67 | crossScalaVersions ++= Seq("2.13.6", "3.0.1") 68 | ``` 69 | - なお、sbt の `reload` コマンドは、常にデフォルトのバージョンをロードするので注意が必要 70 | 71 | ### 4. Prepare the dependencies 72 | 73 | - この段階でコンパイルを実行すると、いくつかの依存関係が見つからないというエラーを出力する可能性がある 74 | - これは、依存関係のバージョンが Scala3 用に公開していないからなので、新しいバージョンにアップグレードするか、sbt に Scala 2.13 バージョンのライブラリを使うように指定する必要がある 75 | - 依存関係のライブラリが Scala 3 のバージョンを公開しているかは、[Scaladex](https://index.scala-lang.org/) で探すことができる 76 | - ライブラリの Scala 3 バージョンが存在した場合 77 | - Scala 3 をサポートしたいずれかのバージョンを使用する 78 | - その際は、選択したバージョンが既存のコードに変化を与えないか確認する 79 | - ライブラリの Scala 3 バージョンが存在しなかった場合 80 | - Scala 2.13 バージョンを使用することができる 81 | ```scala 82 | ("com.lihaoyi" %% "os-lib" % "0.7.7").cross(CrossVersion.for3Use2_13) 83 | ``` 84 | - Scala.js の場合 85 | ```scala 86 | ("com.lihaoyi" %%% "os-lib" % "0.7.7").cross(CrossVersion.for3Use2_13) 87 | ``` 88 | - 未解決の依存関係をすべて修正したら、Scala 2.13 でテストがまだ通るかを確認する 89 | ``` 90 | sbt:example> ++2.13.5 91 | sbt:example> test 92 | ... 93 | [success] 94 | ``` 95 | 96 | ### 5. Configure the Scala 3 Compiler 97 | 98 | - Scala 3 のコンパイラオプションは Scala 2.13 のものとは異なる 99 | - いくつかのオプションはリネームされたり、まだ Scala 3 をサポートしていないものもある 100 | - 詳細は [Compiler Options Lookup Table](https://docs.scala-lang.org/scala3/guides/migration/options-lookup.html) を参照 101 | - 共通的なオプションのリスト、Scala 2.13 固有のオプションのリスト、Scala 3 固有のオプションのリストを作成するにはこのようにする 102 | ```scala 103 | scalacOptions ++= { 104 | Seq( 105 | "-encoding", 106 | "UTF-8", 107 | "-feature", 108 | "-language:implicitConversions", 109 | // disabled during the migration 110 | // "-Xfatal-warnings" 111 | ) ++ 112 | (CrossVersion.partialVersion(scalaVersion.value) match { 113 | case Some((3, _)) => Seq( 114 | "-unchecked", 115 | "-source:3.0-migration" 116 | ) 117 | case _ => Seq( 118 | "-deprecation", 119 | "-Xfatal-warnings", 120 | "-Wunused:imports,privates,locals", 121 | "-Wvalue-discard" 122 | ) 123 | }) 124 | } 125 | ``` 126 | - Scala 3 Migration Mode を有効にするために、`-source:3.0-migration` を追加する 127 | - また、Migration Mode と コードの自動書き換えを最大限に活用するために、`-Xfatal-warnings` は無効にしておく 128 | 129 | ### 6. Solve the Incompatibilities 130 | 131 | - ここでいよいよ Scala 3 でのコンパイルにチャレンジする 132 | ``` 133 | sbt:example> ++3.0.1 134 | [info] Setting Scala version to 3.0.1 on 1 project. 135 | ... 136 | sbt:example> example / compile 137 | ... 138 | sbt:example> example / Test / compile 139 | ``` 140 | - 補足) `example / compile` は、厳密には `example / Compile / compile` 141 | - この時コンパイラは、2つの異なるレベルの診断を行う 142 | - Migration Warning: 143 | - これらの警告は、コンパイラの `-rewrite` オプションで自動的にパッチを当てることができる 144 | - コンパイラが自動的に修正してくれるので、マイグレーションの警告は無視しても構わない 145 | - Error: 146 | - コードの一部が非互換性のエラーでコンパイルできなくなった 147 | - エラーの対処 148 | - 非互換性のエラーについては手動で対処する必要がある 149 | - 多くの既知の非互換性は、[Incompatibility Table](https://docs.scala-lang.org/scala3/guides/migration/incompatibility-table.html) にエラーの説明と解決策についての提案が記載されている 150 | - 可能であれば、コードのバイナリ互換性を最もよく維持できる修正方法を見つけるようにする。これは、移行するプロジェクトが公開されたライブラリである場合に特に重要 151 | - なお、マクロの非互換性は多くのコードを一から書き直さなければならない 152 | - [Metaprogramming](https://docs.scala-lang.org/scala3/guides/migration/compatibility-metaprogramming.html) 参照 153 | - 非互換性を修正した後は、Scala 2.13 のテストを実行する 154 | ``` 155 | sbt:example> ++2.13.6 156 | [info] Setting Scala version to 2.13.6 on 1 project. 157 | ... 158 | sbt:example> example / test 159 | ... 160 | [success] 161 | ``` 162 | - このとき、定期的に変更をコミットすることを検討する 163 | - すべてのエラーを修正すると、Scala 3 で正常にコンパイルできるようになる 164 | - マイグレーションの警告の対処 165 | - `source:3.0-migration -rewrite` オプションを付けてコンパイルすることで、自動的にパッチを当てることができる 166 | ``` 167 | sbt:example> ++3.0.1 168 | sbt:example> set example / scalacOptions += "-rewrite" 169 | sbt:example> example / compile 170 | ... 171 | [info] [patched file /example/src/main/scala/app/Main.scala] 172 | [warn] two warnings found 173 | [success] 174 | ``` 175 | - ここで、`-source:3.0-migration` オプションを削除し、必要に応じて `-Xfatal-warnings` オプションを再度追加する 176 | - このとき `reload` を忘れずに 177 | 178 | ### 7. Validate the migration 179 | 180 | - まれに、異なる暗黙の値が解決され、プログラムの実行時の動作が変わることがある。このようなバグを見逃さないためには、テストが唯一の保証となる 181 | - Scala 2.13 と Scala 3 の両方でテストが通ることを確認する 182 | ``` 183 | sbt:example> ++2.13.6 184 | sbt:example> example / test 185 | ... 186 | [success] 187 | sbt:example> ++3.0.1 188 | sbt:example> example / test 189 | ... 190 | [success] 191 | ``` 192 | - CI のパイプラインを設定している場合は、それを Scala 3 用にセットアップする 193 | 194 | ### 8. Finalize the migration 195 | 196 | - これで単一のモジュールの Scala 3 の移行作業は完了 197 | - プロジェクト全体が Scala 3 に完全に移行するまで、各モジュールに対して同じプロセスを繰り返す 198 | - そして、ライブラリをクロスパブリッシュ(cross-publish)するかどうかによって、Scala 2.13 のクロスビルドの設定をそのままにするかどうかを決める 199 | -------------------------------------------------------------------------------- /docs/step04/06_Scala3MigratePlugin.md: -------------------------------------------------------------------------------- 1 | # Scala 3 Migrate Plugin {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Scala 3 Migrate Plugin](#scala-3-migrate-plugin-1) 10 | - [Requirements](#requirements) 11 | - [Installation](#installation) 12 | - [Choose a module](#choose-a-module) 13 | - [Migrate library dependencies](#migrate-library-dependencies) 14 | - [Macro library](#macro-library) 15 | - [Compiler plugins](#compiler-plugins) 16 | - [Libraries that can be updated](#libraries-that-can-be-updated) 17 | - [Valid libraries](#valid-libraries) 18 | - [The new build after migrate-libs](#the-new-build-after-migrate-libs) 19 | - [Migrate scalacOptions](#migrate-scalacoptions) 20 | - [Non-existing scalacOptions in Scala 3](#non-existing-scalacoptions-in-scala-3) 21 | - [Renamed scalacOptions](#renamed-scalacoptions) 22 | - [Plugins specific scalacOptions](#plugins-specific-scalacoptions) 23 | - [The new build after migrate-scalacOptions](#the-new-build-after-migrate-scalacoptions) 24 | - [Migrate the syntax](#migrate-the-syntax) 25 | - [Migrate the code: last command](#migrate-the-code-last-command) 26 | - [What to do next ?](#what-to-do-next) 27 | 28 | 29 | 30 | 31 | ## 概要 32 | 33 | Scala 3 Migrate Plugin は、ビルド設定とコードを Scala 3 へ移行する際のサポートをしてくれるコンパイラプラグインです。以前確認した sbt Migration Tutorial では、Scala 3 への移行プロセスを確認しましたが、このプラグインを使用することで、より移行が楽になりそうです。 34 | 35 | それでは、Scala 3 Migrate Plugin を使用した場合の移行プロセスを確認しましょう。 36 | 37 | ## ドキュメント参照先 38 | 39 | [Scala 3 Migration guide](https://scalacenter.github.io/scala-3-migration-guide/) の Tooling からこちらを参照します。 40 | 41 | - [Scala 3 Migrate Plugin](https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html) 42 | 43 | ## Scala 3 Migrate Plugin 44 | 45 | Scala 3 Migrate Plugin による移行は、以下の独立したステップで構成されており、これからはそれぞれ sbt のコマンドとなっている。 46 | 47 | - `migrate-libs` : `libraryDependencies` の更新をサポートする 48 | - `migrate-scalacOptions` : `scalacOptions` の更新をサポートする 49 | - `migrate-syntax` : Scala 2.13 のコードに含まれるいくつかの非互換なシンタックスを修正する 50 | - `migrate` : 必要最低限の inferred types(推論された型)と暗黙的な情報を追加することで、Scala 3 でのコンパイルを試みる 51 | 52 | ### Requirements 53 | 54 | - Scala 2.13(望ましいのは Scala 2.13.6) 55 | - sbt 1.4 以上 56 | - 免責事項: マクロを含むライブラリを移行することはできない 57 | - 現在、すべてのコマンドは Compile configuration で動作する 58 | - Test のような他の configuration の対応はまだ未対応 59 | 60 | ### Installation 61 | 62 | https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html#installation 63 | 64 | - `addSbtPlugin` で、`sbt-scala3-migrate` を設定する 65 | - sbt 1.4 の場合は、`sbt-dotty` も設定する 66 | 67 | ### Choose a module 68 | 69 | https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html#choose-a-module 70 | 71 | - プロジェクトに複数のモジュールが含まれている場合、最初のステップはどのモジュールを最初に移行するのかを選択する 72 | - Scala3-migrate は、一度に1つのモジュールを操作するため、選択したモジュールが、プロジェクトの aggregate でないことを確認する 73 | 74 | ### Migrate library dependencies 75 | 76 | https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html#migrate-library-dependencies 77 | 78 | - `migrate-libs` コマンドは、`libraryDependencies` を移行する 79 | - sbt コマンドの `migrate-libs [projectId]` で実行する 80 | ``` 81 | > migrate-libs main 82 | ``` 83 | 84 | #### Macro library 85 | 86 | - Scala 2.13 のマクロは、Scala 3 のコンパイラでは実行できない。そのため、マクロライブラリに依存している場合は、このライブラリが Scala 3 用に公開されるまで待つ必要がある 87 | 88 | #### Compiler plugins 89 | 90 | - Scala 2.13 のコンパイラプラグインは、Scala 3 ではサポートしていない 91 | - もしも Scala 3 でサポートしていないコンパイラプラグインが設定されていた場合、コンパイラプラグインなしでコンパイルできるようにコードを修正する必要がある 92 | - 例 93 | - `better-monadic-for` は、Scala 3 ではサポートしていないので削除して、コンパイラプラグインなしでコンパイルできるようにコードを修正する 94 | - `kind-projector` は、同等のコンパイラオプションがあるので、`scalacOptions` に追加する 95 | 96 | #### Libraries that can be updated 97 | 98 | - Scala3-migrate が利用可能なバージョンを提案してくれる 99 | - 例 100 | - `cats-core` には、`3.0.x` 用に公開された利用可能なバージョンがあるため、そのバージョンを提案してくれる 101 | - `scalafix-rules` は、Scala 3 用の利用可能なバージョンはないが、ライブラリにはマクロが含まれていないため、2.13 バージョンを Scala 3 でそのまま利用することができる。ただしその場合、Scala のバージョンを明示する必要がある 102 | ```scala 103 | // 移行前 104 | "ch.epfl.scala" %% "scalafix-rules" % "0.9.26" % "test" 105 | // 移行後 106 | "ch.epfl.scala" % "scalafix-rules_2.13" % "0.9.26" % "test" 107 | ``` 108 | - 3.0.x のコードには _2.13 アーティファクトを使いたいが、2.13.x には _2.13 を、2.12.x には_2.12 を使い続けたい、ということを表現する場合 109 | ```scala 110 | // sbt 1.5 以上の場合 111 | "ch.epfl.scala"%% "scalafix-rules" % "0.9.26" % Test cross CrossVersion.for3Use2_13 112 | // sbt 1.3 または 1.4 の場合は 113 | ("ch.epfl.scala" %% "scalafix-rules" % "0.9.26" % Test).withDottyCompat(scalaVersion.value) 114 | ``` 115 | 116 | #### Valid libraries 117 | 118 | - そのままの状態で使用できるライブラリは、コンソールに `Valid` と出力される 119 | ``` 120 | [info] ch.epfl.scala:scalafix-interfaces:0.9.26 -> Valid 121 | ``` 122 | - なお、Java ライブラリの場合も `Valid` と出力される 123 | 124 | #### The new build after migrate-libs 125 | 126 | https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html#the-new-build-after-migrate-libs 127 | 128 | - `migrate-libs` によって変更された差分を確認する 129 | 130 | ### Migrate scalacOptions 131 | 132 | https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html#migrate-scalacoptions 133 | 134 | - `migrate-scalacOptions` コマンドは、`scalacOptions` を移行する 135 | - sbt コマンドの `migrate-scalacOptions [projectId]` で実行する 136 | ``` 137 | > migrate-scalacOptions main 138 | ``` 139 | - 移行する `scalacOptions` は [Compiler Options Table](https://scalacenter.github.io/scala-3-migration-guide/docs/compiler-options/compiler-options-table.html) がベースとなっている 140 | 141 | #### Non-existing scalacOptions in Scala 3 142 | 143 | - Scala 3 で存在しなくなった scalacOptions の移行 144 | - 例 145 | - `-Yrangepos`: 146 | - いくつかの `scalacOptions` はビルドファイルではなく、sbt プラグインによって提供されている 147 | - 例えば scala3-migrate は Scala 2 の semanticdb を有効にし、`-Yrangepos` を追加する 148 | - ここでは sbt が Scala 3 の semanticdb のオプションを適応するので、何もする必要はない 149 | - `-Wunused` : 150 | - この `scalacOption` は削除する必要がある 151 | 152 | #### Renamed scalacOptions 153 | 154 | - 名称が変更された `scalacOptions` はリネームするだけ 155 | 156 | #### Plugins specific scalacOptions 157 | 158 | 前回の `migrate-libs` コマンドでは、すでに `kind-projector` を適応させ、`better-monadic-for` を削除したので、前のステップが正しく行われていれば、何も変更する必要はない 159 | 160 | #### The new build after migrate-scalacOptions 161 | 162 | - `migrate-scalacOptions` によって変更された差分を確認する 163 | 164 | ### Migrate the syntax 165 | 166 | https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html#migrate-the-syntax 167 | 168 | - `migrate-syntax` は、以下の Scalafix のルールを低起用することで、いくつかの非互換性を修正する 169 | - ProcedureSyntax 170 | - fix.scala213.ConstructorProcedureSyntax 171 | - fix.scala213.ExplicitNullaryEtaExpansion 172 | - fix.scala213.ParensAroundLambda 173 | - fix.scala213.ExplicitNonNullaryApply 174 | - fix.scala213.Any2StringAdd 175 | - sbt コマンドの `migrate-syntax [projectId]` で実行する 176 | ``` 177 | > migrate-syntax main 178 | ``` 179 | - 対象の非互換性については、[Incompatibility Table](https://scalacenter.github.io/scala-3-migration-guide/docs/incompatibilities/incompatibility-table.html) を参照する 180 | 181 | ### Migrate the code: last command 182 | 183 | https://scalacenter.github.io/scala-3-migration-guide/docs/tooling/scala-3-migrate-plugin.html#migrate-the-code-last-command 184 | 185 | - sbt コマンドの `migrate [projectId]` で実行する 186 | ``` 187 | > migrate main 188 | ``` 189 | - Scala 3 は、新しい型推論アルゴリズムを使用しているため、Scala 3.0 のコンパイラは、Scala 2.13 で推論された型とは異なる型を推論することができる 190 | - このコマンドの目的は、コードをコンパイルするために必要な型を見つけること 191 | - なお、ライブラリが正しく移行されていない場合、 `migrage [projectId]` を実行しても問題のあるライブラリを報告することはできない 192 | - このツールは最後に Scala 3 で `-rewrite` を使ってコンパイルする 193 | 194 | ### What to do next ? 195 | 196 | - 別のモジュール MODULE2 に対して、Scala3-migrate で移行を行う 197 | - MODULE2 が最後に移行したモジュールに依存している場合は、MODULE2 の `scalacOptions` に `-Ytasty-reader` を追加するか、`MODULE-MIGRATED/scalaVersion := "2.13.6" ` を設定する必要がある 198 | - 移行が完了したら、プラグインから `scala3-migrate` を削除する 199 | -------------------------------------------------------------------------------- /docs/step04/07_IncompatibilityTable.md: -------------------------------------------------------------------------------- 1 | # Incompatibility Table {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Incompatibility Table](#incompatibility-table-1) 10 | - [Syntactic Changes](#syntactic-changes) 11 | - [Dropped Features](#dropped-features) 12 | - [Other Changed Features](#other-changed-features) 13 | 14 | 15 | 16 | ## 概要 17 | 18 | 最後に Scala 2.13 と Scala 3.0 の非互換性について確認しましょう。 19 | 20 | Scala 2.13 と Scala 3.0 の非互換性とは、Scala 2.13 ではコンパイルできても Scala 3 ではコンパイルできないコードのことを指します。コードベースを Scala 2.13 から Scala 3 へ移行するには、ソースコードの非互換性をすべて見つけ出して修正する必要があります。 21 | 22 | ## ドキュメント参照先 23 | 24 | [Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html) からこちらを参照します。 25 | 26 | - [Incompatibility Table](https://docs.scala-lang.org/scala3/guides/migration/incompatibility-table.html) 27 | 28 | 補足としてこちらも参照します。 29 | 30 | - [Syntactic Changes](https://docs.scala-lang.org/scala3/guides/migration/incompat-syntactic.html) 31 | - [Dropped Features](https://docs.scala-lang.org/scala3/guides/migration/incompat-dropped-features.html) 32 | - [Other Changed Features](https://docs.scala-lang.org/scala3/guides/migration/incompat-other-changes.html) 33 | 34 | ## Incompatibility Table 35 | 36 | https://docs.scala-lang.org/scala3/guides/migration/incompatibility-table.html 37 | 38 | Incompatibility Table は、Scala 2.13 と Scala 3 の既知の非互換性について、それぞれどのような移行方法があるのかがまとめられています。 39 | 40 | 観点としては、 41 | 42 | - Scala 2.13 コンパイラが deprecation または feature の警告メッセージを出力するか 43 | - それに対する [Scala3 migration](https://docs.scala-lang.org/scala3/guides/migration/tooling-migration-mode.html) のルールの有無 44 | - それを修正するための Scalafix ルールの有無 45 | 46 | ここからは、Incompatibility Table をざっと眺めながら、Scala 3.0 Migration Mode の rewrite を実際に動かしてみたいと思います。 47 | 48 | 本リポジトリの step04 のプロジェクトには、非互換なソースコードがコメントアウトの状態で書いてあります。それらのコメントアウトを一つずつ外し、`build.sbt` のコンパイラオプションを切り替えることで、エラーが警告になったり、rewrite が発動したりを確認することができます。 49 | 50 | ### Syntactic Changes 51 | 52 | - :memo: [01_RestrictedKeywords.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/01_RestrictedKeywords.scala) 53 | - :memo: [02_ProcedureSyntax.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/02_ProcedureSyntax.scala) 54 | - :memo: [03_ParenthesesAroundLambdaParameter.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/03_ParenthesesAroundLambdaParameter.scala) 55 | - :memo: [04_OpenBraceIndentationForPassingAnArgument.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/04_OpenBraceIndentationForPassingAnArgument.scala) 56 | - :memo: [05_WrongIndentation.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/05_WrongIndentation.scala) 57 | - :memo: [06__AsATypeParameter.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/06__AsATypeParameter.scala) 58 | - :memo: [07_+and-AsTypeParameter.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/07_+and-AsTypeParameter.scala) 59 | 60 | ### Dropped Features 61 | 62 | - :memo: [01_SymbolLiterals.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/01_SymbolLiterals.scala) 63 | - :memo: [02_DoWhileConstruct.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/02_DoWhileConstruct.scala) 64 | - :memo: [03_AutoApplication.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/03_AutoApplication.scala) 65 | - :memo: [04_ValueEtaExpansion.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/04_ValueEtaExpansion.scala) 66 | 67 | ### Other Changed Features 68 | 69 | - :memo: [01_InheritanceShadowing.scala](/step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/03_otherChangedFeatures/01_InheritanceShadowing.scala) 70 | -------------------------------------------------------------------------------- /docs/step05/02_AnOverviewOfTASTY.md: -------------------------------------------------------------------------------- 1 | # AN OVERVIEW OF TASTY {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [AN OVERVIEW OF TASTY](#an-overview-of-tasty-1) 10 | - [What is TASTy?](#what-is-tasty) 11 | - [The issue with .class files](#the-issue-with-class-files) 12 | - [TASTy to the Rescue](#tasty-to-the-rescue) 13 | - [Key points](#key-points) 14 | - [Tasty benefits](#tasty-benefits) 15 | 16 | 17 | 18 | ## 概要 19 | 20 | Scala 3 で sbt プロジェクトをコンパイルすると、_target/scala-3.0.1/classes_ 配下には、_.class_ ファイルの他に _.tasty_ ファイルが作成されていることが確認できます。この _.tasty_ ファイルには、Scala 3 の型やメソッドのシグネチャなどの情報が、TASTy と呼ばれる format で格納されています。 21 | 22 | ここでは、この TASTy が何を解決するためのものなのかを見ていきたいと思います。 23 | 24 | ## ドキュメント参照先 25 | 26 | [Scala3 Documentation](https://docs.scala-lang.org/scala3) からこちらを参照します。 27 | 28 | - [AN OVERVIEW OF TASTY](https://docs.scala-lang.org/scala3/guides/tasty-overview.html) 29 | 30 | 31 | ## AN OVERVIEW OF TASTY 32 | 33 | https://docs.scala-lang.org/scala3/guides/tasty-overview.html 34 | 35 | ### What is TASTy? 36 | 37 | - TASTy は、Typed Abstract Syntax Trees の頭文字をとったもの 38 | - Scala 3 の high-level interchange format 39 | - _.tasty_ ファイルは、scalac コンパイラによって生成され、プログラムの構文構造、型、位置、さらにはドキュメントなど、ソースコードに関するすべての情報が含まれている 40 | - _.tasty_ ファイルは、JVM 上で実行するために生成される _.class_ ファイルよりもはるかに多くの情報を含んでいる 41 | - Scala 3 のコンパイルプロセスはこのようになっていて、_.scala_ から _.tasty_ を生成したうえで、_.class_ が生成される 42 | ``` 43 | +-------------+ +-------------+ +-------------+ 44 | $ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class | 45 | +-------------+ +-------------+ +-------------+ 46 | ^ ^ ^ 47 | | | | 48 | Your source TASTy file Class file 49 | code for scalac for the JVM 50 | (contains (incomplete 51 | complete information) 52 | information) 53 | ``` 54 | 55 | #### The issue with .class files 56 | 57 | _.class_ ファイルは、型消去(type erasure)などの問題から、実際にはコードの不完全な表現となっている。 58 | 59 | 例えば `List` の例を挙げると、 60 | 61 | ```scala 62 | val xs: List[Int] = List(1, 2, 3) 63 | ``` 64 | 65 | このコードがコンパイルされると、JVM の互換性の要件を満たすために、_.class_ ファイルの内容は次のようなコードに置き換わっている。 66 | 67 | ``` 68 | public scala.collection.immutable.List xs(); 69 | ``` 70 | 71 | このように、Scala のコードを JVM で動作させるために、型消去が行われ、`xs: List[Int]` は、 `xs: List[java.lang.Object]` として表現されるので、`Int` 型の情報は失われることになる。 72 | 73 | そのため、`List[Int]` の要素にアクセスする場合、_.class_ ファイルでは、 `Int` 型が消去されてしまうので、このようにキャストしてアクセスすることになる。 74 | 75 | ```scala 76 | // 元のコード 77 | val x = xs(0) 78 | 79 | // classファイルではこのように置き換わる 80 | int x = (Int) xs.get(0) // Java-ish 81 | val x = xs.get(0).asInstanceOf[Int] // more Scala-like 82 | ``` 83 | 84 | 型消去は、互換性のために行われていることではあるが、例えば、既にコンパイルされているライブラリに対して Scala プログラムをコンパイルしようとすると問題が発生する。 85 | 86 | この問題を解決するためには、_.class_ ファイルよりも多くの情報が必要となる。 87 | 88 | また、ここでは、型消去の話題だけを取り上げているが、JVM が認識していない他のすべての Scala の構造にも同様の問題がある。これには、Union Types、Intersection Types、Trait parameters など、その他多くの Scala 3 の機能が含まれている。 89 | 90 | #### TASTy to the Rescue 91 | 92 | - TASTy format は、型チェック後の完全な abstract syntax tree (AST) を保存する 93 | - Scala 3 では、この TASTy format を Scala 2.13 の "Pickle" format の代わりに使用する 94 | - "Pickle" format は、_.class_ ファイル内のオリジナルの型に関する情報を持たないか、public API のみを持つフォーマット 95 | - AST 全体を保存することには、多くの利点がある 96 | - 分割コンパイル 97 | - 異なる JVM バージョンでの再コンパイル 98 | - プログラムの静的解析 など 99 | 100 | ##### Key points 101 | 102 | _.class_ ファイルの問題点を整理すると、 103 | 104 | 1. Scala の型は、_.class_ ファイルでは完全には表現できない 105 | 2. コンパイル時に得られる情報と実行時に得られる情報には違いがあることを理解する 106 | - コンパイル時は、`xs` が `List[Int]` であることは知っている 107 | - コンパイラが対象のコードを _.class_ ファイルに書き込むときは、`xs` は `List[Object]` として書き込むので、`xs` にアクセスするすべての場所にキャスト情報が追加される 108 | - その結果、実行時には、`xs` が `List[Int]` であることを JVM は知らないことになる 109 | 110 | Scala 3 と TASTy では、コンパイル時に関するもう一つの重要な注意点がある。 111 | 112 | - 他の Scala 3 ライブラリを使用する Scala 3 コードを書いた場合、scalac はそれらの _.class_ ファイルを読む必要はなくて、代わりに _.tasty_ ファイルを読み込むことができる 113 | - これは、Scala 2.13 と Scala 3 を別々にコンパイルして互換性を持たせるために重要 114 | 115 | ### Tasty benefits 116 | 117 | コードの完全な表現を持つことには多くの利点がある。 118 | 119 | - コンパイラはこれを利用して、分離コンパイル(separate compilation)をサポートしている 120 | - Scala の Language Server Protocol (LSP) では、 121 | - ハイパーリンク、コマンド補完、ドキュメンテーションをサポートするために使用される 122 | - 参照検索や rename などのグローバルな操作にも使用される 123 | - TASTy は、新世代の [reflection-based macros](https://dotty.epfl.ch/docs/reference/metaprogramming/macros.html) の優れた基盤となる 124 | - Optimizer や Analyzer は、より詳細なコード解析や高度なコード生成に利用できる 125 | -------------------------------------------------------------------------------- /docs/step06/01_Referenceを俯瞰してみる.md: -------------------------------------------------------------------------------- 1 | # Referenceを俯瞰してみる {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [New Types](#new-types) 11 | - [Enums](#enums) 12 | - [Contextual Abstractions](#contextual-abstractions) 13 | - [Other New Features](#other-new-features) 14 | - [Metaprogramming](#metaprogramming) 15 | - [Other Changed Features](#other-changed-features) 16 | - [Dropped Features](#dropped-features) 17 | 18 | 19 | 20 | ## 概要 21 | 22 | ここまでで、[Reference](https://dotty.epfl.ch/docs/reference/overview.html) や [Migration guide](https://scalacenter.github.io/scala-3-migration-guide) などから、Part 1 で確認しておきたいものは一通り終えました。ここからは Part 2 ということで、Scala 3 の更に深い部分について触れていきたいと思います。 23 | 24 | それでは、改めて [Reference](https://dotty.epfl.ch/docs/reference/overview.html) を俯瞰し、次に見ておきたいページを抽出したいと思います。 25 | 26 | なお、ここまでで確認済みのページに関しては除外してあります。 27 | 28 | ## New Types 29 | 30 | https://dotty.epfl.ch/docs/New%20Types/ 31 | 32 | 型クラスの実装で必要となる Type Lambdas を確認します。 33 | 34 | ```plantuml 35 | @startmindmap 36 | *[#fff] New Types 37 | **[#38c0c4] Type Lambdas 38 | **[#fff] Match Types 39 | **[#fff] Dependent Function Types 40 | **[#fff] Polymorphic Function Types 41 | @endmindmap 42 | ``` 43 | 44 | ## Enums 45 | 46 | https://dotty.epfl.ch/docs/Enums/ 47 | 48 | :construction: ここは敢えて今見なくても良いとも思うので、除外するかも :construction: 49 | 50 | ```plantuml 51 | @startmindmap 52 | *[#fff] Enums 53 | **[#fff] Translation of Enums and ADTs 54 | @endmindmap 55 | ``` 56 | 57 | ## Contextual Abstractions 58 | 59 | https://dotty.epfl.ch/docs/Contextual%20Abstractions/ 60 | 61 | Extension Methods は Part 1 で確認したので、それ以外をやりましょう! 62 | 63 | ```plantuml 64 | @startmindmap 65 | *[#fff] Contextual Abstractions 66 | **[#38c0c4] Overview 67 | **[#38c0c4] Given Instances 68 | **[#38c0c4] Using Clauses 69 | **[#38c0c4] Context Bounds 70 | **[#38c0c4] Importing Givens 71 | **[#38c0c4] Implementing Type classes 72 | **[#38c0c4] Type Class Derivation 73 | **[#38c0c4] Multiversal Equality 74 | **[#38c0c4] Context Functions 75 | **[#38c0c4] Implicit Conversions 76 | **[#38c0c4] By-Name Context Parameters 77 | **[#38c0c4] Relationship with Scala 2 Implicits 78 | @endmindmap 79 | ``` 80 | 81 | ## Other New Features 82 | 83 | https://dotty.epfl.ch/docs/Other%20New%20Features/ 84 | 85 | `The @targetName annotation` は Part 1 で軽く触れたので、今回はこれ以上深堀りしなくても良さそう。 86 | 87 | :construction: どこまで中盤でやるか考え中 :construction: 88 | 89 | ```plantuml 90 | @startmindmap 91 | *[#fff] Other New Features 92 | **[#38c0c4] Transparent Traits 93 | **[#fff] Kind Polymorphism 94 | **[#38c0c4] The Matchable Trait 95 | **[#38c0c4] The @threadUnsafe annotation 96 | **[#999] The @targetName annotation 97 | **[#38c0c4] Safe Initialization 98 | **[#38c0c4] TypeTest 99 | @endmindmap 100 | ``` 101 | 102 | ## Metaprogramming 103 | 104 | https://dotty.epfl.ch/docs/Metaprogramming/ 105 | 106 | Metaprogramming は、必要に応じて見れば良いので、今回は除外します。 107 | 108 | ## Other Changed Features 109 | 110 | https://dotty.epfl.ch/docs/Other%20Changed%20Features/ 111 | 112 | ```plantuml 113 | @startmindmap 114 | *[#fff] Other Changed Features 115 | **[#38c0c4] Numeric Literals 116 | **[#38c0c4] Programmatic Structural Types 117 | **[#fff] Wildcard Arguments in Types 118 | **[#fff] Changes in Type Checking 119 | **[#fff] Changes in Type Inference 120 | **[#38c0c4] Changes in Implicit Resolution 121 | **[#38c0c4] Implicit Conversions 122 | **[#38c0c4] Option-less pattern matching 123 | **[#38c0c4] Automatic Eta Expansion 124 | **[#38c0c4] Changes in Compiler Plugins 125 | **[#38c0c4] Lazy Vals initialization 126 | @endmindmap 127 | ``` 128 | 129 | ## Dropped Features 130 | 131 | https://dotty.epfl.ch/docs/Dropped%20Features/ 132 | 133 | :construction: ここに触れるか考え中 :construction: 134 | 135 | ```plantuml 136 | @startmindmap 137 | *[#fff] Dropped Features 138 | **[#fff] Dropped: Existential Types 139 | **[#fff] Dropped: General Type Projection 140 | @endmindmap 141 | ``` 142 | -------------------------------------------------------------------------------- /docs/step06/02_ContextualAbstractionsの概要.md: -------------------------------------------------------------------------------- 1 | # Contextual Abstractionsの概要 {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [ドキュメント参照先](#ドキュメント参照先) 11 | - [Overview](#overview) 12 | - [Critique of the Status Quo (現状に対する批判)](#critique-of-the-status-quo-現状に対する批判) 13 | - [The New Design](#the-new-design) 14 | - [Contextual Abstractions の再設計の4つの基本的な変更点](#contextual-abstractions-の再設計の4つの基本的な変更点) 15 | - [Contextual Abstractions の発展的な言語機能](#contextual-abstractions-の発展的な言語機能) 16 | - [なぜ新しい設計にしたのか?](#なぜ新しい設計にしたのか) 17 | 18 | 19 | 20 | ## 概要 21 | 22 | Contextual Abstractions(コンテキストの抽象化)は、Scala 2 で様々なユースケースで使用されていた `implicit` の再設計のことが記載されています。それなりにボリュームがあるので、この Step と 次の Step で前半と後半に分けて見ていきたいと思います。 23 | 24 | まずは、Contextual Abstractions の概要で、Scala 2 の `implicit` には現状どのような課題があって、新しい設計はどうなったか、なぜ新しい設計にしたのかについて確認します。 25 | 26 | 27 | ## ドキュメント参照先 28 | 29 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 30 | 31 | - [Overview](https://dotty.epfl.ch/docs/reference/contextual/motivation.html) 32 | 33 | ## Overview 34 | 35 | https://dotty.epfl.ch/docs/reference/contextual/motivation.html 36 | 37 | ### Critique of the Status Quo (現状に対する批判) 38 | 39 | - Scala 2 の `implicit` は、Scala の最も特徴的な機能であり、次のようなユースケースに対してコンテキストを抽象化するための基本的な方法として提供されている 40 | - 型クラスの実装 (implementing type classes) 41 | - コンテキストの確立 (establishing context) 42 | - 依存性の注入 (dependency injection) 43 | - ケイパビリティの表現 (expressing capabilities) 44 | - 新しい型の計算 (computing new types) 45 | - これらの間のリレーションシップの証明 (proving relationships between them) 46 | - `implicit` は優れた機能であると同時に、最も議論を呼んでいる機能でもある。特に批判されているのは、このあたり 47 | 1. それぞれのユースケースの構文が似ていて、混乱させている 48 | ```scala 49 | // 型クラスのインスタンスを表現している(conditional implicit value) 50 | implicit def i1(implicit x: T): C[T] = ... 51 | // implicit conversion 52 | implicit def i2(x: T): C[T] = ... 53 | 1. `implicit` の import が不可解な型のエラーを引き起こす 54 | 1. `implicit` という単一の修飾子が、初学者にとって意図ではなくメカニズムを伝えてしまうので、誤読しやすい 55 | - 型クラスのインスタンスの場合は、unconditional は `implicit object` や `implicit val` だが、conditional は `implicit def` だったり 56 | 1. implicit parameter の構文上の欠点 57 | ```scala 58 | def currentMap(implicit ctx: Context): Map[String, Int] 59 | ``` 60 | - 上記に対して、`currentMap("abc")` と書くと、`String` 型の `"abc"` が implicit parameter の `ctx` の明示的な引数として取られてしまうため、代わりに `currentMap.apply("abc")` と書かなければならない 61 | - また、implicit parameter には名前が必要だが、多くの場合はその名前が参照されることはないので、ちょっと面倒 62 | 1. `implicit` はツールの課題となっている 63 | - 利用可能な `implicit` のセットはコンテキストに依存するので、コード補完はコンテキストを考慮しなければならない。これはIDEでは可能だが、Scaladocのように静的なWebページをベースにしたツールでは近似値しか提供できない 64 | - もうひとつの問題は、深くネストした `implicit` の再帰的な検索が失敗した場合、非常に不明確なエラーメッセージが表示されてしまう 65 | - 歴史的に見てこれらの欠点の多くは、`implicit` のユースケースが徐々に発見されていったという経緯に起因している 66 | - Scala には元々 implicit conversion しかなかった 67 | - implicit parameter と型クラスのインスタンスの定義は、2006年以降に登場したので、便利そうだったので同じような構文を採用したが、これらを区別するための努力は行われていなかった 68 | - 既存の Scala プログラマは現状に慣れていて変更の必要性をあまり感じていないが、初学者にとっては大きなハードルとなっている。このハードルを超えるために、根本的に新しい設計を考える必要があった 69 | 70 | ### The New Design 71 | 72 | Contextual Abstractions の新しい設計は、全体的に機能の相互作用を回避し、言語の一貫性と直交性を高めています。 73 | 74 | #### Contextual Abstractions の再設計の4つの基本的な変更点 75 | 76 | 1. Given Instances 77 | - 暗黙的な修飾子を多数の機能と混ぜ合わせるのではなく、型に対して合成可能な用語を定義する単一の方法を持つ 78 | 1. Using Clauses 79 | - 暗黙のパラメータのための新しい構文 80 | 1. "Given" Imports 81 | - given のみをインポートするためのインポートセレクタ 82 | 1. Implicit Conversions 83 | - implicit conversion は、標準的な `Conversion` クラスのインスタンスとして表現されるようになった 84 | - 今までの implicit conversion は、段階的に廃止される 85 | 86 | #### Contextual Abstractions の発展的な言語機能 87 | 88 | 公式ドキュメントでは、再設計された Contextual Abstractions の基本的な機能をもとにして、以下のような発展的な機能についても解説しています。 89 | 90 | - Context Bounds 91 | - 変更されずに引き継がれる 92 | - Extension Methods 93 | - Scala 2 の `implicit class` を置き換え、型クラスとの統合性を高める 94 | - Implementing Type Classes 95 | - 新しい構文を使って、一般的な型クラスをどのように実装するかを解説している 96 | - Type Class Derivation 97 | - ADTs のための型クラスのインスタンスを自動的に導出させる 98 | - Multiversal Equality 99 | - 型安全性の高い等式をサポートするために、特別な型クラスを導入する 100 | - Context Functions 101 | - コンテキストパラメータを抽象化する方法を提供する 102 | - By-Name Context Parameters 103 | - ループを使わずに再帰的な合成値を定義するために不可欠なツール 104 | - Relationship with Scala 2 Implicits 105 | - 旧スタイルの implicits と新スタイルの givens の関係と、一方から他方への移行方法について解説している 106 | 107 | #### なぜ新しい設計にしたのか? 108 | 109 | なぜ、既存の `implicit` に手を加えるのではなく、新しい構文とルールを導入する必要があったのか、その理由は以下の通りとされています。 110 | 111 | - 現状の `implicit` は、明らかに構文上の問題があり、それを解決するためには異なる構文が必要だった 112 | - 移行の観点で、途中で `implicit` のルールを変えることはできないが、構文の変更であれば容易 113 | - 新しいルールで新しい構文を導入し、クロスコンパイルを容易にするためにしばらくの間は古い構文をサポートし、将来的には古い構文を非推奨にして段階的に廃止にする 114 | - 新しい構文を導入せずに古い構文を維持することは、このような方法を提供できないし、進化のための実行可能な方法を提供できない 115 | - 教育の問題で、既存の文献や教材に修正を加えると、特に初学者を混乱させてしまう 116 | - 新しい設計と明確に分離しておくことで、`implicit` について言及しているものはすべて古いものと考えることができる 117 | -------------------------------------------------------------------------------- /docs/step06/03_ContextualAbstractionsの4つの基本的な変更点.md: -------------------------------------------------------------------------------- 1 | # Contextual Abstractionsの4つの基本的な変更点 {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [ドキュメント参照先](#ドキュメント参照先) 11 | - [Given Instances](#given-instances) 12 | - [Using Clauses](#using-clauses) 13 | - [Importing Givens](#importing-givens) 14 | - [Implicit Conversions](#implicit-conversions) 15 | 16 | 17 | 18 | 19 | ## 概要 20 | 21 | これから Contextual Abstractions の各機能について見ていきますが、まずは4つの基本的な変更点である、Given Instances、Using Clauses、Importing Givens、Implicit Conversion について確認しておきましょう。 22 | 23 | ## ドキュメント参照先 24 | 25 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 26 | 27 | - [Given Instances](https://dotty.epfl.ch/docs/reference/contextual/givens.html) 28 | - [Using Clauses](https://dotty.epfl.ch/docs/reference/contextual/using-clauses.html) 29 | - [Importing Givens](https://dotty.epfl.ch/docs/reference/contextual/given-imports.html) 30 | - [Implicit Conversions](https://dotty.epfl.ch/docs/reference/contextual/conversions.html) 31 | 32 | ## Given Instances 33 | 34 | https://dotty.epfl.ch/docs/reference/contextual/givens.html 35 | 36 | - Given Instances (またはシンプルに "givens") は、特定の型に対する振る舞いを定義し、コンテキストパラメータへの引数を合成する役割を果たす 37 | ```scala 38 | trait Ord[T]: 39 | def compare(x: T, y: T): Int 40 | extension (x: T) def < (y: T) = compare(x, y) < 0 41 | extension (x: T) def > (y: T) = compare(x, y) > 0 42 | 43 | // Given Instances 44 | given intOrd: Ord[Int] with 45 | def compare(x: Int, y: Int) = 46 | if x < y then -1 else if x > y then +1 else 0 47 | ``` 48 | - givens は、名前を省略することができる 49 | ```scala 50 | given Ord[Int] with 51 | def compare(x: Int, y: Int) = ??? 52 | ``` 53 | - givens のエイリアスを定義することができる 54 | ```scala 55 | given global: ExecutionContext = ExecutionContext.fromExecutorService(ForkJoinPool()) 56 | ``` 57 | - givens のエイリアスは匿名にすることができる 58 | ```scala 59 | given ExecutionContext = ExecutionContext.fromExecutorService(ForkJoinPool()) 60 | ``` 61 | - given は、パターンの中にも書くことができる 62 | ```scala 63 | for given Context <- applicationContexts do ??? 64 | pair match 65 | case (ctx @ given Context, y) => ??? 66 | ``` 67 | - `scala.util.NotGiven` を使用することで、givens がスコープに存在しないかどうかを調べることができる 68 | 69 | :memo: [GivenInstance.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/GivenInstance.scala) 70 | 71 | 72 | ## Using Clauses 73 | 74 | https://dotty.epfl.ch/docs/reference/contextual/using-clauses.html 75 | 76 | - メソッドに対して `using` 句を使用してコンテキストパラメータを暗黙的に指定できる(Scala 2 の implicit parameter に代わるもの) 77 | ```scala 78 | def max[T](x: T, y: T)(using ord: Ord[T]): T = 79 | if ord.compare(x, y) < 0 then y else x 80 | 81 | // Ord[Int]、Ord[List[Int]] の givens がスコープ内に存在する場合はコンテキストパラメータを省略できる 82 | max(2, 3) 83 | max(List(1, 2, 3), Nil) 84 | ``` 85 | - `using` 句の名前は省略できる 86 | ```scala 87 | def maximum[T](xs: List[T])(using Ord[T]): T = xs.reduceLeft(max) 88 | ``` 89 | - `summon` は特定の型の givens を返す(Scala 2 の `implicity` に代わるもの) 90 | ```scala 91 | summon[Ord[List[Int]]] 92 | ``` 93 | 94 | :memo: [UsingClauses.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UsingClauses.scala) 95 | 96 | ## Importing Givens 97 | 98 | https://dotty.epfl.ch/docs/reference/contextual/given-imports.html 99 | 100 | - givens をインポートするために使用する 101 | ```scala 102 | // 今までのインポートは、Given Instance 以外をインポートする 103 | //(と思っているのだが、そういうわけではなさそう???) 104 | import A.* 105 | // givens のみをインポートする 106 | import A.given 107 | // すべてのメンバをインポートする 108 | import A.{ given, * } 109 | ``` 110 | - このルールによって得られるメリット 111 | - スコープ内の givens がどこからインポートされているのかが、より明確になる 112 | - 通常のワイルドカードによるインポートの長いリストの中で埋もれることがなくなる 113 | - 他のものをインポートすることなく、すべての givens をインポートすることができる 114 | - givens は、無名の可能性があるため、通常はワイルドカードによるインポートを使用するが、もしも個別の givens をインポートしたい場合は、By-type インポートを使用する 115 | ```scala 116 | import A.given TC 117 | // 複数の場合 118 | import A.{given T1, ..., given Tn} 119 | ``` 120 | - パラメータ化された型の givens をインポートするには、ワイルドカード引数を使用する 121 | ```scala 122 | import Instances.{ given Ordering[?], given ExecutionContext } 123 | // By-nameのインポートと混在させる場合は、givens は最後に書く 124 | import Instances.{ im, given Ordering[?] } 125 | ``` 126 | - givens のインポートは、マイグレーションを考慮して古いスタイルの暗黙的な定義もスコープに含める 127 | - ただし、Scala 3.1 以降では非推奨となり段階的に削除される 128 | 129 | :memo: [ImportingGivens.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ImportingGivens.scala) 130 | 131 | 132 | ## Implicit Conversions 133 | 134 | https://dotty.epfl.ch/docs/reference/contextual/conversions.html 135 | 136 | - Scala 3 では暗黙の型変換は、`scala.Conversion` クラスの givens として定義する 137 | ```scala 138 | // 例えば、String から Token への暗黙の型変換を定義する場合 139 | given Conversion[String, Token] with 140 | def apply(str: String): Token = new KeyWord(str) 141 | ``` 142 | - エイリアスを使えば、より簡潔に書くことができる 143 | ```scala 144 | given Conversion[String, Token] = new KeyWord(_) 145 | ``` 146 | - `Predef` には、プリミティブな数値型を `java.lang.Number` のサブクラスにマッピングする "auto-boxing" 変換が定義されている。例えば、`Int` から `java.lang.Integer` への変換は次のように定義されている 147 | ```scala 148 | given int2Integer: Conversion[Int, java.lang.Integer] = 149 | java.lang.Integer.valueOf(_) 150 | ``` 151 | 152 | :memo: [ImplicitConversions.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ImplicitConversions.scala) 153 | 154 | -------------------------------------------------------------------------------- /docs/step06/04_型クラスの実装.md: -------------------------------------------------------------------------------- 1 | # 型クラスの実装 {ignore=true} 2 | 3 | **:construction: EPFLのドキュメントは、docs.scala-lang.org/scala3 に統合されたためこの内容は古くなっています。 :construction:** 4 | 5 | 6 | 7 | 8 | 9 | - [概要](#概要) 10 | - [ドキュメント参照先](#ドキュメント参照先) 11 | - [Context Bounds](#context-bounds) 12 | - [Implementing Type Classes (Scala 3 Book)](#implementing-type-classes-scala-3-book) 13 | - [Implementing Type classes (Reference)](#implementing-type-classes-reference) 14 | - [Type Lambdas](#type-lambdas) 15 | 16 | 17 | 18 | 19 | ## 概要 20 | 21 | ここでは、これまで見てきた Contextual Abstractions の基本的な機能をもとに、型クラスの実装方法を見ていきます。最初に Scala 2 から引き継がれた Context Bounds に触れてから、Scala 3 Book や Reference の Type classes のページへと進み、最後に補足的に Type Lambdas にも触れておきたいと思います。 22 | 23 | ## ドキュメント参照先 24 | 25 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 26 | 27 | - [Context Bounds](https://dotty.epfl.ch/docs/reference/contextual/context-bounds.html) 28 | - [Implementing Type classes](https://dotty.epfl.ch/docs/reference/contextual/type-classes.html) 29 | 30 | また、New Types よりこちらも参照します。 31 | 32 | - [Type Lambdas](https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html) 33 | 34 | [Documentation for Scala 3](https://docs.scala-lang.org/scala3) の [Scala 3 Book](https://docs.scala-lang.org/scala3/book/introduction.html) からは、こちらを参照します。 35 | 36 | - [Implementing Type Classes](https://docs.scala-lang.org/scala3/book/ca-type-classes.html) 37 | 38 | ## Context Bounds 39 | 40 | https://dotty.epfl.ch/docs/reference/contextual/context-bounds.html 41 | 42 | - Context Bounds は、型パラメータに依存するコンテキストパラメータの共通パターンを表現するための省略形 43 | ```scala 44 | // using を使用してコンテキストパラメータを渡す書き方 45 | def maximum[T](xs: List[T])(using Ord[T]): T = xs.reduceLeft(max) 46 | 47 | // Context Bounds を使用した場合 48 | def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max) 49 | ``` 50 | - Context Bounds から生成されるコンテキストパラメータは、含まれるメソッドやクラスの定義の最後に展開される 51 | ```scala 52 | // メソッドの場合 53 | def f[T: C1 : C2, U: C3](x: T)(using y: U, z: V): R = ??? 54 | // このように展開される 55 | def f[T, U](x: T)(using y: U, z: V)(using C1[T], C2[T], C3[U]): R = ??? 56 | 57 | // クラスの場合 58 | class class1[T: C1 : C2, U: C3](x: T)(using y: U, z: V) 59 | // このように展開される 60 | class class2[T, U](x: T)(using y: U, z: V)(using C1[T], C2[T], C3[U]) 61 | ``` 62 | - Context Bounds はサブタイプ境界と組み合わせることができる(両方が存在する場合は、サブタイプ境界を先に書く) 63 | ```scala 64 | def g[T <: B : C](x: T): R = ??? 65 | ``` 66 | - 移行を容易にするために Scala 3.0 では、古いスタイルの implicit parameter にマッピングされるが、Scala 3.1 以降では、代わりに `using` 句のコンテキストパラメータにマッピングされる 67 | 68 | :memo: [ContextBounds.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ContextBounds.scala) 69 | 70 | ## Implementing Type Classes (Scala 3 Book) 71 | 72 | https://docs.scala-lang.org/scala3/book/ca-type-classes.html 73 | 74 | - 型クラスとは、パラメータ化された抽象型のことで、サブタイピングを使わずに任意のクローズドなデータ型に対して新しいビヘイビアを追加することができる 75 | - このようなスタイルのプログラミングは、例えば以下のユースケースで有用 76 | - 標準ライブラリやサードパーティライブラリがどのようにそのビヘイビアに準拠するのかを表現できる 77 | - 複数の型の間にサブタイプの関係を導入することなく、それらの型にビヘイビアを追加すること [Ad hoc polymorphism](https://en.wikipedia.org/wiki/Ad_hoc_polymorphism) 78 | - Scala 3 では、型クラスは1つ以上のパラメータを持つ単なる `trait` であり、その実装は `extends` キーワードではなく、`given` Instances で定義する 79 | - サンプルコードでは、`Showable` という型クラスを例に実装方法を解説している。詳細は下記のコードで。 80 | 81 | :memo: [ImplementingTypeClasses.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ImplementingTypeClasses.scala) 82 | 83 | ## Implementing Type classes (Reference) 84 | 85 | https://dotty.epfl.ch/docs/reference/contextual/type-classes.html 86 | 87 | - Monoid、Functor、Monad に対する実装例を解説している。詳細は下記のコードで。 88 | 89 | :memo: [SemigroupsAndMonoids.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/SemigroupsAndMonoids.scala) / [Functors1.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Functors1.scala) / [Functors2.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Functors2.scala) / [Monads.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Monads.scala) 90 | 91 | ## Type Lambdas 92 | 93 | https://dotty.epfl.ch/docs/reference/new-types/type-lambdas.html 94 | 95 | - Type Lambdas は、型の定義を行わずに higher-kinded type を直接表現できる 96 | ```scala 97 | type F1 = Functor[Option] // OK 98 | type F2 = Functor[List] // OK 99 | // type F3 = Functor[Map] // !! 100 | 101 | // Type Lambdas 102 | type F3 = Functor[[A] =>> Map[Int, A]] // OK 103 | ``` 104 | - Type Lambdas の型パラメータは上限/下限境界を指定することはできるが、変位指定はできない 105 | 106 | :memo: [TypeLambdas.scala](/step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/TypeLambdas.scala) -------------------------------------------------------------------------------- /docs/step07/01_TypeClassDerivation.md: -------------------------------------------------------------------------------- 1 | # Type Class Derivation {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Type Class Derivation](#type-class-derivation-1) 10 | 11 | 12 | 13 | ## 概要 14 | 15 | *** TO BE FILLED IN *** 16 | 17 | ## ドキュメント参照先 18 | 19 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 20 | 21 | - [Type Class Derivation](https://dotty.epfl.ch/docs/reference/contextual/derivation.html) 22 | 23 | ## Type Class Derivation 24 | 25 | https://dotty.epfl.ch/docs/reference/contextual/derivation.html 26 | 27 | *** TO BE FILLED IN *** 28 | 29 | -------------------------------------------------------------------------------- /docs/step07/02_MultiversalEquality.md: -------------------------------------------------------------------------------- 1 | # Multiversal Equality {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Multiversal Equality](#multiversal-equality-1) 10 | 11 | 12 | 13 | ## 概要 14 | 15 | *** TO BE FILLED IN *** 16 | 17 | ## ドキュメント参照先 18 | 19 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 20 | 21 | - [Multiversal Equality](https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html) 22 | 23 | ## Multiversal Equality 24 | 25 | https://dotty.epfl.ch/docs/reference/contextual/multiversal-equality.html 26 | 27 | *** TO BE FILLED IN *** 28 | 29 | -------------------------------------------------------------------------------- /docs/step07/03_ContextFunctions.md: -------------------------------------------------------------------------------- 1 | # Context Functions {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Context Functions](#context-functions-1) 10 | 11 | 12 | 13 | ## 概要 14 | 15 | *** TO BE FILLED IN *** 16 | 17 | ## ドキュメント参照先 18 | 19 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 20 | 21 | - [Context Functions](https://dotty.epfl.ch/docs/reference/contextual/context-functions.html) 22 | 23 | ## Context Functions 24 | 25 | https://dotty.epfl.ch/docs/reference/contextual/context-functions.html 26 | 27 | *** TO BE FILLED IN *** 28 | 29 | -------------------------------------------------------------------------------- /docs/step07/04_By-NameContextParameters.md: -------------------------------------------------------------------------------- 1 | # By-Name Context Parameters {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [By-Name Context Parameters](#by-name-context-parameters-1) 10 | 11 | 12 | 13 | ## 概要 14 | 15 | *** TO BE FILLED IN *** 16 | 17 | ## ドキュメント参照先 18 | 19 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 20 | 21 | - [By-Name Context Parameters](https://dotty.epfl.ch/docs/reference/contextual/by-name-context-parameters.html) 22 | 23 | ## By-Name Context Parameters 24 | 25 | https://dotty.epfl.ch/docs/reference/contextual/by-name-context-parameters.html 26 | 27 | *** TO BE FILLED IN *** 28 | 29 | -------------------------------------------------------------------------------- /docs/step07/05_RelationshipWithScala2Implicits.md: -------------------------------------------------------------------------------- 1 | # Relationship with Scala 2 Implicits {ignore=true} 2 | 3 | 4 | 5 | 6 | 7 | - [概要](#概要) 8 | - [ドキュメント参照先](#ドキュメント参照先) 9 | - [Relationship with Scala 2 Implicits](#relationship-with-scala-2-implicits-1) 10 | 11 | 12 | 13 | ## 概要 14 | 15 | *** TO BE FILLED IN *** 16 | 17 | ## ドキュメント参照先 18 | 19 | [Reference](https://dotty.epfl.ch/docs/reference/overview.html) の Contextual Abstractions よりこちらを参照します。 20 | 21 | - [Relationship with Scala 2 Implicits](https://dotty.epfl.ch/docs/reference/contextual/relationship-implicits.html) 22 | 23 | ## Relationship with Scala 2 Implicits 24 | 25 | https://dotty.epfl.ch/docs/reference/contextual/relationship-implicits.html 26 | 27 | *** TO BE FILLED IN *** 28 | -------------------------------------------------------------------------------- /project/Util.scala: -------------------------------------------------------------------------------- 1 | import scala.util._ 2 | import scala.sys.process._ 3 | 4 | import sbt._ 5 | 6 | object Util { 7 | def styled(in: Any): String = 8 | scala.Console.CYAN + in + scala.Console.RESET 9 | 10 | def prompt(projectName: String): String = 11 | gitPrompt 12 | .fold(projectPrompt(projectName)) { g => 13 | s"$g:${projectPrompt(projectName)}" 14 | } 15 | 16 | private def projectPrompt(projectName: String): String = 17 | s"sbt:${styled(projectName)}" 18 | 19 | def projectName(state: State): String = 20 | Project 21 | .extract(state) 22 | .currentRef 23 | .project 24 | 25 | private def gitPrompt: Option[String] = 26 | for { 27 | b <- branch.map(styled) 28 | h <- hash.map(styled) 29 | } yield s"git:$b:$h" 30 | 31 | private def branch: Option[String] = 32 | run("git rev-parse --abbrev-ref HEAD") 33 | 34 | private def hash: Option[String] = 35 | run("git rev-parse --short HEAD") 36 | 37 | private def run(command: String): Option[String] = 38 | Try( 39 | command 40 | .split(" ") 41 | .toSeq 42 | .!!(noopProcessLogger) 43 | .trim 44 | ).toOption 45 | 46 | private val noopProcessLogger: ProcessLogger = 47 | ProcessLogger(_ => (), _ => ()) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.5.5 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addDependencyTreePlugin -------------------------------------------------------------------------------- /sbt.sbt: -------------------------------------------------------------------------------- 1 | import Util._ 2 | 3 | Global / onChangedBuildSource := ReloadOnSourceChanges 4 | 5 | ThisBuild / watchBeforeCommand := Watch.clearScreen 6 | ThisBuild / watchTriggeredMessage := Watch.clearScreenOnTrigger 7 | ThisBuild / watchForceTriggerOnAnyChange := true 8 | 9 | ThisBuild / shellPrompt := { state => s"${prompt(projectName(state))}> " } 10 | ThisBuild / watchStartMessage := { 11 | case (iteration, ProjectRef(build, projectName), commands) => 12 | Some { 13 | s"""|~${commands.map(styled).mkString(";")} 14 | |Monitoring source files for ${prompt(projectName)}...""".stripMargin 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /step01/src/main/scala/com/github/shinharad/gettingStartedWithScala3/NewControlSyntax.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package newControlSyntax 3 | 4 | @main def no1(): Unit = 5 | println("-" * 20) 6 | 7 | val x = 10 8 | 9 | val r = if x < 0 then 10 | "negative" 11 | else if x == 0 then 12 | "zero" 13 | else 14 | "positive" 15 | 16 | println(r) 17 | println("-" * 20) 18 | 19 | @main def no2(): Unit = 20 | println("-" * 20) 21 | 22 | val x = 10 23 | 24 | val r = if x < 0 then -x else x 25 | 26 | println(r) 27 | println("-" * 20) 28 | 29 | // while 30 | @main def no3(): Unit = 31 | println("-" * 20) 32 | 33 | var x = 10 34 | val f: Int => Int = _ - 1 35 | 36 | while x >= 0 do x = f(x) 37 | 38 | println(x) 39 | println("-" * 20) 40 | 41 | // for 42 | @main def no4(): Unit = 43 | println("-" * 20) 44 | 45 | val xs = List(1, 2, -1, 3, -3, 4, 5) 46 | 47 | val r = 48 | for x <- xs if x > 0 49 | yield x * x 50 | 51 | println(r) 52 | println("-" * 20) 53 | 54 | @main def no5(): Unit = 55 | println("-" * 20) 56 | 57 | val xs = List(1, 2, 3) 58 | val ys = List(3, 4, 6) 59 | 60 | for 61 | x <- xs 62 | y <- ys 63 | do 64 | println(x + y) 65 | 66 | println("-" * 20) 67 | 68 | // try 69 | @main def no6(): Unit = 70 | println("-" * 20) 71 | 72 | val x = 10 73 | 74 | import java.io.IOException 75 | def body: Unit = throw IOException("io exception") 76 | def handle: Unit = println("# error") 77 | 78 | try body 79 | catch case ex: IOException => handle 80 | 81 | println("-" * 20) 82 | -------------------------------------------------------------------------------- /step01/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OptionalBraces.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package optionalBraces 3 | 4 | // Indentation Rules 5 | 6 | // def no1_ng = 7 | // val x = 10 8 | // if (x < 0) { 9 | // println(1) 10 | // println(2) 11 | 12 | // println("done") // error: indented too far to the left 13 | 14 | def no1_ok(): Unit = 15 | val x = 10 16 | if (x < 0) { 17 | println(1) 18 | println(2) 19 | } 20 | println("done") 21 | 22 | // Optional Braces 23 | 24 | // def no2_ng = 25 | // val x = 10 26 | // if x < 0 then 27 | // -x 28 | // else // error: `else` does not align correctly 29 | // x 30 | 31 | def no2_ok: Int = 32 | val x = 10 33 | if x < 0 then 34 | -x 35 | else 36 | x 37 | 38 | // Optional Braces Around Template Bodies 39 | 40 | trait A: 41 | def f: Int 42 | 43 | class C(x: Int) extends A: 44 | def f = x 45 | 46 | object O: 47 | def f = 3 48 | 49 | enum Color: 50 | case Red, Green, Blue 51 | 52 | def no3 = 53 | new A: 54 | def f = 3 55 | 56 | package p: 57 | def a = 1 58 | 59 | package q: 60 | def b = 2 61 | 62 | // Indentation and Braces 63 | 64 | def no4(): Unit = 65 | def f(x: Int, y: Int => Int): Int = ??? 66 | val x = 10 67 | 68 | // インデントは中括弧 {...} だけでなく、括弧 [....] や括弧 (....) も自由に混在させることができる 69 | { 70 | val z = f(x: Int, y => 71 | x * ( 72 | y + 1 73 | ) + 74 | (x + 75 | x) 76 | ) 77 | } 78 | 79 | // Special Treatment of Case Clauses 80 | 81 | def no5(): Unit = 82 | val x = 1 83 | 84 | x match 85 | case 1 => print("I") 86 | case 2 => print("II") 87 | case 3 => print("III") 88 | case 4 => print("IV") 89 | case 5 => print("V") 90 | 91 | println(".") 92 | 93 | // The End Marker 94 | 95 | package p1.p2: 96 | 97 | abstract class C(): 98 | 99 | def this(x: Int) = 100 | this() 101 | if x > 0 then 102 | val a :: b = 103 | x :: Nil 104 | end val 105 | var y = 106 | x 107 | end y 108 | while y > 0 do 109 | println(y) 110 | y -= 1 111 | end while 112 | try 113 | x match 114 | case 0 => println("0") 115 | case _ => 116 | end match 117 | finally 118 | println("done") 119 | end try 120 | end if 121 | end this 122 | 123 | def f: String 124 | end C 125 | 126 | object C: 127 | given C = 128 | new C: 129 | def f = "!" 130 | end f 131 | end new 132 | end given 133 | end C 134 | 135 | extension (x: C) 136 | def ff: String = x.f ++ x.f 137 | end extension 138 | 139 | end p2 140 | 141 | // Example 142 | 143 | enum IndentWidth: 144 | case Run(ch: Char, n: Int) 145 | case Conc(l: IndentWidth, r: Run) 146 | 147 | def <= (that: IndentWidth): Boolean = this match 148 | case Run(ch1, n1) => 149 | that match 150 | case Run(ch2, n2) => n1 <= n2 && (ch1 == ch2 || n1 == 0) 151 | case Conc(l, r) => this <= l 152 | case Conc(l1, r1) => 153 | that match 154 | case Conc(l2, r2) => l1 == l2 && r1 <= r2 155 | case _ => false 156 | 157 | def < (that: IndentWidth): Boolean = 158 | this <= that && !(that <= this) 159 | 160 | override def toString: String = 161 | this match 162 | case Run(ch, n) => 163 | val kind = ch match 164 | case ' ' => "space" 165 | case '\t' => "tab" 166 | case _ => s"'$ch'-character" 167 | val suffix = if n == 1 then "" else "s" 168 | s"$n $kind$suffix" 169 | case Conc(l, r) => 170 | s"$l, $r" 171 | 172 | object IndentWidth: 173 | private inline val MaxCached = 40 174 | 175 | private val spaces = IArray.tabulate(MaxCached + 1)(new Run(' ', _)) 176 | private val tabs = IArray.tabulate(MaxCached + 1)(new Run('\t', _)) 177 | 178 | def Run(ch: Char, n: Int): Run = 179 | if n <= MaxCached && ch == ' ' then 180 | spaces(n) 181 | else if n <= MaxCached && ch == '\t' then 182 | tabs(n) 183 | else 184 | new Run(ch, n) 185 | end Run 186 | 187 | val Zero = Run(' ', 0) 188 | end IndentWidth 189 | -------------------------------------------------------------------------------- /step01/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Scala3SyntaxRewriting.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package scala3SyntaxRewriting 3 | 4 | // scalacOptions を変更すると、 5 | // コンパイラがそれぞれのシンタックスに書き換えてくれます 6 | 7 | object Counter { 8 | enum Protocol { 9 | case Reset 10 | case MoveBy(step: Int) 11 | } 12 | } 13 | 14 | case class Animal(name: String) 15 | 16 | trait Incrementer { 17 | def increment(n: Int): Int 18 | } 19 | 20 | case class State(n: Int, minValue: Int, maxValue: Int) { 21 | def inc: State = 22 | if (n == maxValue) 23 | this 24 | else 25 | this.copy(n = n + 1) 26 | def printAll: Unit = { 27 | println("Printing all") 28 | for { 29 | i <- minValue to maxValue 30 | j <- 0 to n 31 | } println(i + j) 32 | } 33 | } -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DeprecatedNonlocalReturns.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package deprecatedNonlocalReturns 3 | 4 | import scala.util.chaining.* 5 | 6 | // ネストされた匿名関数からのreturn(大域脱出) 7 | @main def no1(): Unit = 8 | def f(xs: List[Int]): Int = 9 | xs.foreach { x => 10 | return x 11 | } 12 | 0 13 | 14 | f(List(1, 2, 3)).tap(println) 15 | 16 | //--- 17 | // try catch してみる 18 | @main def no2(): Unit = 19 | def f(xs: List[Int]): Int = 20 | try 21 | xs.foreach { x => 22 | return x 23 | } 24 | catch case e => e.getClass.tap(println) // scala.runtime.NonLocalReturnControl 25 | 0 26 | 27 | f(List(1, 2, 3)).tap(println) 28 | 29 | //--- 30 | // Scala 3 では、代替である scala.util.control.NonLocalReturns を使用する 31 | 32 | import scala.util.control.NonLocalReturns.* 33 | 34 | extension [T](xs: List[T]) 35 | def has(elem: T): Boolean = returning { 36 | for x <- xs do 37 | if x == elem then throwReturn(true) 38 | false 39 | } 40 | 41 | @main def no3(): Unit = 42 | val xs = List(1, 2, 3, 4, 5) 43 | assert(xs.has(2) == xs.contains(2)) -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedAutoApplication.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedAutoApplication 3 | 4 | type T = String 5 | 6 | //--- 7 | // Scala 2 では、nullary method を引数なしで呼び出した場合、暗黙的に () が挿入されていたが、 8 | // Scala 3 では厳密にチェックされるようになった 9 | 10 | // 以下はコンパイルエラー 11 | // def no1_ng(): T = 12 | // def next(): T = ??? // nullary method 13 | // next // is expanded to next() 14 | 15 | // パラメータの syntax は正確に従う必要がある 16 | def no1_ok(): T = 17 | def next(): T = ??? // nullary method 18 | next() 19 | 20 | // 呼び出し側が呼び出し先のパラメータに正確に従っていればOK 21 | def no2(): T = 22 | def next: T = ??? 23 | next 24 | 25 | //--- 26 | // Java の場合は、このルールに対して寛容になってる 27 | def no3(): Unit = 28 | val xs = List(1, 2, 3) 29 | 30 | // これを、 31 | xs.toString().length() 32 | 33 | // こうに書くのは、uniform access principle に準拠しているため、Scala のイディオムとなっている 34 | // 副作用の無いメソッドはまるでフィールドにアクセスしているかのように () を省略できる 35 | xs.toString.length 36 | 37 | // Java で定義されたメソッドは、この区別がつかないため、ルールから除外されている 38 | val sb = new java.lang.StringBuilder() 39 | sb.length() 40 | sb.length 41 | 42 | //--- 43 | // override するときメソッドの引数リストは一致している必要がある 44 | 45 | // これはコンパイルエラー 46 | // class A: 47 | // def next(): Int = ??? 48 | // class B extends A: 49 | // override def next: Int = ??? // overriding error: incompatible type 50 | 51 | // これもコンパイルエラー 52 | // class C: 53 | // def next: Int = ??? 54 | // class D extends C: 55 | // override def next(): Int = ??? 56 | 57 | // 一致していればOK 58 | class E: 59 | def next(): Int = ??? 60 | class F extends E: 61 | override def next(): Int = ??? 62 | 63 | class G: 64 | def next: Int = ??? 65 | class H extends G: 66 | override def next: Int = ??? 67 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedClassShadowing.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedClassShadowing 3 | 4 | //---- 5 | // Scala 3 では、親クラスで定義されているクラスと同じ名前のクラスを子クラスで定義できなくなった 6 | // 以下は、Scala 2 ではコンパイルが通るが、Scala 3 ではコンパイルエラーになる 7 | 8 | class Base { 9 | class Ops { } 10 | } 11 | 12 | class Sub extends Base { 13 | // これはコンパイルエラーになる 14 | // class Ops { } 15 | } 16 | 17 | //---- 18 | // 別の名前を付ける必要がある 19 | 20 | class Sub2 extends Base { 21 | class OpsEx { } 22 | } -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedDoWhile.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedDoWhile 3 | 4 | // do-while は廃止された 5 | def no1(): Unit = 6 | var i = 0 7 | def f(v: Int): Int = v / 10 8 | 9 | // コンパイルエラー 10 | // do { 11 | // i += 1 12 | // } while (f(i) == 0) 13 | 14 | // インデントベースの場合 15 | // do 16 | // i += 1 17 | // while (f(i) == 0) 18 | 19 | // これからはこのように書く 20 | @main def no2(): Unit = 21 | println("-" * 50) 22 | 23 | var i = 0 24 | def f(v: Int): Int = v / 10 25 | 26 | while 27 | i += 1 // 28 | f(i) == 0 // 29 | do () 30 | 31 | println(i) 32 | println("-" * 50) 33 | 34 | // while の条件をブロック内で書けることは、"loop-and-a-half" 問題の解決にもなる 35 | @main def no3(): Unit = 36 | println("-" * 50) 37 | 38 | val iterator = List(1, 2, 3, -1, 2, 3).iterator 39 | while 40 | val x: Int = iterator.next // 41 | x >= 0 // 42 | do print(".") 43 | 44 | println() 45 | println("-" * 50) -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedLimit22.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedLimit22 3 | 4 | import scala.util.chaining.* 5 | 6 | // 22の制限は廃止された! 7 | 8 | @main def no1(): Unit = 9 | println("-" * 50) 10 | 11 | val function23 = ( 12 | x1: Int, x2: Int, x3: Int, x4: Int, x5: Int, 13 | x6: Int, x7: Int, x8: Int, x9: Int, x10: Int, 14 | x11: Int, x12: Int, x13: Int, x14: Int, x15: Int, 15 | x16: Int, x17: Int, x18: Int, x19: Int, x20: Int, 16 | x21: Int, x22: Int, x23: Int) => 17 | x1 + x2 + x3 + x4 + x5 18 | + x6 + x7 + x8 + x9 + x10 19 | + x11 + x12 + x13 + x14 + x15 20 | + x16 + x17 + x18 + x19 + x20 21 | + x21 + x22 + x23 22 | 23 | function23(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23) 24 | .tap(println) 25 | 26 | println("-" * 50) 27 | 28 | @main def no2(): Unit = 29 | println("-" * 50) 30 | 31 | val tuple = 32 | (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 33 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 34 | 21, 22.0, "23") 35 | println(tuple) 36 | 37 | // 型は維持されている 38 | val (t1: Int, t2: Int, t3: Int, t4: Int, t5: Int, 39 | t6: Int, t7: Int, t8: Int, t9: Int, t10: Int, 40 | t11: Int, t12: Int, t13: Int, t14: Int, t15: Int, 41 | t16: Int, t17: Int, t18: Int, t19: Int, t20: Int, 42 | t21: Int, t22: Double, t23: String) = tuple 43 | 44 | // 単一の要素はAny 45 | val a: Any = tuple.productElement(0) 46 | val b: Any = tuple.productElement(22) 47 | 48 | // これはできない 49 | // tuple._1 50 | // tuple._23 51 | 52 | // 23以上はTupleXXL 53 | val tupleB = scala.runtime.TupleXXL( 54 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 55 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 56 | 21, 22.0, "23") 57 | 58 | assert(tuple == tupleB) 59 | 60 | println("-" * 50) 61 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedPackageObjects.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedPackageObjects 3 | 4 | // 現時点でまだ Package Objects は使えるが、今後非推奨となり削除される予定 5 | package object p: 6 | val a = ??? 7 | def b = ??? 8 | 9 | //--- 10 | // あらゆる種類の定義がトップレベルで書けるようになったので、Package Objects は不要になった 11 | type Labelled[T] = (String, T) 12 | val a: Labelled[Int] = ("count", 1) 13 | def b = a._2 14 | 15 | case class C() 16 | 17 | extension (x: C) def pair(y: C) = (x, y) 18 | 19 | //--- 20 | package p2: 21 | case class D() 22 | extension (x: D) def pair(y: D) = (x, y) 23 | 24 | package p3: 25 | import p2.* 26 | def droppedPackageObjects: (D, D) = 27 | D().pair(D()) 28 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedProcedureSyntax.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedProcedureSyntax 3 | 4 | // Procedure Syntax はコンパイルエラー 5 | // def f1() { 6 | 7 | // } 8 | 9 | // = を必ず書く 10 | def f2() = { 11 | 12 | } 13 | 14 | def f3(): Unit = { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedSymbolLiterals.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedSymbolLiterals 3 | 4 | // シンボルリテラルは既に削除されているので、コンパイルエラーになる 5 | // val no1 = 'xyz 6 | 7 | // scala.Symbol は将来的に非推奨となり削除される予定 8 | val no2 = Symbol("xyz") 9 | 10 | // 代わりにプレーンな String を使用する 11 | val no3 = "xyz" 12 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedWeakConformance.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedWeakConformance 3 | 4 | // weak conformanceの主な動機は、 5 | // 以下のような式を List[Double] に型付けすることだった 6 | val no1 = List(1.0, math.sqrt(3.0), 0, -3.3) // : List[Double] 7 | 8 | // しかし、Scala 2 では weak conformance の対象に Char も含まれていたため、 9 | // 以下も List[Double] として型付けされていた 10 | // これは、意図した使い方ではなかった 11 | def no2: List[Double] = 12 | val n: Int = 3 13 | val c: Char = 'X' // Char が Double に変換されていた 14 | val d: Double = math.sqrt(3.0) 15 | List(n, c, d) // Scala 2 では、List[Double] 16 | 17 | // そこで、Scala 3 では weak conformance を Int リテラルに限定した 18 | // その結果、以下は Int、Char、Double の least upper bound である AnyVal に変換されるので、 19 | // List[AnyVal] として型付けされる 20 | def no3: List[AnyVal] = 21 | val n: Int = 3 22 | val c: Char = 'X' 23 | val d: Double = math.sqrt(3.0) 24 | List(n, c, d) // used to be: List[Double], now: List[AnyVal] 25 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/DroppedWildcardInitializer.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package droppedWildcardInitializer 3 | 4 | // Scala 2 では、初期化されていないフィールドを示すために _ が使われていたが、 5 | // Scala 3の将来のバージョンで廃止される予定 6 | @main def no1(): Unit = 7 | println("-" * 50) 8 | 9 | class A: 10 | var x: String = _ 11 | 12 | def initialize(): Unit = 13 | x = "abc" 14 | 15 | val a = new A 16 | println(a.x) // null 17 | 18 | a.initialize() 19 | println(a.x) // abc 20 | 21 | println("-" * 50) 22 | 23 | // Scala 3 では uninitialized を使用する 24 | @main def no2(): Unit = 25 | println("-" * 50) 26 | 27 | import scala.compiletime.uninitialized 28 | 29 | class A: 30 | var x: String = uninitialized 31 | 32 | def initialize(): Unit = 33 | x = "abc" 34 | 35 | val a = new A 36 | println(a.x) // null 37 | 38 | a.initialize() 39 | println(a.x) // abc 40 | 41 | println("-" * 50) 42 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ExtensionMethods.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package extensionMethods 3 | 4 | import scala.util.chaining.* 5 | 6 | @main def no1(): Unit = 7 | case class Circle(x: Double, y: Double, radius: Double) 8 | 9 | // Circle に対して extension method を定義 10 | extension (c: Circle) 11 | def circumference: Double = c.radius * math.Pi * 2 12 | 13 | val circle = Circle(0, 0, 1) 14 | 15 | // 通常のメソッドと同じ様に infix `.` で呼び出すことができる 16 | circle.circumference 17 | .tap(println) 18 | 19 | // コンパイラは内部的にこのように解釈するので、 20 | // def circumference(c: Circle): Double = c.radius * math.Pi * 2 21 | // 22 | // circumference(circle) でも呼び出すことができる 23 | assert(circle.circumference == circumference(circle)) 24 | 25 | //--- 26 | // Operators 27 | 28 | def no2(): Unit = 29 | case class Elem(v: Int) 30 | 31 | extension (x: String) 32 | def < (y: String): Boolean = ??? 33 | extension (x: Elem) 34 | def +: (xs: Seq[Elem]): Seq[Elem] = ??? 35 | extension (x: Number) 36 | infix def min (y: Number): Number = ??? 37 | 38 | /* 39 | これは、コンパイラは内部的にこのように解釈する 40 | def < (x: String)(y: String): Boolean = ... 41 | def +: (xs: Seq[Elem])(x: Elem): Seq[Elem] = ... 42 | infix def min(x: Number)(y: Number): Number = ... 43 | 44 | `+:` は右結合演算子のため、順序が逆になっていることに注意 45 | `x +: xs` は、infix `.` で書くと `xs.+:(x)` となる 46 | */ 47 | 48 | "ab" < "c" 49 | Elem(1) +: List(Elem(2), Elem(3)) 50 | 2 min 3 51 | 52 | // こう書くこともできる 53 | <("ab")("c") 54 | +:(List(Elem(2), Elem(3)))(Elem(1)) 55 | min(2)(3) 56 | 57 | // Elem(1) +: List(Elem(2), Elem(3)) は、infix . で呼び出すと順序が逆になる 58 | List(Elem(2), Elem(3)).+:(Elem(1)) 59 | 60 | //---- 61 | // Generic Extensions 62 | // (こちらの内容は、Contextual Abstractions の内容を含むので、今はさらっと見る程度でOK) 63 | 64 | def no3(): Unit = 65 | 66 | // extension methods に型パラメータを指定する場合 67 | extension [T](xs: List[T]) 68 | def second = xs.tail.head 69 | 70 | // context bound で型クラスを指定する場合 71 | extension [T: Numeric](x: T) 72 | def + (y: T): T = summon[Numeric[T]].plus(x, y) // summon は Scala 2 の implicitly に相当し、型クラスのインスタンスをsummon(召喚)している 73 | 74 | // extension methods の型パラメータは、メソッドの型パラメータと組み合わせて定義できる 75 | extension [T](xs: List[T]) 76 | def sumBy[U: Numeric](f: T => U): U = ??? 77 | 78 | // sumBy を呼び出すときに、メソッドの型パラメータ `[U: Numeric]` を指定する 79 | // (Int は Numeric 型クラスのインスタンス) 80 | List("a", "bb", "ccc").sumBy[Int](_.length) 81 | // List("a", "bb", "ccc").sumBy(_.length) // (この場合、型推論に任せて省略できる) 82 | 83 | // sumBy を呼び出すときに、extension の型パラメータ `[T]` を指定する 84 | // extension の型パラメータは、extension としてではなく、通常のメソッド呼び出しとして参照している場合のみ指定できる 85 | sumBy[String](List("a", "bb", "ccc"))(_.length) 86 | 87 | // 両方の型パラメータを指定する場合 88 | sumBy[String](List("a", "bb", "ccc"))[Int](_.length) 89 | 90 | // extension は、using 句を使用することもできる 91 | // (using 句は、Scala 2 の implicit parameter に相当する) 92 | extension [T](x: T)(using n: Numeric[T]) 93 | def + (y: T): T = n.plus(x, y) 94 | 95 | //--- 96 | // Collective Extensions 97 | 98 | // extension のパラメータに対して、複数の extension method(collective extensions)を定義できる 99 | def no4_collective(): Unit = 100 | // extension のパラメータは、この場合 `ss: Seq[String]` 101 | extension (ss: Seq[String]) 102 | def longestStrings: Seq[String] = 103 | val maxLength = ss.map(_.length).max 104 | ss.filter(_.length == maxLength) 105 | 106 | // 本来は `ss.longestStrings.head` とすべきだが、 107 | // 同一の extension ブロック内では、extension method を直接呼び出すことができる 108 | def longestString: String = longestStrings.head 109 | 110 | // 上記の collective extensions は、個別に書くとこのようになる 111 | def no4_expand(): Unit = 112 | extension (ss: Seq[String]) 113 | def longestStrings: Seq[String] = 114 | val maxLength = ss.map(_.length).max 115 | ss.filter(_.length == maxLength) 116 | 117 | extension (ss: Seq[String]) 118 | def longestString: String = ss.longestStrings.head 119 | 120 | // ちなみに longestString を先に定義するとコンパイルエラー 121 | def no4_expand_2(): Unit = 122 | // コンパイルエラー 123 | // extension (ss: Seq[String]) 124 | // def longestString: String = ss.longestStrings.head 125 | 126 | extension (ss: Seq[String]) 127 | def longestStrings: Seq[String] = 128 | val maxLength = ss.map(_.length).max 129 | ss.filter(_.length == maxLength) 130 | 131 | // Collective extensions は、型パラメータを取り、using 句を持つこともできる 132 | def no5(): Unit = 133 | import math.Ordering.Implicits.given 134 | extension [T](xs: List[T])(using Ordering[T]) 135 | def smallest(n: Int): List[T] = xs.sorted.take(n) 136 | def smallestIndices(n: Int): List[Int] = 137 | val limit = smallest(n).max 138 | xs.zipWithIndex.collect { case (x, i) if (x <= limit) => i } 139 | 140 | //-- 141 | // Translation of Calls to Extension Methods 142 | 143 | /* 144 | extension methodsを参照可能にするためには4つの方法がある 145 | 146 | 1. スコープ内で定義、またはそれを継承、インポートしている 147 | 2. 参照先のスコープ内で型クラスのインスタンスが定義されている 148 | 3. extension method `m` を `r.m` で参照する場合、`r` の暗黙のスコープで定義されている 149 | 4. extension method `m` を `r.m` で参照する場合、型クラスのインスタンスが `r` の暗黙のスコープで定義されている 150 | */ 151 | 152 | // 1. スコープ内で定義、またはそれを継承、インポートしている 153 | trait IntOps: 154 | extension (i: Int) def isZero: Boolean = i == 0 155 | 156 | // isZero は、同一スコープ内(IntOps 内)で定義されているので参照可能 157 | extension (i: Int) def safeMod(x: Int): Option[Int] = 158 | if x.isZero then None 159 | else Some(i % x) 160 | 161 | object IntOpsEx extends IntOps: 162 | // isZero は、IntOps を継承しているので参照可能 163 | extension (i: Int) def safeDiv(x: Int): Option[Int] = 164 | if x.isZero then None 165 | else Some(i / x) 166 | 167 | trait SafeDiv: 168 | import IntOpsEx.* 169 | 170 | // safeDiv は、IntOpsEx をインポートしてるので参照可能 171 | extension (i: Int) def divide(d: Int): Option[(Int, Int)] = 172 | (i.safeDiv(d), i.safeMod(d)) match 173 | case (Some(d), Some(r)) => Some((d, r)) 174 | case _ => None 175 | 176 | // 2. 参照先のスコープ内で型クラスのインスタンスが定義されている 177 | def no6_2(): Unit = 178 | // 型クラスのインスタンスを定義している 179 | given IntOps() 180 | // given ops1: IntOps() 181 | 182 | 1.safeMod(2) 183 | 184 | // 3. extension method `m` を `r.m` で参照する場合、`r` の暗黙のスコープで定義されている 185 | // 4. extension method `m` を `r.m` で参照する場合、型クラスのインスタンスが `r` の暗黙のスコープで定義されている 186 | def no6_3_4(): Unit = 187 | 188 | // 例えば、標準ライブラリの List のコンパニオンオブジェクトでは、 189 | // extension method として、flatten が、 190 | // それと、Ordering 型クラスのインスタンスが定義されている 191 | 192 | /* 193 | class List[T] 194 | 195 | object List: 196 | extension [T](xs: List[List[T]]) 197 | def flatten: List[T] = xs.foldLeft(List.empty[T])(_ ++ _) 198 | 199 | given [T: Ordering]: Ordering[List[T]] with 200 | extension (xs: List[T]) 201 | def < (ys: List[T]): Boolean = ??? 202 | */ 203 | 204 | // コンパニオンオブジェクトは、暗黙のスコープに入っていてクラスから参照ができるので、 205 | 206 | // exntension method の flatten を利用できる 207 | List(List(1, 2), List(3, 4)).flatten 208 | 209 | // Ordering 型クラスのインスタンスも参照できるので、`<` も利用できる 210 | import math.Ordering.Implicits.given 211 | List(1, 2) < List(3) 212 | 213 | //--- 214 | // 再帰的な呼び出しの場合 215 | 216 | def no7_extension(): Unit = 217 | extension (s: String) 218 | def position(ch: Char, n: Int): Int = 219 | if n < s.length && s(n) != ch then position(ch, n + 1) 220 | else n 221 | 222 | // コンパイラは内部的に通常のメソッド呼び出しとして解釈する 223 | def no7_rewrite(): Unit = 224 | def position(s: String)(ch: Char, n: Int): Int = 225 | if n < s.length && s(n) != ch then position(s)(ch, n + 1) 226 | else n 227 | 228 | //--- 229 | // 同一スコープ内で重複した定義ができるっぽいけど、呼び出そうとするとコンパイルエラー 230 | 231 | @main def no8(): Unit = 232 | extension (s: String) 233 | def foo(): Unit = println("a") 234 | 235 | extension (s: String) 236 | def foo(): Unit = println("b") 237 | 238 | // 呼び出そうとするとコンパイルエラー 239 | // "x".foo() // foo is already defined as method foo 240 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/MainMethods.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package mainMethods 3 | 4 | import scala.util.chaining.* 5 | 6 | /* 7 | これで実行する 8 | 9 | > runMain com.github.shinharad.gettingStartedWithScala3.mainMethods.happyBirthday 23 Lisa Peter 10 | 11 | 引数が足りないとエラーになる 12 | 13 | > runMain com.github.shinharad.gettingStartedWithScala3.mainMethods.happyBirthday 22 14 | 15 | 引数の型が違う場合もエラーになる 16 | 17 | > runMain com.github.shinharad.gettingStartedWithScala3.mainMethods.happyBirthday sixty Fred 18 | */ 19 | 20 | @main def happyBirthday(age: Int, name: String, others: String*): Unit = 21 | println("-" * 50) 22 | 23 | val suffix = 24 | age % 100 match 25 | case 11 | 12 | 13 => "th" 26 | case _ => 27 | age % 10 match 28 | case 1 => "st" 29 | case 2 => "nd" 30 | case 3 => "rd" 31 | case _ => "th" 32 | val bldr = new StringBuilder(s"Happy $age$suffix birthday, $name") 33 | for other <- others do bldr.append(" and ").append(other) 34 | bldr.toString.tap(println) 35 | 36 | println("-" * 50) 37 | 38 | /* 39 | 上記のコードは、コンパイラがこんなイメージのコードを作成するらしい 40 | 41 | final class happyBirthday: 42 | import scala.util.CommandLineParser as CLP 43 | def main(args: Array[String]): Unit = 44 | try 45 | happyBirthday( 46 | CLP.parseArgument[Int](args, 0), 47 | CLP.parseArgument[String](args, 1), 48 | CLP.parseRemainingArguments[String](args, 2)) 49 | catch 50 | case error: CLP.ParseError => CLP.showError(error) 51 | */ 52 | 53 | //--- 54 | // object の中で書くこともできる 55 | 56 | object HappyBirth: 57 | @main def happyBirthday2(age: Int, name: String, others: String*): Unit = 58 | happyBirthday(age, name, others: _*) 59 | 60 | //--- 61 | // Scala 2 で使用できた App は、今のところ限定的な形で存在しているが、 62 | // DelayedInit による意図しない挙動や、コマンドライン引数をサポートしていないため、将来的には廃止される予定 63 | // Scala 2 と Scala 3 の間でクロスビルドする必要がある場合は、代わりに def main(args: Array[String]): Unit を使用する 64 | object happyBirthday3 extends App: 65 | println("hello") 66 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/MatchExpressions.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package matchExpressions 3 | 4 | //--- 5 | // マッチ式を連鎖させることができる 6 | // brasesは省略可能 7 | def no1: Int = 8 | val xs: List[Int] = ??? 9 | 10 | xs match { 11 | case Nil => "empty" 12 | case x :: xs1 => "nonempty" 13 | } match { 14 | case "empty" => 0 15 | case "nonempty" => 1 16 | } 17 | 18 | xs match 19 | case Nil => "empty" 20 | case x :: xs1 => "nonempty" 21 | match 22 | case "empty" => 0 23 | case "nonempty" => 1 24 | 25 | //--- 26 | // ピリオドの後に書くことができる 27 | def no2: String = 28 | val xs: List[Int] = ??? 29 | 30 | if xs.match 31 | case Nil => false 32 | case _ => true 33 | then "nonempty" 34 | else "empty" 35 | 36 | //--- 37 | // `x: T match { ... }` はサポートされなくなったので、`(x: T) match { ... }` と書く 38 | def no3: Int = 39 | val x: Int = ??? 40 | 41 | // NG 42 | // x: Int match { case 0 => 1 } 43 | // x: Int match { case 0 => 1 } 44 | 45 | (x: Int) match { case 0 => 1 } 46 | (x: Int) match { case 0 => 1 } 47 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ParameterUntupling.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package parameterUntupling 3 | 4 | def parameterUntupling(): Unit = 5 | val xs: List[(Int, Int)] = ??? 6 | 7 | // 今までは 8 | xs map { 9 | case (x, y) => x + y 10 | } 11 | 12 | // Scala 3 からは 13 | xs map { 14 | (x, y) => x + y 15 | } 16 | 17 | // 省略してこんな感じに書ける 18 | xs map (_ + _) 19 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/RulesForOperators.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package rulesForOperators 3 | 4 | import scala.annotation.targetName 5 | 6 | //--- 7 | // The infix Modifier 8 | 9 | trait MultiSet[T]: 10 | 11 | infix def union(other: MultiSet[T]): MultiSet[T] 12 | 13 | // 記号メソッドにも infix を付けられるが冗長 14 | infix def **(other: MultiSet[T]): MultiSet[T] 15 | 16 | def difference(other: MultiSet[T]): MultiSet[T] 17 | 18 | @targetName("intersection") 19 | def *(other: MultiSet[T]): MultiSet[T] 20 | 21 | def no1: Unit = 22 | val s1, s2: MultiSet[Int] = ??? 23 | 24 | s1 union s2 // OK 25 | s1 `union` s2 // also OK but unusual 26 | s1.union(s2) // also OK 27 | 28 | s1.difference(s2) // OK 29 | s1 `difference` s2 // OK 30 | s1 difference s2 // gives a deprecation warning 31 | 32 | s1 * s2 // OK 33 | s1 `*` s2 // also OK, but unusual 34 | s1.*(s2) // also OK, but unusual 35 | 36 | // typeでも使用できる 37 | def no2: Unit = 38 | infix type or[X, Y] 39 | val x: String or Int = ??? 40 | 41 | // two parameters はコンパイルエラーになるはずが、そうならない... 42 | trait Infix: 43 | 44 | infix def op1(x: Int): Int // ok 45 | infix def op2(x: Int)(y: Int): Int // ok 46 | infix def op3(x: Int, y: Int): Int // error: two parameters 47 | 48 | extension (x: Infix) 49 | infix def op4(y: Int): Int // ok 50 | infix def op5(y1: Int, y2: Int): Int // error: two parameters 51 | 52 | //--- 53 | // Syntax Change 54 | 55 | // infix 演算子を複数行の行頭で書けるようになった 56 | def no3: Unit = 57 | val x: Int = ??? 58 | val xs: List[Int] = ??? 59 | 60 | val str = "hello" 61 | ++ " world" 62 | ++ "!" 63 | 64 | def condition = 65 | x > 0 66 | || 67 | xs.exists(_ > 0) 68 | || xs.isEmpty 69 | 70 | // これまで通り、infix 演算子を行末に書いても一応コンパイルは通る 71 | val str2 = "hello" ++ 72 | " world" ++ 73 | "!" 74 | 75 | def condition2 = 76 | x > 0 || 77 | xs.exists(_ > 0) || 78 | xs.isEmpty 79 | 80 | // 行頭に infix 演算子を書く場合、その後の式の前にスペースを少なくとも1つ入れる必要がある 81 | def no4(): Int = 82 | val freezing = true 83 | val boiling = true 84 | 85 | // `|` の後にスペースがあるので、これは1つのステートメントだけど 86 | freezing 87 | | boiling 88 | 89 | // これは、`!` の後にスペースが無いので、別々のステートメントとして扱われる 90 | // freezing 91 | // !boiling 92 | 93 | // これは3つのステートメントとして扱われる 94 | // `???` は構文的にはシンボリック演算子だけど、 95 | // どちらの出現箇所にもスペースとその後ろの式が無い 96 | println("hello") 97 | ??? 98 | ??? match { case 0 => 1 } 99 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/TraitParameters.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package traitParameters 3 | 4 | import scala.util.chaining.* 5 | 6 | trait Greeting(val name: String): 7 | def msg = s"How are you, $name" 8 | 9 | class C extends Greeting("Bob"): 10 | println(msg) 11 | 12 | @main def no1(): Unit = 13 | new C 14 | 15 | //--- 16 | // 曖昧な定義はコンパイルエラー 17 | 18 | // C は既に Greeting を extends してるので、別の Greeting を extends することはできない 19 | // Bob か Bill か分からん、という曖昧な状態になってしまう 20 | 21 | // class D extends C, Greeting("Bill") // error: parameter passed twice 22 | 23 | //--- 24 | // Greeting の パラメータは未指定のまま extends すると、 25 | 26 | trait FormalGreeting extends Greeting: 27 | override def msg = s"How do you do, $name" 28 | 29 | // 具象化するにしてもパラメータが未解決なので、Greeting を extends しないとダメだよと怒られる 30 | // class E extends FormalGreeting // error: missing arguments for `Greeting`. 31 | 32 | // パラメータを設定した Greeting を extends してあげるとコンパイルが通る 33 | class E extends Greeting("Bob"), FormalGreeting 34 | 35 | @main def no2(): Unit = 36 | (new E).msg 37 | .tap(println) 38 | 39 | // 色々応用できそう 40 | 41 | //--- 42 | // 同一 trait を直接 extends するのはコンパイルエラー 43 | 44 | // class F extends Greeting, Greeting: 45 | // override val name: String = "Bob" 46 | // 47 | // class F extends Greeting, Greeting("Bob") 48 | 49 | //--- 50 | // 同一メンバーが存在する場合はコンパイルエラー 51 | 52 | // trait Greeting2(val name: String): 53 | // def msg = s"How are you, $name" 54 | 55 | // class G extends Greeting("Bob"), Greeting2("Bill") 56 | 57 | //--- 58 | // メンバーが重複していなければOK 59 | 60 | trait Greeting3(val namename: String): 61 | def msgmsg = s"How are you, $namename" 62 | 63 | class H extends Greeting("Bob"), Greeting3("Bill") 64 | 65 | @main def no3(): Unit = 66 | (new H) 67 | .tap(x => println(x.msgmsg)) 68 | .tap(x => println(x.msg)) 69 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/TraitParameters2.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package traitParametersPart2 3 | 4 | import scala.util.chaining.* 5 | 6 | //--- 7 | // 番外編 8 | 9 | trait Greeting(val name: String): 10 | def msg = s"How are you, $name" 11 | 12 | trait FormalGreeting extends Greeting: 13 | override def msg = s"How do you do, $name" 14 | 15 | // コンパイルエラー 16 | // class A extends FormalGreeting 17 | 18 | class B extends Greeting("Bob"), FormalGreeting 19 | class C extends FormalGreeting, Greeting("Bob") 20 | 21 | // コンパイルエラー 22 | // trait FormalGreeting2(val name: String) extends Greeting: 23 | // def msg = s"How do you do, $name" 24 | 25 | trait FormalGreeting3(override val name: String) extends Greeting: 26 | override def msg = s"How do you do, $name" 27 | 28 | class D extends FormalGreeting3("Bill"), Greeting("Bob") 29 | class E extends Greeting("Bob"), FormalGreeting3("Bill") 30 | 31 | // コンパイルエラー 32 | // class F extends FormalGreeting3("Bill") 33 | // class G extends FormalGreeting3, Greeting("Bob") 34 | 35 | // コンパイルエラー 36 | // trait FormalGreeting4(override val name: String) extends Greeting(name): 37 | // override def msg = s"How do you do, $name" 38 | 39 | @main def no1(): Unit = 40 | println("-" * 50) 41 | 42 | (new B).msg.tap(println) 43 | (new C).msg.tap(println) 44 | // => How do you do, Bob 45 | 46 | println("-" * 10) 47 | 48 | (new D).msg.tap(println) 49 | (new E).msg.tap(println) 50 | // => How do you do, Bill 51 | 52 | println("-" * 50) 53 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UniversalApplyMethods.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package universalApplyMethods 3 | 4 | //--- 5 | // インスタンス生成で new が不要になった 6 | 7 | class StringBuilder1(s: String): 8 | def this() = this("") 9 | 10 | def no1(): Unit = 11 | 12 | // 今まではインスタンスの生成に new を書いていたが 13 | val sb1 = new StringBuilder1("abc") 14 | val sb2 = new StringBuilder1() 15 | 16 | // Scala 3 では、new が不要になった 17 | val sb3 = StringBuilder1("abc") 18 | val sb4 = StringBuilder1() 19 | 20 | /* 21 | これは、コンパニオンオブジェクトにこういうイメージの apply メソッドが自動的に追加されるため 22 | constructor proxies と呼ばれてる 23 | object StringBuilder1: 24 | inline def apply(s: String): StringBuilder = new StringBuilder(s) 25 | inline def apply(): StringBuilder = new StringBuilder() 26 | */ 27 | 28 | //--- 29 | // Javaのクラスも new が不要になった 30 | 31 | def no2(): Unit = 32 | val list1 = java.util.ArrayList[String]() 33 | val map1 = java.util.HashMap[String, String]() 34 | 35 | // Scala 2 ではこうだった 36 | val list2 = new java.util.ArrayList[String]() 37 | val map2 = new java.util.HashMap[String, String]() 38 | 39 | //--- 40 | // 既にコンパニオンオブジェクトに apply が実装されていた場合は、自動追加されない 41 | 42 | class C(name: String) 43 | object C 44 | 45 | class D(name: String) 46 | object D: 47 | def apply(name: String, age: Int): D = new D(name) 48 | 49 | def no3(): Unit = 50 | 51 | // コンパニオンオブジェクトが存在していても apply は自動的に追加される 52 | val c = C("abc") 53 | 54 | // コンパニオンオブジェクトに apply が実装されていたら自動追加はされない 55 | 56 | // 以下はコンパイルエラー 57 | // val d1 = D("abc") 58 | 59 | val d2 = new D("abc") 60 | -------------------------------------------------------------------------------- /step02/src/main/scala/com/github/shinharad/gettingStartedWithScala3/VarargSplices.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package varargSplices 3 | 4 | @main def no1(): Unit = 5 | val arr = Array(0, 1, 2, 3) 6 | val lst = List(arr*) // vararg splice argument 7 | lst match 8 | case List(0, 1, xs*) => println(xs) // binds xs to Seq(2, 3) 9 | case List(1, _*) => // wildcard pattern 10 | case _ => 11 | 12 | // 今までの書き方は段階的に廃止される予定 13 | @main def no2(): Unit = 14 | val arr = Array(0, 1, 2, 3) 15 | val lst = List(arr: _*) 16 | 17 | lst match 18 | case List(0, 1, xs @ _*) => println(xs) // binds xs to Seq(2, 3) 19 | case _ => 20 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/AlgebraicDataTypes.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package algebraicDataTypes 3 | 4 | // Enums を使用して、Algebraic Data Types(ADTs、代数的データ型)を表現できる 5 | 6 | //--- 7 | // Option 型を Enums で表現する 8 | 9 | enum Option[+T]: 10 | case Some(x: T) 11 | case None 12 | 13 | //--- 14 | // enum の型パラメータの変位指定が covariant(共変)の場合は、 15 | // コンパイラの型推論により、列挙した型の extends を省略できる 16 | 17 | def no2: Unit = 18 | 19 | // 省略した場合 20 | enum Option[+T]: 21 | case Some(x: T) 22 | case None 23 | 24 | // 明示的に書いた場合 25 | enum OptionV2[+T]: 26 | case Some(x: T) extends OptionV2[T] 27 | case None extends OptionV2[Nothing] 28 | 29 | // なお、型パラメータの無い None は、Option[Nothing] と推論されていることに注意 30 | // (Nothing はすべての型のサブタイプであり、ボトム型と呼ばれている) 31 | 32 | //--- 33 | // covariant(共変)以外の場合は、すべて明示的に書く必要がある 34 | // 省略するとコンパイルエラー 35 | 36 | def no3: Unit = 37 | 38 | // contravariant (反変) 39 | enum View[-T]: 40 | case Refl[R](f: R => R) extends View[R] 41 | 42 | // invariant (非変) 43 | enum Option[T]: 44 | case Some(x: T) extends Option[T] 45 | case None extends Option[Nothing] 46 | 47 | //--- 48 | // Enums で列挙した型は、Enums のコンパニオンオブジェクトで定義される 49 | 50 | def no4: Unit = 51 | 52 | // つまり、このように使える 53 | Option.Some("hello") 54 | Option.None 55 | 56 | // 列挙した型は内部的にクラスになるので、new で生成することも一応できる 57 | val o1: Option.Some[Int] = new Option.Some(2) 58 | 59 | //--- 60 | // Enums と同様、ADTs は独自のメソッドを定義することができる 61 | 62 | def no5: Unit = 63 | enum Option[+T]: 64 | case Some(x: T) 65 | case None 66 | 67 | def isDefined: Boolean = this match 68 | case None => false 69 | case _ => true 70 | 71 | object Option: 72 | def apply[T >: Null](x: T): Option[T] = 73 | if x == null then None else Some(x) 74 | 75 | Option(123).isDefined 76 | 77 | //--- 78 | // ADTs は、Enums と混在させることができる 79 | 80 | // 以下は、3つの Enums (Red, Green, Blue) を持つか、 81 | // またはパラメータ化された case を持つ Color か 82 | enum Color(val rgb: Int): 83 | case Red extends Color(0xFF0000) 84 | case Green extends Color(0x00FF00) 85 | case Blue extends Color(0x0000FF) 86 | case Mix(mix: Int) extends Color(mix) 87 | 88 | //--- 89 | // ADTs の case の型パラメータは、親である enum の変位指定を引き継ぐ 90 | 91 | def no6_ng: Unit = 92 | 93 | // つまりこれはコンパイルエラー 94 | // enum View[-T]: 95 | // case Refl(f: T => T) extends View[T] 96 | 97 | // - Refl の T は、View で指定している contravariant (反変) を引き継いでいる 98 | // - 関数 f の結果型には、contravariant (反変) を指定できない 99 | 100 | // 上記のコードは、コンパイラにはこのように見えている 101 | // enum View[-T]: 102 | // case Refl[-T1](f: T1 => T1) extends View[T1] 103 | 104 | () 105 | 106 | // Refl の f を正しく型付けするには、Refl 自身で non-variant 型パラメータを宣言する必要がある 107 | def no6_ok: Unit = 108 | enum View[-T]: 109 | case Refl[R](f: R => R) extends View[R] 110 | 111 | // さらにいくつかの変更を加えた後、View のより完全な実装は以下のようになり、 112 | // 関数型 T => U として使用することができる 113 | 114 | def no6_ok2: Unit = 115 | enum View[-T, +U] extends (T => U): 116 | case Refl[R](f: R => R) extends View[R, R] 117 | 118 | final def apply(t: T): U = this match 119 | case refl: Refl[r] => refl.f(t) 120 | 121 | View.Refl((x: Int) => x + 1) 122 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Enumerations.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package enumerations 3 | 4 | import scala.util.Try 5 | import scala.util.chaining.* 6 | 7 | // Scala 2 まではこんな感じで定義していたけど、正直使いづらかった 8 | object ColorOld extends Enumeration { 9 | val Red, Green, Blue = Value 10 | } 11 | 12 | //--- 13 | // Scala 3 の列挙型 14 | 15 | // 上記のコードは、Scala 3 ではこのように書けるようになった 16 | enum Color: 17 | case Red, Green, Blue 18 | 19 | // パラメータを持つこともできる 20 | enum ColorWithParameter(val rgb: Int): 21 | case Red extends ColorWithParameter(0xFF0000) 22 | case Green extends ColorWithParameter(0x00FF00) 23 | case Blue extends ColorWithParameter(0x0000FF) 24 | 25 | // 使う場合はこうにする 26 | @main def no1(): Unit = 27 | println("-" * 50) 28 | 29 | val red: Color = Color.Red 30 | .tap(println) 31 | 32 | // パターンマッチで使う 33 | red match 34 | case Color.Red => 35 | case Color.Green => 36 | case Color.Blue => 37 | 38 | // 網羅できていない場合はコンパイラが警告メッセージを表示 39 | // red match 40 | // case Color.Red => 41 | // case Color.Green => 42 | 43 | /* 44 | [warn] 39 | red match 45 | [warn] | ^^^ 46 | [warn] | match may not be exhaustive. 47 | [warn] | 48 | [warn] | It would fail on pattern case: Blue 49 | */ 50 | 51 | println("-" * 50) 52 | 53 | //--- 54 | // 列挙した型は一意の Int に対応していて、ordinal で取得ができる 55 | @main def no2(): Unit = 56 | println("-" * 50) 57 | 58 | Color.Red.ordinal 59 | .tap(println) // 0 60 | Color.Green.ordinal 61 | .tap(println) // 1 62 | Color.Blue.ordinal 63 | .tap(println) // 2 64 | 65 | println("-" * 50) 66 | 67 | //--- 68 | // コンパニオンオブジェクトのユーティリティメソッド 69 | @main def no3(): Unit = 70 | println("-" * 50) 71 | 72 | println("valueOf:") 73 | val blue: Color = Color.valueOf("Blue") 74 | .tap(println) 75 | 76 | // 未定義の値を渡した場合は、java.lang.IllegalArgumentException 77 | Try(Color.valueOf("Yellow")).tap(println) 78 | println("-" * 20) 79 | 80 | println("values:") 81 | val values: Array[Color] = Color.values 82 | .tap(_.foreach(println)) 83 | println("-" * 20) 84 | 85 | println("fromOrdinal:") 86 | val color: Color = Color.fromOrdinal(0) 87 | .tap(println) 88 | 89 | // 未定義の値を渡した場合は、java.util.NoSuchElementException 90 | Try(Color.fromOrdinal(4)).tap(println) 91 | 92 | println("-" * 50) 93 | 94 | //--- 95 | // Enums には独自のメソッドを定義できる 96 | 97 | @main def no4(): Unit = 98 | enum Planet(mass: Double, radius: Double): 99 | private final val G = 6.67300E-11 100 | def surfaceGravity = G * mass / (radius * radius) 101 | def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity 102 | 103 | case Mercury extends Planet(3.303e+23, 2.4397e6) 104 | case Venus extends Planet(4.869e+24, 6.0518e6) 105 | case Earth extends Planet(5.976e+24, 6.37814e6) 106 | case Mars extends Planet(6.421e+23, 3.3972e6) 107 | case Jupiter extends Planet(1.9e+27, 7.1492e7) 108 | case Saturn extends Planet(5.688e+26, 6.0268e7) 109 | case Uranus extends Planet(8.686e+25, 2.5559e7) 110 | case Neptune extends Planet(1.024e+26, 2.4746e7) 111 | 112 | object Planet: 113 | def reportWeight(earthWeight: Double): Unit = 114 | val mass = earthWeight / Earth.surfaceGravity 115 | for p <- values do 116 | println(s"Your weight on $p is ${p.surfaceWeight(mass)}") 117 | 118 | // 呼び出す 119 | Planet.reportWeight(60) 120 | 121 | //--- 122 | // Enums の一部を deprecated にした場合、 123 | // スコープの外で使用すると警告メッセージを表示するが、 124 | // スコープの中では警告メッセージを表示しない 125 | // (deprecated にしてから削除するまでの移行期間として便利) 126 | 127 | @main def no5(): Unit = 128 | enum Planet(mass: Double, radius: Double): 129 | private final val G = 6.67300E-11 130 | def surfaceGravity = G * mass / (radius * radius) 131 | def surfaceWeight(otherMass: Double) = otherMass * surfaceGravity 132 | 133 | case Mercury extends Planet(3.303e+23, 2.4397e6) 134 | case Venus extends Planet(4.869e+24, 6.0518e6) 135 | case Earth extends Planet(5.976e+24, 6.37814e6) 136 | case Mars extends Planet(6.421e+23, 3.3972e6) 137 | case Jupiter extends Planet(1.9e+27, 7.1492e7) 138 | case Saturn extends Planet(5.688e+26, 6.0268e7) 139 | case Uranus extends Planet(8.686e+25, 2.5559e7) 140 | case Neptune extends Planet(1.024e+26, 2.4746e7) 141 | 142 | @deprecated("refer to IAU definition of planet") 143 | case Pluto extends Planet(1.309e+22, 1.1883e3) 144 | 145 | trait Deprecations[T <: reflect.Enum]: 146 | extension (t: T) def isDeprecatedCase: Boolean 147 | 148 | object Planet: 149 | // これは型クラスのインスタンスの定義だけど、今後取り上げるので現段階ではさらっと見る程度で 150 | given Deprecations[Planet] with 151 | extension (p: Planet) 152 | // deprecated な Pluto はスコープ外では警告メッセージを表示するが、 153 | // スコープの中では警告メッセージを表示しない 154 | def isDeprecatedCase = p == Pluto 155 | 156 | // スコープの外では警告メッセージを表示する 157 | // Planet.Pluto.isDeprecatedCase 158 | // .tap(x => println(s"Pluto: $x")) 159 | 160 | //--- 161 | // Scala で定義した Enums は、 `java.lang.Enum` を継承することで、 162 | // Java の Enums として使用することができる 163 | 164 | enum JColor extends java.lang.Enum[JColor] { case Red, Green, Blue } 165 | 166 | @main def no6(): Unit = 167 | println("-" * 50) 168 | 169 | JColor.Red.compareTo(JColor.Green) 170 | .tap(println) 171 | .tap(r => assert(r == -1)) 172 | 173 | println("-" * 50) 174 | 175 | //--- 176 | // Enums は継承できない 177 | // enum Child extends Color: 178 | // case Yellow 179 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/IntersectionTypes.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package intersectionTypes 3 | 4 | trait Resettable: 5 | def reset(): Unit 6 | 7 | trait Growable[T]: 8 | def add(t: T): Unit 9 | 10 | //--- 11 | // Intersection Types をメソッドの引数に使用する 12 | 13 | // 引数 x は、Resettable であり、Growable[String] でもある型 14 | def f(x: Resettable & Growable[String]) = 15 | x.reset() 16 | x.add("first") 17 | 18 | @main def no1(): Unit = 19 | 20 | class A extends Resettable, Growable[String]: 21 | override def reset(): Unit = println("reset") 22 | override def add(t: String): Unit = println(s"add: $t") 23 | 24 | f(new A) 25 | 26 | // & 演算子は可換的なので、逆にしてもOK 27 | class B extends Growable[String], Resettable: 28 | override def reset(): Unit = println("reset") 29 | override def add(t: String): Unit = println(s"add: $t") 30 | 31 | f(new B) 32 | 33 | // メソッドの引数の型を逆にしてもOK 34 | def g(x: Growable[String] & Resettable) = 35 | x.reset() 36 | x.add("first") 37 | 38 | g(new A) 39 | g(new B) 40 | 41 | // 両方を満たしていない場合はコンパイルエラー 42 | // class C extends Resettable: 43 | // override def reset(): Unit = println("reset") 44 | 45 | // f(new C) 46 | 47 | // これもエラー 48 | // class D extends Growable[String]: 49 | // override def add(t: String): Unit = println(s"add: $t") 50 | 51 | // f(new D) 52 | 53 | // インスタンスを生成するときにミックスインした場合 54 | def no2(): Unit = 55 | 56 | class C extends Growable[String]: 57 | override def add(t: String): Unit = println(s"add: $t") 58 | 59 | val c = new C with Resettable: 60 | override def reset(): Unit = println("reset") 61 | 62 | f(c) 63 | 64 | //--- 65 | // A と B に同一のメンバが存在する場合、 66 | // A と B を継承したメンバの型は、それぞれのメンバの型の Intersection Types になる 67 | 68 | def no3(): Unit = 69 | trait X 70 | trait Y 71 | 72 | trait A: 73 | def foo: X 74 | 75 | trait B: 76 | def foo: Y 77 | 78 | class C extends A, B: 79 | override def foo: X & Y = new X with Y {} 80 | 81 | val c: A & B = new C 82 | val r: X & Y = c.foo 83 | 84 | // List の場合は、List[A] & List[B] となるが、 85 | // List は、covariant(共変)なので、これらをさらに単純化して 86 | // List[A & B] とすることができる 87 | 88 | def no4(): Unit = 89 | trait A: 90 | def children: List[A] 91 | 92 | trait B: 93 | def children: List[B] 94 | 95 | class C extends A, B: 96 | override def children: List[A & B] = ??? 97 | 98 | val x: A & B = new C 99 | val ys: List[A & B] = x.children 100 | 101 | //--- 102 | // 3種類以上の型でも Intersection Types を作れる 103 | 104 | def no5(): Unit = 105 | trait A 106 | trait B 107 | trait C 108 | 109 | def f(x: A & B & C): Unit = ??? 110 | 111 | class D extends A, B, C 112 | f(new D) 113 | 114 | //--- 115 | // Scala 2 では、with を使って Compound Types (複合型)で表現していた 116 | // ただし、Compound Types は、Intersection Types とは異なり、 117 | // 同一メンバで異なる型が存在する場合は、Linearization のルールでどちらかの型に推論されてしまう 118 | 119 | def no6(): Unit = 120 | trait X 121 | trait Y 122 | 123 | //--- 124 | // 以下は Scala 2 での話 125 | 126 | trait A { 127 | def foo: X 128 | } 129 | trait B { 130 | def foo: Y 131 | } 132 | 133 | def f(x: A with B): Unit = { 134 | // Linearization のルールで Y 型になる(X with Y とはならない) 135 | // (型アノテーションで無理矢理 Y にしている。Scala 3の場合は X でもコンパイルが通る) 136 | val a: Y = x.foo 137 | } 138 | 139 | def g(x: B with A): Unit = { 140 | // Linearization のルールで X 型になる(Y with X とはならない) 141 | // (型アノテーションで無理矢理 X にしている。Scala 3の場合は Y でもコンパイルが通る) 142 | val a: X = x.foo 143 | } 144 | 145 | //--- 146 | // ここから Scala 3 の話 147 | 148 | // Scala 3 では with を & に置き換えるので、 149 | // この場合は Intersection Types になる 150 | 151 | trait C: 152 | def foo: X 153 | trait D: 154 | def foo: Y 155 | 156 | def ff(x: C with D): Unit = 157 | val a: X with Y = x.foo 158 | val b: Y with X = x.foo // 可換的なのでこれもOK 159 | 160 | val c: X & Y = x.foo 161 | val d: Y & X = x.foo 162 | 163 | def gg(x: D with C): Unit = 164 | val a: Y with X = x.foo 165 | val b: X with Y = x.foo // 可換的なのでこれもOK 166 | 167 | val c: Y & X = x.foo 168 | val d: X & Y = x.foo 169 | 170 | //--- 171 | // Stackable Modifications としての with は今まで通り Linearization のルールに従う 172 | 173 | @main def no7(): Unit = 174 | class Animal: 175 | def action(ball: String) = println("Action : " + ball) 176 | 177 | trait Cat extends Animal: 178 | override def action(ball: String) = super.action("Cat-" + ball) 179 | 180 | trait Dog extends Animal: 181 | override def action(ball: String) = super.action("Dog-" + ball) 182 | 183 | trait Monkey extends Animal: 184 | override def action(ball: String) = super.action("Monkey-" + ball) 185 | 186 | val animal1 = new Animal with Monkey with Dog 187 | animal1.action("action-1") // "Action : Monkey-Dog-action-1" 188 | 189 | val animal2 = new Animal with Dog with Monkey 190 | animal2.action("action-2") // "Action": Dog-Monkey-action-2" 191 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpaqueTypeAliases1.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package opaqueTypeAliases1 3 | 4 | import scala.util.chaining.* 5 | 6 | //--- 7 | // 実装例その1 8 | 9 | // - 以下は、Logarithm(対数)を抽象化したもので、実体は Double 10 | // - Double であるという事実は、MyMath のスコープ内でのみ知られている 11 | // - スコープ内では Logarithm は型エイリアスとして扱われるが、 12 | // スコープ外からは opaque(不透明)なので、Logarithm は抽象型として見られ、 13 | // 実体が Double であることを認識することはできない 14 | object MyMath: 15 | 16 | // 必ず何らかのスコープ内で定義する 17 | opaque type Logarithm = Double 18 | 19 | object Logarithm: 20 | 21 | def apply(d: Double): Logarithm = math.log(d) 22 | 23 | def safe(d: Double): Option[Logarithm] = 24 | if d > 0.0 then Some(math.log(d)) else None 25 | 26 | end Logarithm 27 | 28 | extension (x: Logarithm) 29 | def toDouble: Double = math.exp(x) 30 | def + (y: Logarithm): Logarithm = Logarithm(math.exp(x) + math.exp(y)) 31 | def * (y: Logarithm): Logarithm = x + y 32 | 33 | end MyMath 34 | 35 | @main def no1(): Unit = 36 | import MyMath.Logarithm 37 | 38 | println("-" * 50) 39 | 40 | // Logarithm の生成は、コンパニオンオブジェクトで定義された 41 | // apply か safe メソッドからのみ生成ができる 42 | val l = Logarithm(1.0) 43 | val l2 = Logarithm(2.0) 44 | val l3 = Logarithm.safe(3.0) // Option[Logarithm] 45 | 46 | // Logarithm は、Extension methods で公開したメソッドからのみアクセスが可能 47 | val l4 = l * l2 48 | val l5 = l + l2 49 | 50 | println(s"l: $l") 51 | println(s"l2: $l2") 52 | println(s"l3: $l3") 53 | println(s"l4: $l4") 54 | println(s"l5: $l5") 55 | 56 | //--- 57 | // 以下はコンパイルエラー 58 | 59 | // Double のメソッドは外部から呼び出せない 60 | // l.floatValue 61 | 62 | // Logarithm は、Double 型の変数に設定できない 63 | // val d: Double = l // error: found: Logarithm, required: Double 64 | 65 | // Double 型で初期化はできない 66 | // val l6: Logarithm = 1.0 // error: found: Double, required: Logarithm 67 | 68 | // Double 型で計算はできない 69 | // l * 2 // error: found: Int(2), required: Logarithm 70 | 71 | // `/` というメソッドは Logarithm で定義されていないので使えない 72 | // l / l2 // error: `/` is not a member of Logarithm 73 | 74 | println("-" * 50) 75 | 76 | //--- 77 | // Bounds For Opaque Type Aliases 78 | // 実装例その2 79 | 80 | object Access: 81 | 82 | // Permission: 1つのパーミッションを表す 83 | // Permissions: Permission のセットを表す 84 | // PermissionChoice: 「Permissions の内、少なくとも1つ」という意味で、Permission のセットを表す 85 | 86 | // - 3つの opaque type aliases はすべて同じ基礎表現の Int で定義されている 87 | // - Permission 型には、上限境界として Permissions & PermissionChoice が指定されている 88 | // これにより、Permission が他の2つの型のサブタイプであることが 89 | // Access オブジェクトの外部に知られるようになる 90 | 91 | opaque type Permissions = Int 92 | opaque type PermissionChoice = Int 93 | opaque type Permission <: Permissions & PermissionChoice = Int // `&` は Intersection Types 94 | 95 | // Accessオブジェクトの外側では、 96 | // - Permission 型の値は `&` 演算子を使用して結合することができる 97 | // - PermissionChoice 型の値は、`|` 演算子を使用して結合することができる 98 | 99 | // スコープの内側では、Permissions は Int の型エイリアスなので、 100 | // メソッド本体の `|` は、Int に対する論理演算子となる。(Union Types ではない) 101 | // つまり、Extension methods の `|` が呼ばれるわけではないので、無限の再帰を引き起こすことはない 102 | extension (x: Permissions) 103 | def & (y: Permissions): Permissions = x | y // `x | y` の `|` は論理演算子 104 | extension (x: PermissionChoice) 105 | def | (y: PermissionChoice): PermissionChoice = x | y // `x | y` の `|` は論理演算子 106 | extension (granted: Permissions) 107 | def is(required: Permissions) = (granted & required) == required 108 | extension (granted: Permissions) 109 | def isOneOf(required: PermissionChoice) = (granted & required) != 0 110 | 111 | val NoPermission: Permission = 0 112 | val Read: Permission = 1 113 | val Write: Permission = 2 114 | val ReadWrite: Permissions = Read | Write 115 | val ReadOrWrite: PermissionChoice = Read | Write 116 | 117 | end Access 118 | 119 | @main def no2(): Unit = 120 | println("-" * 50) 121 | 122 | import Access._ 123 | 124 | case class Item(rights: Permissions) 125 | 126 | val roItem = Item(Read) 127 | val rwItem = Item(ReadWrite) 128 | val noItem = Item(NoPermission) 129 | 130 | roItem.rights.is(ReadWrite) 131 | .tap(r => assert(!r)) 132 | 133 | roItem.rights.isOneOf(ReadOrWrite) 134 | .tap(assert(_)) 135 | 136 | rwItem.rights.is(ReadWrite) 137 | .tap(assert(_)) 138 | 139 | rwItem.rights.isOneOf(ReadOrWrite) 140 | .tap(assert(_)) 141 | 142 | noItem.rights.is(ReadWrite) 143 | .tap(r => assert(!r)) 144 | 145 | noItem.rights.isOneOf(ReadOrWrite) 146 | .tap(r => assert(!r)) 147 | 148 | // 一方で、Permission と PermissionChoice は Access の外では無関係な別の型なので、 149 | // これはコンパイルエラーになる 150 | // roItem.rights.isOneOf(ReadWrite) 151 | 152 | println("-" * 50) 153 | 154 | //--- 155 | // Opaque Type Members on Classes 156 | // 157 | // Opaque Type Aliases は、class と一緒に使用することもできる 158 | 159 | class MyMathClass: 160 | 161 | opaque type Logarithm = Double 162 | 163 | def apply(d: Double): Logarithm = math.log(d) 164 | 165 | def safe(d: Double): Option[Logarithm] = 166 | if d > 0.0 then Some(math.log(d)) else None 167 | 168 | def mul(x: Logarithm, y: Logarithm) = x + y 169 | 170 | @main def no3(): Unit = 171 | println("-" * 50) 172 | 173 | // 異なるインスタンスの Opaque Type のメンバーは異なるものとして扱われる 174 | 175 | val l1 = new MyMathClass 176 | val l2 = new MyMathClass 177 | val x = l1(1.5) 178 | val y = l1(2.6) 179 | val z = l2(3.1) 180 | l1.mul(x, y) // type checks 181 | // l1.mul(x, z) // error: found l2.Logarithm, required l1.Logarithm 182 | 183 | println("-" * 50) 184 | 185 | //--- 186 | // Opaque Type Aliase に case class を指定すると、copyメソッドを封じることができる 187 | 188 | object CaseClassOpaque: 189 | 190 | final case class InternalPerson(name: String) 191 | 192 | opaque type Person = InternalPerson 193 | object Person: 194 | def apply(name: String): Person = InternalPerson(name) 195 | 196 | extension (p: Person) 197 | def withName(name: String): Person = p.copy(name = name) 198 | 199 | @main def no4(): Unit = 200 | println("-" * 50) 201 | 202 | import CaseClassOpaque.Person 203 | 204 | val p = Person("hoge") 205 | .tap(println) 206 | 207 | val p2 = p.withName("aaaa") 208 | .tap(println) 209 | 210 | // copy は公開されていないのでコンパイルエラーになる 211 | // p.copy(name = "xxxx") 212 | 213 | println("-" * 50) 214 | 215 | //--- 216 | // ちなみにトップレベルで定義してしまうと、実体がダダ漏れなので注意 217 | 218 | opaque type Logarithm2 = Double 219 | 220 | object Logarithm2: 221 | def apply(d: Double): Logarithm2 = math.log(d) 222 | 223 | extension (x: Logarithm2) 224 | def + (y: Logarithm2): Logarithm2 = Logarithm2(math.exp(x) + math.exp(y)) 225 | 226 | @main def no5(): Unit = 227 | println("-" * 50) 228 | 229 | val l = Logarithm2(1.0) 230 | 231 | // スコープの中なので、Extension Methods だって生やせちゃう 232 | extension (x: Logarithm2) 233 | def toDouble: Double = x 234 | 235 | l.toDouble 236 | 237 | println("-" * 50) 238 | 239 | //--- 240 | // Value Classes も一応使える 241 | 242 | class ValueClasses(value: String) extends AnyVal 243 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpaqueTypeAliases2.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package opaqueTypeAliases2 3 | 4 | object Scope: 5 | 6 | opaque type Amount1 = Double 7 | object Amount1: 8 | def apply(amount: Double): Amount1 = amount 9 | 10 | opaque type Amount2 = Double 11 | object Amount2: 12 | def apply(amount: Double): Amount2 = amount 13 | 14 | end Scope 15 | 16 | def no1(): Unit = 17 | import Scope.* 18 | 19 | val a1 = Amount1(100.0) 20 | val a2 = Amount2(100.0) 21 | 22 | def f(x1: Amount1, x2: Amount2): Boolean = ??? 23 | 24 | f(a1, a2) // OK 25 | // f(a2, a1) // NG 26 | 27 | @main def no2(): Unit = 28 | import Scope.* 29 | 30 | val a1 = Amount1(100.0) 31 | val a2 = Amount2(100.0) 32 | 33 | println(a1 == a2) // true -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpaqueTypeAliases3.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package opaqueTypeAliases3 3 | 4 | //--- 5 | // Domain Modeling Made Functional - Chapter 4 Understanding Types の 6 | // Building a Domain Model by Composing Types を Scala 3で表現すると 7 | 8 | object Purchase: 9 | 10 | // スコープの外からは実体が隠されている 11 | opaque type CheckNumber = Int 12 | object CheckNumber: 13 | def apply(n: Int): CheckNumber = n 14 | 15 | // もしもスコープの外からアクセスしたい場合は、extension methods で公開する 16 | extension (a: CheckNumber) 17 | def hoge: Unit = ??? 18 | 19 | opaque type CardNumber = String 20 | object CardNumber: 21 | def apply(n: String): CardNumber = n 22 | 23 | opaque type PaymentAmount = Float 24 | object PaymentAmount: 25 | def apply(amount: Float): PaymentAmount = amount 26 | 27 | enum CardType: 28 | case Visa, Mastercard 29 | 30 | enum Currency: 31 | case EUR, USD 32 | 33 | final case class CreditCardInfo(cardType: CardType, cardNumber: CardNumber) 34 | 35 | enum PaymentMethod: 36 | case Cash 37 | case Check(checkNumber: CheckNumber) 38 | case Card(creditCardInfo: CreditCardInfo) 39 | 40 | final case class Payment( 41 | amount: PaymentAmount, 42 | currency: Currency, 43 | method: PaymentMethod 44 | ) 45 | 46 | def no1(): Unit = 47 | import Purchase.* 48 | 49 | Payment( 50 | PaymentAmount(10.0f), 51 | Currency.EUR, 52 | PaymentMethod.Cash) 53 | 54 | Payment( 55 | PaymentAmount(350), 56 | Currency.USD, 57 | PaymentMethod.Card(CreditCardInfo(CardType.Visa, CardNumber("1111111111111111")))) 58 | 59 | // 外のスコープからは公開されたメソッドにしかアクセスできない 60 | CheckNumber(1234).hoge 61 | 62 | // 公開されていないメソッドを呼び出すことはできない(実体である Int の toDouble とか) 63 | // CheckNumber(1234).toDouble 64 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UnionTypes.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package unionTypes 3 | 4 | case class UserName(value: String) 5 | case class Email(value: String) 6 | 7 | case class User(name: UserName) 8 | 9 | def lookupName(name: String): Unit = println("call lookupName") 10 | def lookupEmail(email: String): Unit = println("call lookupEmail") 11 | 12 | //--- 13 | // Union Types をメソッドの引数に使用する 14 | 15 | // 引数 id は、UserName か Email のどちらかの型 16 | def help(id: UserName | Email): Unit = 17 | id match 18 | case UserName(value) => lookupName(value) 19 | case Email(value) => lookupEmail(value) 20 | 21 | //--- 22 | // どちらの型でも渡すことができる 23 | 24 | @main def no1(): Unit = 25 | val name = UserName("Eve") 26 | val email = Email("abc@xxx.xxx") 27 | 28 | help(name) // call lookupName 29 | help(email) // call lookupEmail 30 | 31 | //--- 32 | // Union Types は、型を省略せずに明示する必要がある 33 | 34 | @main def no2(): Unit = 35 | val name = UserName("Eve") 36 | val email = Email("abc@xxx.xxx") 37 | 38 | // これは明示してないので、Object & Product になる 39 | // (UserName と Email の least upper bound である、Object & Product) 40 | val result1 = if true then name else email 41 | 42 | // これはコンパイルエラー 43 | // help(result1) 44 | 45 | // これは明示してるので、 Union Types になる 46 | val result2: UserName | Email = if true then name else email 47 | 48 | help(result2) // call lookupName 49 | 50 | //--- 51 | // | 演算子は可換的なので逆にしてもOK 52 | 53 | def no3(): Unit = 54 | val name = UserName("Eve") 55 | val email = Email("abc@xxx.xxx") 56 | 57 | def help(id: Email | UserName): Unit = 58 | id match 59 | case UserName(value) => lookupName(value) 60 | case Email(value) => lookupEmail(value) 61 | 62 | val result: UserName | Email = if true then name else email 63 | 64 | help(result) 65 | 66 | //--- 67 | // どちらかの型、なので共通する振る舞いを持っているわけではない 68 | // つまり、型ごとに振る舞いが違う 69 | 70 | @main def no4(): Unit = 71 | trait A: 72 | def foo(): Unit = println("foo") 73 | 74 | trait B: 75 | def fuga(): Unit = println("fuga") 76 | 77 | class C extends A, B 78 | 79 | // A か B のどちらかなので、これはできない 80 | // def f(x: A | B) = 81 | // x.foo() 82 | 83 | // パターンマッチで型を判別する必要がある 84 | def f(x: A | B) = 85 | x match 86 | case a: A => a.foo() 87 | case b: B => b.fuga() 88 | 89 | f(new A {}) // foo 90 | f(new B {}) // fuga 91 | f(new C) // foo 92 | 93 | //--- 94 | // メソッドの結果型として、Union Types を返すこともできる 95 | 96 | def no5(): Unit = 97 | trait A: 98 | def foo(): A = ??? 99 | 100 | trait B: 101 | def fuga(): B = ??? 102 | 103 | def f(x: A | B): A | B = 104 | x match 105 | case a: A => a.foo() 106 | case b: B => b.fuga() 107 | 108 | //--- 109 | // 3種類以上の型でも Union Types を作れる 110 | 111 | def no6() : Unit = 112 | trait A 113 | trait B 114 | trait C 115 | 116 | def f(x: A | B | C): Unit = ??? 117 | 118 | f(new A {}) 119 | f(new B {}) 120 | f(new C {}) 121 | 122 | //--- 123 | // ヘテロジニアスなListも作れる 124 | 125 | def no7() : Unit = 126 | val xs: List[Int | String] = List("a", 1, "b", 2) 127 | // val xs2: List[Matchable] = List("a", 1, "b", 2) 128 | 129 | trait A 130 | trait B 131 | class C extends A, B 132 | 133 | // Int または、 A であり B でもある型 134 | val ys: List[Int | A & B] = List(1, new C, 2, 3) 135 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UnionTypes2.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package unionTypes2 3 | 4 | import com.github.shinharad.gettingStartedWithScala3.unionTypes2.UsernameOrPassword 5 | 6 | //--- 7 | // Scala 3 Book より 8 | // https://docs.scala-lang.org/scala3/book/types-union.html 9 | 10 | trait Hash 11 | 12 | // Alternative to Union Types 13 | // 14 | // Union Types は、複数の異なる型の代替品を表現するために使用することができる 15 | // https://docs.scala-lang.org/scala3/book/types-union.html 16 | 17 | //--- 18 | // Pre-Planning the Class Hierarchy 19 | // 今までは、次のようにクラス階層を事前に決める必要があった 20 | trait UsernameOrPassword 21 | case class Username(name: String) extends UsernameOrPassword 22 | case class Password(hash: Hash) extends UsernameOrPassword 23 | 24 | def help(id: UsernameOrPassword) = id match 25 | case Username(name) => ??? 26 | case Password(hash) => ??? 27 | 28 | // 例えば、APIのクライアントの要求は予測できないかもしれないので、事前に計画を立ててもあまりうまくいかない。 29 | // また、UsernameOrPassword のようなマーカートレイトで型階層を乱雑にすると、コードが読みにくくなる。 30 | 31 | //--- 32 | // Tagged Unions 33 | // もう一つの方法は、列挙型で分離して定義すること 34 | enum UsernameOrPassword2: 35 | case IsUsername(u: Username) 36 | case IsPassword(p: Password) 37 | 38 | def help2(id: UsernameOrPassword2) = id match 39 | case UsernameOrPassword2.IsUsername(u) => u.name 40 | case UsernameOrPassword2.IsPassword(h) => h.hash 41 | 42 | // 列挙型の UsernameOrPassword2 は、Username と Password の tagged union を表している。 43 | // しかし、このような方法で Union をモデル化するには、明示的なラッピングとアンラッピングが必要であり、 44 | // たとえば、Username は UsernameOrPassword2 のサブタイプではない。 45 | 46 | //--- 47 | // Union Types 48 | // そこで、Union Types で表現する 49 | 50 | def help3(id: Username | Password) = id match 51 | case Username(name) => ??? 52 | case Password(hash) => ??? 53 | -------------------------------------------------------------------------------- /step03/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UnionTypes3.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package unionTypes3 3 | 4 | //--- 5 | // Literal-based singleton types 6 | // https://docs.scala-lang.org/sips/42.type.html 7 | // (since Scala 2.13) 8 | 9 | @main def no1(): Unit = 10 | println("-" * 50) 11 | 12 | val intValue = 10 13 | val ten: 10 = 10 14 | 15 | def printInt(n: Int) = println(n) 16 | printInt(50) 17 | printInt(ten) 18 | 19 | def printStrict(n: 10) = println(n) 20 | printStrict(ten) 21 | // printTen(50) // compile error 22 | 23 | println("-" * 50) 24 | 25 | //--- 26 | // Literal-based singleton types と Union Types を組み合わせる 27 | 28 | @main def no2(): Unit = 29 | println("-" * 50) 30 | 31 | def passStrict(n: 1 | 2 | 3) = println(n) 32 | passStrict(1) // ok 33 | passStrict(2) // ok 34 | passStrict(3) // ok 35 | // passStrict(4) // compile error 36 | 37 | println("-" * 50) 38 | 39 | type Valid = 1 | 2 | 3 40 | def passStrict2(n: Valid) = println(n) 41 | passStrict2(1) // ok 42 | passStrict2(2) // ok 43 | passStrict2(3) // ok 44 | // passStrict2(4) // compile error 45 | 46 | println("-" * 50) 47 | 48 | type ValidOr[A] = A | 1 | 2 | 3 49 | def passStrict3(n: ValidOr[4]) = println(n) 50 | passStrict3(1) // ok 51 | passStrict3(2) // ok 52 | passStrict3(3) // ok 53 | passStrict3(4) // ok 54 | // passStrict3(5) // compile error 55 | 56 | println("-" * 50) 57 | 58 | //--- 59 | // Errorの文脈を表現 60 | 61 | @main def no3(): Unit = 62 | println("-" * 50) 63 | 64 | type ErrorOr[A] = A | "error" 65 | 66 | def handle(value: ErrorOr[Int]): Unit = 67 | value match 68 | case _: "error" => println("error") 69 | case x: Int => println(s"value: $value") 70 | 71 | handle(10) 72 | handle("error") 73 | // handle("errorerror") // compile error 74 | 75 | println("-" * 50) -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/01_RestrictedKeywords.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `01_syntacticChange` 3 | package `01_restrictedKeywords` 4 | 5 | // Scala 3.0 Migration mode の動作確認をします 6 | // 以下のコメントアウトを外して、 7 | // build.sbt の step04 の scalacOptions の設定を変えてみてください 8 | 9 | //--- 10 | // scalacOptions の以下を順番に有効にしてみてください 11 | // - "-explain" 12 | // - "-source:3.0-migration" 13 | // - "-source:3.0-migration", "-rewrite" 14 | 15 | // object given { 16 | // val enum = ??? 17 | // println(enum) 18 | // } 19 | 20 | //--- 21 | // scalacOptions の以下を有効にしてみてください 22 | // - "-explain" 23 | // (これに関してはマイグレーションモードが効かないらしい) 24 | 25 | // def then(): Unit = () 26 | 27 | object Dummy 28 | -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/02_ProcedureSyntax.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `01_syntacticChange` 3 | package `02_procedureSyntax` 4 | 5 | //--- 6 | // scalacOptions の以下を順番に有効にしてみてください 7 | // - "-explain" 8 | // - "-source:3.0-migration" 9 | // - "-source:3.0-migration", "-rewrite" 10 | 11 | // trait Foo { 12 | // def print() 13 | // } 14 | 15 | // object Bar { 16 | // def print() { 17 | // println("bar") 18 | // } 19 | // } 20 | 21 | object Dummy -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/03_ParenthesesAroundLambdaParameter.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `01_syntacticChange` 3 | package `03_parenthesesAroundLambdaParameter` 4 | 5 | //--- 6 | // scalacOptions の以下を順番に有効にしてみてください 7 | // - "-source:3.0-migration" 8 | // - "-source:3.0-migration", "-rewrite" 9 | 10 | // val f = { x: Int => x * x } 11 | 12 | object Dummy -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/04_OpenBraceIndentationForPassingAnArgument.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `01_syntacticChange` 3 | package `04_openBraceIndentationForPassingAnArgument` 4 | 5 | def test(name: String)(f: => Unit) = ??? 6 | 7 | //--- 8 | // scalacOptions の以下を順番に有効にしてみてください 9 | // - "-explain" 10 | // - "-source:3.0-migration" 11 | 12 | // Scala 2 では、改行後の引数を中括弧で囲めるが推奨されていない 13 | // Scala 3 では警告が表示される 14 | // def no1(): Unit = 15 | // test("my test") 16 | // { 17 | // assert(1 == 1) 18 | // } 19 | 20 | // Scala 2 では、このように書くのが正しい 21 | def no2(): Unit = 22 | test("my test") { 23 | assert(1 == 1) 24 | } 25 | 26 | // no1 を Scala 3 で敢えて書くならこう書くのが正しい 27 | def no3(): Unit = 28 | test("my test") 29 | { 30 | assert(1 == 1) 31 | } 32 | -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/05_WrongIndentation.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `01_syntacticChange` 3 | package `05_wrongIndentation` 4 | 5 | // 以下のコメントアウトしているコードは、Scala 2 ではコンパイルが通るが、 6 | // Scala 3 ではインデントが間違ってるのでコンパイルが通らない 7 | // この場合は、scalafmt などでコードフォーマットする必要がある 8 | 9 | // val foo_wrong = 10 | // Vector(1) ++ 11 | // Vector(2) ++ 12 | // Vector(3) 13 | 14 | // def bar_wrong: (Int, Int) = { 15 | // val foo = 1.0 16 | // val bar = foo 17 | // (1, 1) 18 | // } 19 | 20 | val foo = 21 | Vector(1) ++ 22 | Vector(2) ++ 23 | Vector(3) 24 | 25 | def bar: (Int, Int) = { 26 | val foo = 1.0 27 | val bar = foo 28 | (1, 1) 29 | } 30 | -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/06__AsATypeParameter.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `01_syntacticChange` 3 | package `06__AsATypeParameter` 4 | 5 | trait Foo[T] 6 | 7 | // Scala 2 では、これはコンパイルが通る 8 | // def foo[_: Foo]: Unit = ??? 9 | 10 | // これは、fooというメソッドの型パラメータ `_` に Context Bound として `trait Foo[_]` を指定したものだが、 11 | // このような書き方は、Scala コンパイラのバグを巧みに利用したもので、想定外な使われ方だったため、Scala 3 では廃止された 12 | // 13 | // 参考) 14 | // https://www.reddit.com/r/scala/comments/fczcvo/mysterious_context_bounds_in_fastparse_2/fjecokn/ 15 | 16 | // Scala 3 からは型パラメータを明示する 17 | def foo[T: Foo]: Unit = ??? 18 | -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/01_syntacticChanges/07_+and-AsTypeParameter.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `01_syntacticChange` 3 | package `07_+and-AsTypeParameter` 4 | 5 | // Scala 2 では、型パラメータに `+` や `-` を指定してもコンパイルが通ったが、Scala 3 ではエラーになる 6 | // def foo[+]: Unit = ??? 7 | // def foo[-]: Unit = ??? 8 | 9 | // Scala 3 ではこのようにする 10 | def foo[T]: Unit = ??? 11 | 12 | // ただし、型の識別子としての `+` や `-` はまだ有効 13 | type + = String 14 | type - = Int -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/01_SymbolLiterals.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `02_droppedFeatures` 3 | package `01_SymbolLiterals` 4 | 5 | //--- 6 | // scalacOptions の以下を順番に有効にしてみてください 7 | // - "-explain" 8 | // - "-source:3.0-migration" 9 | // - "-source:3.0-migration", "-rewrite" 10 | 11 | // val values: Map[Symbol, Int] = Map('abc -> 1) 12 | 13 | // val abc = values('abc) 14 | 15 | object Dummy 16 | -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/02_DoWhileConstruct.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `02_droppedFeatures` 3 | package `02_DoWhileConstruct` 4 | 5 | //--- 6 | // scalacOptions の以下を順番に有効にしてみてください 7 | // - "-source:3.0-migration" 8 | // - "-source:3.0-migration", "-rewrite" 9 | 10 | def no1(): Unit = { 11 | def f(i: Int): Int = ??? 12 | var i = 1 13 | 14 | // do { 15 | // i += 1 16 | // } while (f(i) == 0) 17 | } 18 | -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/03_AutoApplication.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `02_droppedFeatures` 3 | package `03_AutoApplication` 4 | 5 | //--- 6 | // scalacOptions の以下を順番に有効にしてみてください 7 | // - "-explain" 8 | // - "-source:3.0-migration" 9 | // - "-source:3.0-migration", "-rewrite" 10 | 11 | // trait Chunk { 12 | // def bytes(): Seq[Byte] 13 | // def toSeq: Seq[Byte] = bytes 14 | // } 15 | 16 | object Dummy -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/02_droppedFeatures/04_ValueEtaExpansion.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `02_droppedFeatures` 3 | package `04_ValueEtaExpansion` 4 | 5 | //--- 6 | // scalacOptions の以下を順番に有効にしてみてください 7 | // - "-explain" 8 | // - "-source:3.0-migration" 9 | // - "-source:3.0-migration", "-rewrite" 10 | 11 | // val x = 1 12 | // val f: () => Int = x _ 13 | 14 | object Dummy -------------------------------------------------------------------------------- /step04/src/main/scala/com/github/shinharad/gettingStartedWithScala3/03_otherChangedFeatures/01_InheritanceShadowing.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package `03_otherChangedFeatures` 3 | package `01_InheritanceShadowing` 4 | 5 | //--- 6 | // scalacOptions の以下を順番に有効にしてみてください 7 | // - "-explain" 8 | // - "-source:3.0-migration" 9 | // - "-source:3.0-migration", "-rewrite" 10 | 11 | // class A { 12 | // val x = 2 13 | // } 14 | 15 | // object B { 16 | // val x = 1 17 | // class C extends A { 18 | // println(x) 19 | // } 20 | // } 21 | 22 | object Dummy -------------------------------------------------------------------------------- /step05/explicit-nulls/src/main/java/com/github/shinharad/explicitnulls/JavaClass.java: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.explicitnulls; 2 | 3 | public class JavaClass { 4 | 5 | public String f(String x) { 6 | return ""; 7 | } 8 | 9 | public String unsafeMethod() { 10 | return null; 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /step05/explicit-nulls/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ExplicitNulls2.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package explicitNulls2 3 | 4 | //--- 5 | // Flow Typing 6 | 7 | // nullable な変数が if 式などで non-null であると判定した場合、 8 | // そのスコープ内では変数を non-null として扱える(Flow Typing) 9 | 10 | def no1_1(): Unit = 11 | val s: String | Null = ??? 12 | 13 | if s != null then 14 | // if の中では non-null であることが自明なので、String として使用できる 15 | s.length // s: String 16 | 17 | // if の外では nullable 18 | // s.length // これはコンパイルエラー 19 | s.nn.length 20 | 21 | // assert の後も non-null は自明なので、String として使用できる 22 | assert(s != null) 23 | s.length // s: String 24 | 25 | // else でも同様に Flow Typing できる 26 | def no1_2(): Unit = 27 | val s: String | Null = ??? 28 | 29 | if s == null then 30 | s.nn.length // s: String | Null 31 | else 32 | // s は、non-null なので、String として使用可能 33 | s.length // s: String 34 | 35 | //--- 36 | // Logical Operators 37 | 38 | // 論理演算子でも Flow Typing できる 39 | def no2(): Unit = 40 | val s: String | Null = ??? 41 | val s2: String | Null = ??? 42 | 43 | if s != null && s2 != null then 44 | s.length // s: String 45 | s2.length // s2: String 46 | 47 | if s == null || s2 == null then 48 | // s: String | Null 49 | // s2: String | Null 50 | s.nn.length 51 | s2.nn.length 52 | else 53 | s.length // s: String 54 | s2.length // s2: String 55 | 56 | //--- 57 | // Inside Conditions 58 | 59 | // 論理演算子の短絡評価が、Flow Typing にも反映する 60 | def no3(): Unit = 61 | val s: String | Null = ??? 62 | 63 | if s != null && s.length > 0 then 64 | s.length // s: String 65 | 66 | if s == null || s.length > 0 then 67 | s.nn.length // s: String | Null 68 | else 69 | s.length // s: String 70 | 71 | //--- 72 | // Match Case 73 | 74 | // match でも Flow Typing できる 75 | def no4(): Unit = 76 | val s: String | Null = ??? 77 | 78 | s match 79 | case _: String => s.length // s: String 80 | case _ => s.nn.length // s: String | Null 81 | 82 | //--- 83 | // Mutable Variable 84 | 85 | // mutable 変数の場合は、変数が null かどうかをコンパイラが追跡可能であれば Flow Typing できる 86 | def no5_1(): Unit = 87 | 88 | class C(val x: Int, val next: C | Null) 89 | 90 | var xs: C | Null = C(1, C(2, null)) 91 | 92 | while xs != null do 93 | // xs は non-null と推論される 94 | 95 | val xsx: Int = xs.x 96 | val xscpy: C = xs 97 | 98 | // xscpy は non-null なので、それを再代入した xs も non-null 99 | xs = xscpy // xs: C 100 | 101 | // nullable な xs.next を再代入すると、xs は nullable になる 102 | xs = xs.next // xs: C | Null 103 | 104 | // 変数が null かどうかをコンパイラが追跡できない場合、Flow Typing できない 105 | // 例えば、変数がクロージャで代入される場合 106 | def no5_2(): Unit = 107 | var x: String | Null = ??? 108 | def y = 109 | x = null 110 | 111 | if x != null then 112 | // x は、クロージャ y で代入されているので、x に対して Flow Typing を行わない 113 | // つまり、x が例え if で non-null と判定されたとしても、nullable のままとなる 114 | 115 | // val a1: String = x // コンパイルエラー 116 | val a2: String | Null = x 117 | 118 | // 変数がクロージャで代入されていない場合、ローカル変数の定義と同じメソッドに属する箇所では Flow Typing できる 119 | // ただし、クロージャの中では、例え non-null と判定したとしても、安全ではないので Flow Typing できない 120 | def no5_3(): Unit = 121 | var x: String | Null = ??? 122 | 123 | def y = 124 | if x != null then 125 | // クロージャ y がどのタイミングで実行されるか分からず、かつ x の状態は変わる可能性があるので、 126 | // non-null として扱うことは安全ではなく、この場合は Flow Typing できない 127 | 128 | x.nn.length // x: String | Null 129 | 130 | if x != null then 131 | // ローカル変数の定義と同じメソッドに属するので、Flow Typing できる 132 | val a: String = x // x: String 133 | 134 | // null を再代入すると再び nullable に 135 | x = null 136 | 137 | val c: String | Null = x // x: String | Null 138 | 139 | //--- 140 | // Unsupported Idioms 141 | 142 | // Flow Typing は nullability と関係ない場合は使えない 143 | def no6_1(): Unit = 144 | val x: Int = ??? 145 | if x == 0 then 146 | // x: 0.type とは推論されない 147 | () 148 | 149 | // non-null の変数との比較では Flow Typing を使えない 150 | def no6_2(): Unit = 151 | val s: String | Null = ??? 152 | val s2: String | Null = ??? 153 | 154 | if s != null && s == s2 then 155 | // s は String と推論されるが、 156 | s.length 157 | 158 | // non-null な s と比較したからといって、s2 が String と推論されるわけではない 159 | // s2.length // コンパイルエラー 160 | 161 | //--- 162 | // UnsafeNulls 163 | 164 | // 多くの nullable な値を扱うのは時として困難になるので、language feature として `unsafeNulls` が提供されている 165 | // unsafeNulls スコープの中では、すべての `T | Null` は `T` として使用することができる。 166 | 167 | def no7_1(): Unit = 168 | def f(x: String): String = ??? 169 | def nullOf[T >: Null]: T = null 170 | 171 | import scala.language.unsafeNulls 172 | 173 | val s: String | Null = ??? 174 | 175 | // nullable を non-null に設定する 176 | val a: String = s 177 | 178 | // nullable を non-null として扱える 179 | val b1 = s.trim 180 | val b2 = b1.length 181 | 182 | // nullable を non-null な引数に渡せる 183 | f(s).trim 184 | 185 | // non-null に null を渡せる 186 | val c: String = null 187 | 188 | val d1: Array[String] = ??? 189 | 190 | // non-null の型パラメータを nullable な型パラメータの変数に設定できる 191 | val d2: Array[String | Null] = d1 192 | 193 | // Arrayの要素に null を設定したとしても non-null な型パラメータとして扱える 194 | val d3: Array[String] = Array(null) 195 | 196 | // Null は Any のサブタイプだが、AnyRef として使用できるので、 197 | // このような安全でない上限型境界を指定できる 198 | class C[T >: Null <: String] // define a type bound with unsafe conflict bound 199 | 200 | // Null は Any のサブタイプだが、AnyRef として使用できるので、任意の参照型を渡せる 201 | // val n = nullOf[String] 202 | 203 | // unsafeNuls を適用した場合、通常の Scala と同様のセマンティックを持つが、同等ではない 204 | // 例えば、以下のようなコードは unsafeNulls を使ってもコンパイルはできない 205 | def no7_2(): Unit = 206 | import scala.language.unsafeNulls 207 | 208 | // Javaとの相互運用のため,getの結果型は `T | Null` になるが、 209 | // コンパイラは `T` が参照型であるかどうかを知らないため `T | Null` を `T` にキャストすることができない 210 | // これはコンパイルエラー 211 | // def head[T](xs: java.util.List[T]): T = xs.get(0) 212 | 213 | // これは、`xs.get(0)` の後に `.nn` を明示的に書く必要がある 214 | def head[T](xs: java.util.List[T]): T = xs.get(0).nn 215 | -------------------------------------------------------------------------------- /step05/explicit-nulls/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ExplicitNulls3.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package explicitNulls3 3 | 4 | import com.github.shinharad.explicitnulls.JavaClass 5 | 6 | import scala.util.chaining.* 7 | import scala.util.{ Try, Success } 8 | 9 | //--- 10 | // Java の unsafe なメソッド呼び出しを Try でラップして、 11 | // Flow Typing を試してみる 12 | 13 | @main def no1(): Unit = 14 | println("-" * 50) 15 | 16 | val javaClass = JavaClass() 17 | 18 | val a: Try[String | Null] = Try(javaClass.unsafeMethod()) 19 | 20 | // match式のガード条件ではFlow Typingが発動しないっぽい 21 | val result1: Int = a match 22 | case Success(r) if r != null => 23 | val b: String | Null = r // non-null とはならない 24 | 25 | // 改めて Flow Typing する 26 | if b != null then 27 | b.length 28 | else 29 | 0 30 | 31 | case _ => 32 | 0 33 | 34 | println(result1) 35 | 36 | // 代わりにこのようにすると良さそう 37 | val result2: Int = a match 38 | case Success(r: String) => 39 | r.length 40 | 41 | case _ => 0 42 | 43 | println(result2) 44 | 45 | // 高階関数の中でFlow Typingを使ってみる 46 | val result3: Try[Int] = a.map { x => 47 | if x != null then 48 | x.length 49 | else 50 | 0 51 | } 52 | 53 | println(result3) 54 | 55 | println("-" * 50) 56 | -------------------------------------------------------------------------------- /step05/import-export/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ChangesInOverloadResolution.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package changesInOverloadResolution 3 | 4 | //--- 5 | // Looking Beyond the First Argument List 6 | 7 | // Scala 3 のオーバーロードは、複数の引数リストが存在する場合、 8 | // 最初の引数リストだけでなく、すべての引数リストの型を考慮するようになった 9 | object No1: 10 | def f(x: Int)(y: String): Int = 0 11 | def f(x: Int)(y: Int): Int = 0 12 | 13 | f(3)("") // ok 14 | 15 | // Scala 2 でこのようなコードを書くとコンパイルエラーになっていた 16 | /* 17 | f(3)("") 18 | ^ 19 | error: ambiguous reference to overloaded definition, 20 | both method f in object No1 of type (x: Int)(y: Int): Int 21 | and method f in object No1 of type (x: Int)(y: String): Int 22 | match argument types (Int) 23 | */ 24 | 25 | // 引数リストが3つ以上の場合でもオーバーロードが可能 26 | object No2: 27 | def f(x: Int)(y: Int)(z: Int): Int = 0 28 | def f(x: Int)(y: Int)(z: String): Int = 0 29 | 30 | f(2)(3)(4) // ok 31 | f(2)(3)("") // ok 32 | 33 | //--- 34 | // Parameter Types of Function Values 35 | 36 | // Scala 3 では、オーバーロードされた最初の引数リストに、 37 | // 欠損したパラメータ型(missing parameter types)を持つ関数値を渡せるようになった 38 | object No3: 39 | def f(x: Int, f2: Int => Int) = f2(x) 40 | def f(x: String, f2: String => String) = f2(x) 41 | 42 | f("a", _.toUpperCase) 43 | f(2, _ * 2) 44 | 45 | // Scala 2 でこのようなコードを書くとコンパイルエラーになっていた 46 | /* 47 | missing parameter type for expanded function (() => x$1.toUpperCase) 48 | [error] f("a", _.toUpperCase) 49 | [error] ^ 50 | [error] one error found 51 | */ 52 | -------------------------------------------------------------------------------- /step05/import-export/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ExportClauses.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package exportClauses 3 | 4 | import scala.util.chaining.* 5 | 6 | class BitMap 7 | class InkJet 8 | 9 | class Printer: 10 | type PrinterType 11 | def print(bits: BitMap): Unit = 12 | println(s"print: - $bits") 13 | def status: List[String] = 14 | List("printer status") 15 | 16 | class Scanner: 17 | def scan(): BitMap = 18 | new BitMap 19 | def status: List[String] = 20 | List(s"scanner status") 21 | 22 | //--- 23 | // export 句を使うと、オブジェクトのメンバのエイリアス(export aliases)を定義できる 24 | 25 | @main def no1(): Unit = 26 | 27 | class Copier: 28 | private val printUnit = new Printer { type PrinterType = InkJet } 29 | private val scanUnit = new Scanner 30 | 31 | export scanUnit.scan 32 | export printUnit.print 33 | 34 | // 上記はこのような export aliases を生成する 35 | // final def scan(): BitMap = scanUnit.scan() 36 | // final def print(bits: BitMap): Unit = printUnit.status 37 | 38 | val copier = new Copier 39 | copier.print(copier.scan()) // export aliasesでアクセスする 40 | 41 | //--- 42 | // export 句は、trait や object でも定義できる 43 | 44 | @main def no2(): Unit = 45 | 46 | trait Copier1: 47 | private val scanUnit = new Scanner 48 | export scanUnit.scan 49 | 50 | object Copier2: 51 | private val scanUnit = new Scanner 52 | export scanUnit.scan 53 | 54 | //--- 55 | // export 句のワイルドカード指定 56 | 57 | @main def no3(): Unit = 58 | 59 | class Copier: 60 | private val printUnit = new Printer { type PrinterType = InkJet } 61 | private val scanUnit = new Scanner 62 | 63 | export scanUnit.* 64 | export printUnit.print 65 | 66 | // 上記はこのような export aliases を生成する 67 | // final def scan(): BitMap = scanUnit.scan() 68 | // final def status: List[String] = scanUnit.status 69 | // final def print(bits: BitMap): Unit = printUnit.print(bits) 70 | 71 | val copier = new Copier 72 | copier.print(copier.scan()) // export aliasesでアクセスする 73 | 74 | //--- 75 | // `as` でリネームできる 76 | 77 | @main def no4(): Unit = 78 | 79 | class Copier: 80 | private val scanUnit = new Scanner 81 | 82 | export scanUnit.scan as sc 83 | 84 | // 上記はこのような export aliases を生成する 85 | // final def sc(): BitMap = scanUnit.scan() 86 | 87 | val copier = new Copier 88 | copier.sc() // リネームしたメソッドを呼び出す 89 | 90 | //--- 91 | // export 句で特定のメンバを除外する 92 | 93 | @main def no5(): Unit = 94 | 95 | class Copier: 96 | private val printUnit = new Printer { type PrinterType = InkJet } 97 | private val scanUnit = new Scanner 98 | 99 | export scanUnit.scan 100 | export printUnit.status as _ // status を除外(敢えてこう書く必要は無いけど分かりやすさのため) 101 | export printUnit.print 102 | 103 | // ドキュメントの通りに書くとこうだけど、Imports のリネームが as なので 104 | // export printUnit.{ status => _ } 105 | 106 | // 上記はこのような export aliases を生成する 107 | // final def scan(): BitMap = scanUnit.scan() 108 | // final def print(bits: BitMap): Unit = printUnit.print(bits) 109 | 110 | val copier = new Copier 111 | 112 | // これはアクセスできるけど 113 | copier.print(copier.scan()) 114 | 115 | // status は export してないのでアクセスできない 116 | // copier.status 117 | 118 | //--- 119 | // 特定の定義以外を export する場合 120 | 121 | @main def no6(): Unit = 122 | 123 | class Copier: 124 | private val printUnit = new Printer { type PrinterType = InkJet } 125 | private val scanUnit = new Scanner 126 | 127 | export scanUnit.scan 128 | export printUnit.{ status as _, * } // status 以外をexport 129 | 130 | // 上記はこのような export aliases を生成する 131 | // final def scan(): BitMap = scanUnit.scan() 132 | // final def print(bits: BitMap): Unit = printUnit.print(bits) 133 | // final type PrinterType = printUnit.PrinterType 134 | 135 | val copier = new Copier 136 | 137 | // これはアクセスできるけど 138 | copier.print(copier.scan()) 139 | 140 | // status は export してないのでアクセスできない 141 | // copier.status 142 | 143 | //--- 144 | // export 句によるエイリアスと既存のメンバとの定義が重複する場合はコンパイルエラー 145 | 146 | def no7(): Unit = 147 | 148 | class Copier: 149 | private val printUnit = new Printer { type PrinterType = InkJet } 150 | private val scanUnit = new Scanner 151 | 152 | // status は既存メンバの定義と重複するのでコンパイルエラー 153 | // export printUnit.status 154 | 155 | // この場合は as でリネームする 156 | export printUnit.status as st 157 | 158 | def status: List[String] = printUnit.status ++ scanUnit.status 159 | 160 | val copier = new Copier 161 | 162 | copier.st 163 | copier.status 164 | 165 | //--- 166 | // overload はOK 167 | 168 | def no8(): Unit = 169 | 170 | class Copier: 171 | private val printUnit = new Printer { type PrinterType = InkJet } 172 | 173 | export printUnit.print 174 | 175 | // 上記はこのような export aliases を生成する 176 | // final def print(bits: BitMap): Unit = printUnit.print(bits) 177 | 178 | final def print(inkJet: InkJet): Unit = ??? // overload 179 | 180 | //--- 181 | // override はNG 182 | // (export 句は final def のエイリアスを作成するため) 183 | 184 | def no9(): Unit = 185 | 186 | class Copier: 187 | private val printUnit = new Printer { type PrinterType = InkJet } 188 | 189 | export printUnit.print 190 | 191 | // 上記はこのようなエイリアスを生成する 192 | // final def print(bits: BitMap): Unit = printUnit.print(bits) 193 | 194 | class Child extends Copier: 195 | type Dummy = Int 196 | 197 | // override はできない 198 | // override def print(bits: BitMap): Unit = ??? 199 | -------------------------------------------------------------------------------- /step05/import-export/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Imports.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package imports 3 | 4 | //--- 5 | // Wildcard Imports 6 | 7 | // Scala 3 の import 文のワイルドカードは、`*` になった 8 | // (今までのサンプルコードでも出てきたし、今更感がありますが...) 9 | import scala.util.chaining.* 10 | 11 | // 一応、`_` もまだ使えるけど、将来のバージョンで廃止される予定 12 | import scala.util.chaining._ 13 | 14 | //--- 15 | // `*` というメソッドを import するには? 16 | 17 | object A: 18 | def * = "*" 19 | def min = "min" 20 | 21 | object B: 22 | import A.`*` // A のメソッド `*` をimport 23 | 24 | def show = * 25 | 26 | // def minimum = min // `min` は import してないのでこれはコンパイルエラー 27 | 28 | object C: 29 | import A.* // A のメンバをすべて import 30 | 31 | def show = * + min 32 | 33 | @main def no1(): Unit = 34 | println("-" * 50) 35 | 36 | B.show.tap(println) 37 | C.show.tap(println) 38 | 39 | println("-" * 50) 40 | 41 | //--- 42 | // Renaming Imports 43 | 44 | // import のリネームは、`=>` から `as` に変わった 45 | @main def no2(): Unit = 46 | println("-" * 50) 47 | 48 | // 単一のリネームであれば `{ .. }` は不要 49 | import A.min as minimum1 50 | import A.{ min => minimum2 } // Scala 2 ではこうだった 51 | 52 | // 両方使える 53 | minimum1.tap(println) 54 | minimum2.tap(println) 55 | 56 | // `{ .. }` が不要なので簡潔に書ける 57 | import scala.annotation as ann 58 | import java as j 59 | 60 | val list = j.util.ArrayList[String] 61 | 62 | println("-" * 10) 63 | 64 | // import するものが複数の場合はこう 65 | import A.{ min as minimum, `*` as multiply } 66 | 67 | minimum.tap(println) 68 | multiply.tap(println) 69 | 70 | println("-" * 50) 71 | 72 | // import するものを除外したい場合 73 | def no3(): Unit = 74 | import Predef.{ augmentString as _, * } // augmentString 以外のすべてを import する 75 | 76 | // StringOps に変換されないのでコンパイルエラー 77 | // val a = "-" * 50 // value * is not a member of String 78 | -------------------------------------------------------------------------------- /step05/import-export/src/main/scala/com/github/shinharad/gettingStartedWithScala3/PackageObjectMigrationWithExportClauses.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package exportClausesPackageObject 3 | 4 | //--- 5 | // パッケージオブジェクトからトップレベル定義への移行によってできたギャップを埋める 6 | 7 | // 移行前 8 | // Facade クラスを継承したパッケージオブジェクトを使用 9 | def no1(): Unit = 10 | import com.github.shinharad.gettingStartedWithScala3.before._ 11 | 12 | foo() 13 | 14 | // 移行後 15 | // Facade クラスを export でトップレベルで定義したものを使用 16 | def no2(): Unit = 17 | import com.github.shinharad.gettingStartedWithScala3.after._ 18 | 19 | foo() 20 | 21 | -------------------------------------------------------------------------------- /step05/import-export/src/main/scala/com/github/shinharad/gettingStartedWithScala3/after/ToplevelDefinitions.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package after 3 | 4 | import com.github.shinharad.gettingStartedWithScala3.before 5 | 6 | //--- 7 | // 移行後 8 | // パッケージオブジェクトが継承していた Facade クラスを export 句でトップレベルに定義する 9 | 10 | val facade: Facade = new Facade 11 | 12 | export facade.foo 13 | -------------------------------------------------------------------------------- /step05/import-export/src/main/scala/com/github/shinharad/gettingStartedWithScala3/before/package.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | 3 | //--- 4 | // 移行前 5 | // パッケージオブジェクトに共通的な Facade クラスを継承している 6 | 7 | class Facade: 8 | def foo(): Int = ??? 9 | 10 | package object before extends Facade 11 | -------------------------------------------------------------------------------- /step05/open-class/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpenClassEncryptedWriter.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package openClass 3 | 4 | trait Encryptable[T]: 5 | def encrypt(x: T): T = ??? 6 | 7 | //--- 8 | // open class を継承してるのでOK 9 | 10 | class EncryptedWriter1[T: Encryptable] extends WriterOpen[T]: 11 | override def send(x: T) = 12 | super.send(summon[Encryptable[T]].encrypt(x)) 13 | 14 | //--- 15 | // open ではない class を継承してる場合は警告メッセージ 16 | // (コメントアウトを外すと警告メッセージが表示される) 17 | 18 | // class EncryptedWriter2[T: Encryptable] extends Writer[T]: 19 | // override def send(x: T) = 20 | // super.send(summon[Encryptable[T]].encrypt(x)) 21 | 22 | //--- 23 | // `scala.language.adhocExtensions` を import すると 24 | // アドホックに継承できる(テストダブルや一時的なパッチ適用など、ご利用は計画的に) 25 | 26 | import scala.language.adhocExtensions 27 | 28 | class EncryptedWriter3[T: Encryptable] extends Writer[T]: 29 | override def send(x: T) = 30 | super.send(summon[Encryptable[T]].encrypt(x)) 31 | -------------------------------------------------------------------------------- /step05/open-class/src/main/scala/com/github/shinharad/gettingStartedWithScala3/OpenClassWriter.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package openClass 3 | 4 | // open を明示してるので、別のファイルで継承可能 5 | open class WriterOpen[T]: 6 | def send(x: T) = println(x) 7 | def sendAll(xs: T*) = xs.foreach(send) 8 | 9 | // open を明示していないので、基本的に継承不可 10 | class Writer[T]: 11 | def send(x: T) = println(x) 12 | def sendAll(xs: T*) = xs.foreach(send) 13 | -------------------------------------------------------------------------------- /step05/open-class/src/main/scala/com/github/shinharad/gettingStartedWithScala3/PatternBindings.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package patternBindings 3 | 4 | import scala.util.chaining.* 5 | 6 | //--- 7 | // Bindings in Pattern Definitions 8 | 9 | // 以下のコードは、右辺の Any を pattern bindings で、具象型の String へ設定している 10 | @main def no1(): Unit = 11 | 12 | // このコードは実行時エラーとなるが、Scala 2 ではコンパイル時に検出ができなかった 13 | // Scala 3.1 からはこのような場合に警告メッセージを出力するようになった 14 | // (Scala 3.0 の場合は、`-source future` を設定することで警告メッセージが出力されるようになる) 15 | 16 | // val xs: List[Any] = List(1, 2, 3) 17 | // val (x: String) :: _ = xs 18 | 19 | // Scala 3.1 の pattern bindings は、右辺の型がパターンの型に適合する場合にのみ許可される 20 | val pair = (1, true) 21 | val (x, y) = pair 22 | 23 | // 時にはパターンが不適合であっても、pattern bindings したいことがある 24 | // 例えば、ある時点でリストが空でないことがわかった場合、次のように pattern bindings する 25 | def no2(): Unit = 26 | val elems = Seq(1, 2, 3) 27 | 28 | // これは警告メッセージが出力される 29 | // val first :: rest = elems 30 | 31 | // この場合、右辺に @unchecked を付けることで警告メッセージを避けることができる 32 | val first :: rest = elems: @unchecked // OK 33 | 34 | // ただし、elems が空になる場合は、実行時エラーとなる 35 | 36 | //--- 37 | // Pattern Bindings in for Expressions 38 | 39 | // 同様の変更は、for 式のパターンにも適用される 40 | @main def no3(): Unit = 41 | val elems: List[Any] = List((1, 2), "hello", (3, 4)) 42 | 43 | // パターンの型 (Any, Any) は、右辺の式の型である Any よりも具象化されているため、 44 | // Scala 3.1 ではコンパイル時に警告メッセージを出力する 45 | 46 | // for (x, y) <- elems yield (y, x) 47 | 48 | // なお、上記のコードは、Scala 2 では、 49 | // パターン (x, y) にマッチするタプル型の要素のみを保持するようにリスト elems がフィルタリングされる 50 | // Scala 3.1 では、パターンの前に `case` を付けることで、フィルタリングすることができる 51 | 52 | // for case (x, y) <- elems yield (y, x) // returns List((2, 1), (4, 3)) 53 | // .tap(println) 54 | 55 | // (ただ、警告メッセージは消えないっぽい) 56 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ContextBounds.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package contextBounds 3 | 4 | import givenInstances.Ord 5 | import givenInstances.given 6 | 7 | //--- 8 | // Context Bounds 9 | 10 | // 型パラメータに依存するコンテキストパラメータの共通パターンを表現するための省略形 11 | 12 | def max[T](x: T, y: T)(using ord: Ord[T]): T = 13 | if ord.compare(x, y) < 0 then y else x 14 | 15 | // Context Bounds を使用するとこのように書き換えることができる 16 | def no1(): Unit = 17 | // using でコンテキストパラメータを渡す場合 18 | // def maximum[T](xs: List[T])(using Ord[T]): T = xs.reduceLeft(max) 19 | 20 | // Context Bounds を使用した場合 21 | def maximum[T: Ord](xs: List[T]): T = xs.reduceLeft(max) 22 | 23 | //--- 24 | // Context Bounds から生成されるコンテキストパラメータは、含まれるメソッドやクラスの定義の最後に展開される 25 | def no2(): Unit = 26 | trait C1[T] 27 | trait C2[T] 28 | trait C3[T] 29 | type R 30 | type V 31 | 32 | // メソッドの場合 33 | def f1[T: C1 : C2, U: C3](x: T)(using y: U, z: V): R = ??? 34 | // このように展開される 35 | def f2[T, U](x: T)(using y: U, z: V)(using C1[T], C2[T], C3[U]): R = ??? 36 | 37 | // クラスの場合 38 | class class1[T: C1 : C2, U: C3](x: T)(using y: U, z: V) 39 | // このように展開される 40 | class class2[T, U](x: T)(using y: U, z: V)(using C1[T], C2[T], C3[U]) 41 | 42 | //--- 43 | // Context Bounds はサブタイプ境界と組み合わせることができる 44 | // 両方が存在する場合は、サブタイプ境界を先に書く 45 | 46 | def no3(): Unit = 47 | trait C[T] 48 | type B 49 | type R 50 | 51 | def g[T <: B : C](x: T): R = ??? 52 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Functors1.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package functors1 3 | 4 | //--- 5 | // Functors 6 | 7 | // - ある型の Functor は、その型の値を写像(mapped over)する機能を提供する 8 | // - つまり、値の形状(shepe)を維持したまま値を変換する関数を適用することができる 9 | 10 | object typeclass: 11 | 12 | // Functor 型クラスの定義 13 | // - F は型コンストラクタで、型パラメータを与えると、具体的な型となる 14 | // - F[_] と定義することで、F が任意の型を引数に取ることを表現している 15 | // - 型コンストラクタ F[_] の Functor は、型 A => B の関数 f を適用することで、F[A] を F[B] に変換する能力を表している 16 | trait Functor[F[_]]: 17 | def map[A, B](x: F[A], f: A => B): F[B] 18 | 19 | object instances: 20 | import typeclass.Functor 21 | 22 | // List 型に対して Functor のインスタンスを定義 23 | given Functor[List] with 24 | def map[A, B](x: List[A], f: A => B): List[B] = 25 | x.map(f) // List already has a `map` method 26 | 27 | @main def no1(): Unit = 28 | import typeclass.Functor 29 | import instances.given 30 | 31 | println("-" * 50) 32 | 33 | def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = 34 | assert(expected == summon[Functor[F]].map(original, mapping)) 35 | 36 | // このインスタンスがスコープに入っていると、Functor が期待される場所では、コンパイラは List を使用することを受け入れる 37 | assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1") 38 | 39 | println("-" * 50) 40 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Functors2.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package functors2 3 | 4 | // Extension Methods を使用することで、Functor のインスタンスに直接 map 関数を生やすことができる 5 | // そうすれば、summon[Functor[F]] を取り除くことができる 6 | object typeclass: 7 | trait Functor[F[_]]: 8 | extension [A](x: F[A]) 9 | def map[B](f: A => B): F[B] 10 | 11 | object instance: 12 | import typeclass.Functor 13 | 14 | given Functor[List] with 15 | extension [A](xs: List[A]) 16 | def map[B](f: A => B): List[B] = 17 | xs.map(f) // List already has a `map` method 18 | 19 | @main def no1(): Unit = 20 | import typeclass.Functor 21 | import instance.given 22 | 23 | println("-" * 50) 24 | 25 | def assertTransformation[F[_]: Functor, A, B](expected: F[B], original: F[A], mapping: A => B): Unit = 26 | assert(expected == original.map(mapping)) 27 | 28 | assertTransformation(List("a1", "b1"), List("a", "b"), elt => s"${elt}1") 29 | 30 | println("-" * 50) 31 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/GivenInstance.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package givenInstances 3 | 4 | //--- 5 | // Given instances (またはシンプルに "givens") は、特定の型に対する振る舞いを定義し、 6 | // コンテキストパラメータへの引数を合成する役割を果たす 7 | 8 | trait Ord[T]: 9 | def compare(x: T, y: T): Int 10 | extension (x: T) def < (y: T) = compare(x, y) < 0 11 | extension (x: T) def > (y: T) = compare(x, y) > 0 12 | 13 | // Ord[Int] の givens 14 | given intOrd: Ord[Int] with 15 | def compare(x: Int, y: Int) = 16 | if x < y then -1 else if x > y then +1 else 0 17 | 18 | // Ord[List[T]] の givens 19 | // この givens が存在するためには using で指定した Ord[T] の givens が存在しなければならない 20 | given listOrd[T](using ord: Ord[T]): Ord[List[T]] with 21 | def compare(xs: List[T], ys: List[T]): Int = (xs, ys) match 22 | case (Nil, Nil) => 0 23 | case (Nil, _) => -1 24 | case (_, Nil) => +1 25 | case (x :: xs1, y :: ys1) => 26 | val fst = ord.compare(x, y) 27 | if fst != 0 then fst else compare(xs1, ys1) 28 | 29 | //--- 30 | // Anonymous Givens 31 | 32 | // givens は、名前を省略することができる 33 | object no2: 34 | given Ord[Int] with 35 | def compare(x: Int, y: Int) = ??? 36 | 37 | given [T](using Ord[T]): Ord[List[T]] with 38 | def compare(xs: List[T], ys: List[T]): Int = ??? 39 | 40 | // この場合、コンパイラは自動的にこのような名前を付与する 41 | given_Ord_Int 42 | given_Ord_List 43 | 44 | //--- 45 | // Alias Givens 46 | 47 | // givens のエイリアスを定義することができる 48 | def no3(): Unit = 49 | import java.util.concurrent.ForkJoinPool 50 | import scala.concurrent.ExecutionContext 51 | 52 | // import scala.concurrent.JavaConversions.asExecutionContext 53 | // given global: ExecutionContext = ForkJoinPool() // method asExecutionContext in object JavaConversions is deprecated since 2.13.0: Use `ExecutionContext.fromExecutorService` instead 54 | 55 | // givens のエイリアスはメモ化される 56 | // つまり、最初にアクセスしたときに評価され、以降はそれを使い回す 57 | // この操作はスレッドセーフ 58 | given global: ExecutionContext = ExecutionContext.fromExecutorService(ForkJoinPool()) 59 | 60 | // エイリアスは匿名にすることもできる 61 | def no4(): Unit = 62 | trait Position 63 | class EnclosingTree: 64 | def position: Position = ??? 65 | 66 | val enclosingTree = EnclosingTree() 67 | 68 | // Anonymous 69 | given Position = enclosingTree.position 70 | 71 | trait Config 72 | trait Factory 73 | class MemoizingFactory(config: Config) extends Factory 74 | 75 | // Anonymous 76 | given (using config: Config): Factory = MemoizingFactory(config) 77 | 78 | //--- 79 | // Pattern-Bound Given Instances 80 | 81 | // given は、パターンの中にも書くことができる 82 | def no5(): Unit = 83 | 84 | trait Context 85 | val applicationContexts: Seq[Context] = ??? 86 | def f(using Context): String = ??? 87 | 88 | // for の中で 89 | for given Context <- applicationContexts do f 90 | 91 | // パターンマッチの中で 92 | val pair: (Context, Int) = ??? 93 | pair match 94 | case (ctx @ given Context, y) => f 95 | 96 | //--- 97 | // Negated Givens 98 | 99 | // scala.util.NotGiven を使用することで、 100 | // givens がスコープに存在しないかどうかを調べることができる 101 | 102 | @main def no6(): Unit = 103 | import scala.util.NotGiven 104 | 105 | trait Tagged[A] 106 | 107 | case class Foo[A](value: Boolean) 108 | object Foo: 109 | given fooTagged[A](using Tagged[A]): Foo[A] = Foo(true) 110 | given fooNotTagged[A](using NotGiven[Tagged[A]]): Foo[A] = Foo(false) 111 | 112 | given Tagged[Int]() 113 | 114 | println(summon[Foo[Int]].value) 115 | println(summon[Foo[String]].value) 116 | 117 | assert(summon[Foo[Int]].value) // fooTagged is found 118 | assert(!summon[Foo[String]].value) // fooNotTagged is found 119 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ImplementingTypeClasses.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package implementingTypeClasses 3 | 4 | // Show(Haskellではよく知られた型クラス)の実装例 5 | 6 | //--- 7 | // The type class 8 | 9 | // 型クラスを作成する最初のステップは、1つまたは複数の抽象メソッドを持つパラメータ化された trait を宣言すること 10 | // Showable は show という名前のメソッドを1つだけ持っているので、このように書く。 11 | 12 | // 型クラス 13 | trait Showable[A]: 14 | extension(a: A) def show: String 15 | 16 | // 単なる trait 17 | trait Show: 18 | def show: String 19 | 20 | // 重要なポイント 21 | // - Showable のような型クラスは、どの型に対して show の実装を提供するかを示す型パラメータ A を取る 22 | // 一方、Show のような通常の trait はそうではない 23 | // - ある型 A に show の機能を追加するには、通常の trait では A が show を拡張していることが必要だが、型クラスでは Showable[A] の実装が必要になる 24 | // - Showable と Show のメソッド呼び出し構文を同じにするために、 Showable.show を extension method として定義している 25 | 26 | //--- 27 | // Implement concrete instances 28 | 29 | // 次のステップでは、アプリケーション内のどのクラスに対して Showable が機能するかを決定し、 30 | // そのクラスに対してそのビヘイビアを実装する 31 | // 例えば、Person クラスに Showable を実装するには、次のようにする。 32 | 33 | case class Person(firstName: String, lastName: String) 34 | 35 | // Showable[Person] に given instance を定義する 36 | given Showable[Person] with 37 | extension(p: Person) def show: String = 38 | s"${p.firstName} ${p.lastName}" 39 | 40 | //--- 41 | // Using the type class 42 | 43 | // もしScalaがすべてのクラスに toString メソッドを用意していなかったら、 44 | // このテクニックを使って、Stringに変換できるようにしたい任意のクラスに Showable のビヘイビアを追加することができる。 45 | 46 | @main def no1(): Unit = 47 | val person = Person("John", "Doe") 48 | println(person.show) 49 | 50 | //--- 51 | // Writing methods that use the type class 52 | 53 | // 継承と同様に、Showable を型パラメータとして使用するメソッドを定義することができる 54 | 55 | def showAll[S: Showable](xs: List[S]): Unit = 56 | xs.foreach(x => println(x.show)) 57 | 58 | @main def no2(): Unit = 59 | showAll(List(Person("Jane", "xxx"), Person("Mary", "yyy"))) 60 | 61 | //--- 62 | // A type class with multiple methods 63 | 64 | // 複数のメソッドを持つ型クラスを作成する場合、次のようになる 65 | 66 | trait HasLegs[A]: 67 | extension (a: A) 68 | def walk(): Unit 69 | def run(): Unit 70 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ImplicitConversions.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package implicitConversions 3 | 4 | import scala.language.implicitConversions 5 | 6 | trait Token 7 | final class KeyWord(value: String) extends Token 8 | 9 | //--- 10 | // Scala 3 から Implicit Conversion は、scala.Conversion を使用する 11 | 12 | def no1(): Unit = 13 | 14 | // String から Token への Implicit Conversion を定義 15 | given Conversion[String, Token] with 16 | def apply(str: String): Token = KeyWord(str) 17 | 18 | val a: Token = "abc" 19 | 20 | //--- 21 | // エイリアスを使えばより簡潔に表現できる 22 | 23 | def no2(): Unit = 24 | 25 | given Conversion[String, Token] = KeyWord(_) 26 | 27 | val a: Token = "abc" 28 | 29 | //--- 30 | // Implicit Conversion は、次の3つの場合にコンパイラによって自動的に適用される 31 | 32 | // 1. 式 `e` が型 `T` を持ち、`T` が式の期待される型 `S` に適合しない場合 33 | def no3(): Unit = 34 | class T 35 | class S 36 | 37 | given Conversion[T, S] = _ => S() 38 | 39 | val e: T = T() 40 | val s: S = e 41 | 42 | // 2. `e.m` では、`e` は `T` 型だが、`T` はメンバー `m` を定義していない場合 43 | def no4(): Unit = 44 | class T 45 | class S(val m: String) 46 | 47 | given Conversion[T, S] = _ => S("abc") 48 | 49 | val e: T = T() 50 | 51 | val m = e.m 52 | 53 | // 3. `T` 型の `e` を持つ `e.m(args)` において、`T` が `m` という名前のメンバを定義していても、 54 | // そのメンバーのどれもが引数 `args` に適用できない場合 55 | def no5(): Unit = 56 | class T: 57 | def m(): String = ??? 58 | class S: 59 | def m(args: Array[String]): String = ??? 60 | 61 | given Conversion[T, S] = _ => S() 62 | 63 | val e: T = T() 64 | 65 | e.m(Array("abc")) 66 | 67 | //--- 68 | // magnet パターン 69 | // オーバーロードされたメソッドを定義する代わりに、 70 | // Implicit Conversionを使用することで、様々な引数の型に対応できる 71 | 72 | def no6(): Unit = 73 | import scala.concurrent.Future 74 | 75 | case class HttpResponse() 76 | case class StatusCode() 77 | 78 | object Completions: 79 | 80 | enum CompletionArg: 81 | case Error(s: String) 82 | case Response(f: Future[HttpResponse]) 83 | case Status(code: Future[StatusCode]) 84 | 85 | object CompletionArg: 86 | given fromString: Conversion[String, CompletionArg] = Error(_) 87 | given fromFuture: Conversion[Future[HttpResponse], CompletionArg] = Response(_) 88 | given fromStatusCode: Conversion[Future[StatusCode], CompletionArg] = Status(_) 89 | 90 | import CompletionArg.* 91 | 92 | def complete[T](arg: CompletionArg) = arg match 93 | case Error(s) => ??? 94 | case Response(f) => ??? 95 | case Status(code) => ??? 96 | 97 | import Completions.* 98 | 99 | complete("result") 100 | complete(Future.successful(HttpResponse())) 101 | complete(Future.successful(StatusCode())) 102 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/ImportingGivens.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package importingGivens 3 | 4 | //--- 5 | // Importing Givens 6 | // givens をインポートするために使用する 7 | 8 | object A: 9 | class TC 10 | given tc: TC = ??? 11 | def f(using TC) = ??? 12 | 13 | // `import A.*` は、A の givens 以外のすべてをインポートする 14 | def no1(): Unit = 15 | import A.* 16 | // import A.TC 17 | // import A.f 18 | 19 | // TC を参照できる 20 | val t = TC() 21 | 22 | // TC の givens をインポートしていないはずなのに使えてしまう... 23 | f 24 | 25 | def g(using TC) = ??? 26 | 27 | 28 | // `import A.given` は、A の givens のみをインポートする 29 | def no2(): Unit = 30 | import A.given 31 | 32 | // TC は参照できない 33 | // val t = TC() 34 | 35 | // TC の givens はインポートしているので使える 36 | A.f 37 | 38 | // `import A.{ given, * }` は、すべてのメンバをインポートする 39 | def no3(): Unit = 40 | import A.{ given, * } 41 | 42 | // TC を参照できる 43 | val t = TC() 44 | 45 | // TC の givens をインポートしてるので使える 46 | f 47 | 48 | //--- 49 | // Importing By Type 50 | 51 | // 個別の givens をインポートする場合(By-typeインポート) 52 | def no4(): Unit = 53 | import A.TC 54 | 55 | // By-typeインポート 56 | import A.given TC 57 | 58 | // 複数の型の givens をインポートする場合はこのように書く 59 | // import A.{given T1, ..., given Tn} 60 | 61 | import scala.concurrent.ExecutionContext 62 | trait Monoid[A] 63 | 64 | object Instances: 65 | given intOrd: Ordering[Int] = ??? 66 | given listOrd[T: Ordering]: Ordering[List[T]] = ??? 67 | given ec: ExecutionContext = ??? 68 | given im: Monoid[Int] = ??? 69 | 70 | // パラメータ化された型の givens をインポートするには、ワイルドカード引数を使用する 71 | def no5(): Unit = 72 | import Instances.{ given Ordering[?], given ExecutionContext } 73 | 74 | intOrd 75 | listOrd 76 | ec 77 | 78 | // この場合、Monoid はインポートしていないので参照できない 79 | // im 80 | 81 | // By-typeインポートは、By-nameインポートと混在できる 82 | def no6(): Unit = 83 | // import 句の中に By-type と By-name が混在する場合、By-type は最後に書く必要がある 84 | import Instances.{ im, given Ordering[?] } 85 | 86 | // 末尾にBy-nameを書くとコンパイルエラー 87 | // import Instances.{ given Ordering[?], im } 88 | 89 | intOrd 90 | listOrd 91 | im 92 | 93 | // ExecutionContext はインポートしていないので参照できない 94 | // ec 95 | 96 | //--- 97 | // Migration 98 | 99 | object B: 100 | class TC 101 | implicit val tc: TC = ??? 102 | def f(implicit t: TC) = ??? 103 | 104 | // Importing Givens は、マイグレーションを考慮して古いスタイルの暗黙的な定義もスコープに含める 105 | // ただし、Scala 3.1 以降では、非推奨となり段階的に削除される 106 | // つまり、古いスタイルの暗黙的な定義をしているライブラリは、Scala 3.1 以降で使えなくなる可能性があるってこと? 107 | def no7(): Unit = 108 | import B.given 109 | 110 | B.f 111 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/Monads.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package monads 3 | 4 | import com.github.shinharad.gettingStartedWithScala3.functors2.typeclass.Functor 5 | 6 | //--- 7 | // Monads 8 | 9 | object typeclass: 10 | 11 | // F[_] 型の Monad は、Functor[F] にさらに2つの演算子を加えたもの 12 | // - flatMap: A => F[B] 型の関数が与えられたとき、F[A] を F[B] に変換する 13 | // - pure: 単一の値 A から F[A] を生成する(ドキュメントでは、Applicative Functorが省略されている) 14 | trait Monad[F[_]] extends Functor[F]: 15 | def pure[A](x: A): F[A] 16 | 17 | extension [A](x: F[A]) 18 | def flatMap[B](f: A => F[B]): F[B] 19 | def map[B](f: A => B) = x.flatMap(f.andThen(pure)) 20 | 21 | //--- 22 | // List 23 | 24 | object instance1: 25 | import typeclass.Monad 26 | 27 | given listMonad: Monad[List] with 28 | def pure[A](x: A): List[A] = 29 | List(x) 30 | extension [A](xs: List[A]) 31 | def flatMap[B](f: A => List[B]): List[B] = 32 | xs.flatMap(f) // rely on the existing `flatMap` method of `List` 33 | 34 | @main def no1(): Unit = 35 | import typeclass.Monad 36 | import instance1.given 37 | 38 | println("-" * 50) 39 | 40 | assert(List(1) == summon[Monad[List]].pure(1)) 41 | 42 | def assertTransformation[F[_]: Monad, A, B](expected: F[B], original: F[A], mapping: A => F[B]): Unit = 43 | assert(expected == original.flatMap(mapping)) 44 | 45 | assertTransformation(List(2, 4, 6), List(1, 2, 3), x => List(x * 2)) 46 | 47 | println("-" * 50) 48 | 49 | //--- 50 | // Option 51 | 52 | object instance2: 53 | import typeclass.Monad 54 | 55 | given optionMonad: Monad[Option] with 56 | def pure[A](x: A): Option[A] = 57 | Option(x) 58 | extension [A](xo: Option[A]) 59 | def flatMap[B](f: A => Option[B]): Option[B] = xo match 60 | case Some(x) => f(x) 61 | case None => None 62 | 63 | @main def no2(): Unit = 64 | import typeclass.Monad 65 | import instance2.given 66 | 67 | println("-" * 50) 68 | 69 | assert(Option(1) == summon[Monad[Option]].pure(1)) 70 | 71 | def assertTransformation[F[_]: Monad, A, B](expected: F[B], original: F[A], mapping: A => F[B]): Unit = 72 | assert(expected == original.flatMap(mapping)) 73 | 74 | assertTransformation(Option(2), Option(1), x => Option(x * 2)) 75 | 76 | println("-" * 50) 77 | 78 | //--- 79 | // Reader 80 | 81 | trait Config 82 | 83 | def compute(i: Int)(config: Config): String = ??? 84 | def show(str: String)(config: Config): Unit = ??? 85 | 86 | object instance3: 87 | import typeclass.Monad 88 | 89 | type ConfigDependent[Result] = Config => Result 90 | 91 | given configDependentMonad: Monad[ConfigDependent] with 92 | 93 | def pure[A](x: A): ConfigDependent[A] = 94 | config => x 95 | 96 | extension [A](x: ConfigDependent[A]) 97 | def flatMap[B](f: A => ConfigDependent[B]): ConfigDependent[B] = 98 | config => f(x(config))(config) 99 | 100 | def no3(): Unit = 101 | import typeclass.Monad 102 | import instance3.given 103 | 104 | def before(i: Int)(config: Config): Unit = 105 | show(compute(i)(config))(config) 106 | 107 | def after(i: Int): Config => Unit = 108 | compute(i).flatMap(show) 109 | 110 | //--- 111 | // Reader 112 | // Type Lambdas を使用した場合 113 | 114 | object instance4: 115 | import typeclass.Monad 116 | 117 | given configDependentMonad: Monad[[Result] =>> Config => Result] with 118 | 119 | def pure[A](x: A): Config => A = 120 | config => x 121 | 122 | extension [A](x: Config => A) 123 | def flatMap[B](f: A => Config => B): Config => B = 124 | config => f(x(config))(config) 125 | 126 | def no4(): Unit = 127 | import typeclass.Monad 128 | import instance4.given 129 | 130 | def before(i: Int)(config: Config): Unit = 131 | show(compute(i)(config))(config) 132 | 133 | def after(i: Int): Config => Unit = 134 | compute(i).flatMap(show) 135 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/SemigroupsAndMonoids.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package semigroupsAndMonoids 3 | 4 | //--- 5 | // Semigroups and monoids 6 | 7 | trait Semigroup[T]: 8 | extension (x: T) def combine (y: T): T 9 | 10 | trait Monoid[T] extends Semigroup[T]: 11 | def unit: T 12 | 13 | // Monoid 型クラスのインスタンス 14 | // この Monoid 型クラスの String 型と Int 型に対するインスタンスの実装は以下の通り 15 | given Monoid[String] with 16 | extension (x: String) def combine (y: String): String = x.concat(y) 17 | def unit: String = "" 18 | 19 | given Monoid[Int] with 20 | extension (x: Int) def combine (y: Int): Int = x + y 21 | def unit: Int = 0 22 | 23 | // この Monoid は、このように Context bound として使用できる 24 | def combineAll[T: Monoid](xs: List[T]): T = 25 | xs.foldLeft(summon[Monoid[T]].unit)(_.combine(_)) 26 | 27 | // summon[...] を無くすには、Monoid のコンパニオンオブジェクトをこのように定義する 28 | object Monoid: 29 | def apply[T](using m: Monoid[T]) = m 30 | 31 | def combineAll2[T: Monoid](xs: List[T]): T = 32 | xs.foldLeft(Monoid[T].unit)(_.combine(_)) 33 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/TypeLambdas.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package typeLambdas 3 | 4 | import com.github.shinharad.gettingStartedWithScala3.functors2.* 5 | 6 | //--- 7 | // Type Lambdas 8 | 9 | def no1(): Unit = 10 | import typeclass.Functor 11 | 12 | // Option や List は、型パラメータが1つなので Functor に設定できる 13 | type F1 = Functor[Option] // OK 14 | type F2 = Functor[List] // OK 15 | 16 | // Map は型パラメータが2つなので、そのままでは Functor に設定できない 17 | // type F3 = Functor[Map] // !! 18 | 19 | // 代わりにKeyの型を確定させたtypeエイリアスを Functor に設定する 20 | type IntKeyMap[A] = Map[Int, A] 21 | type F3 = Functor[IntKeyMap] 22 | 23 | // typeエイリアスを定義せずに、部分適用で Functor に設定できないだろうか? 24 | // しかし、部分適用は関数には適用できるが、型パラメータには適用できない 25 | val cube = Math.pow(_: Double, 3) 26 | cube(2) 27 | // type F4 = Functor[Map[Int, _]] // !! 28 | 29 | // そこで、Type Lambdas を使用する 30 | type F5 = Functor[[A] =>> Map[Int, A]] 31 | 32 | // Scala 2 では、このような書き方をしていた 33 | type F6 = Functor[({ type T[A] = Map[Int, A] })#T] 34 | 35 | // もしくは、kind-projectorを使用してこのように書いていた 36 | // ("-Ykind-projector" を設定すれば、Scala 3 でもこのように書くことができる) 37 | type F7 = Functor[Map[Int, *]] 38 | 39 | //--- 40 | // given の定義で Type Lambdas を使用する 41 | def no2(): Unit = 42 | import typeclass.Functor 43 | 44 | given Functor[[A] =>> Map[Int, A]] with 45 | extension [A](m: Map[Int, A]) 46 | def map[B](f: A => B): Map[Int, B] = 47 | m.map((k, v) => (k, f(v))) 48 | -------------------------------------------------------------------------------- /step06/src/main/scala/com/github/shinharad/gettingStartedWithScala3/UsingClauses.scala: -------------------------------------------------------------------------------- 1 | package com.github.shinharad.gettingStartedWithScala3 2 | package usingClauses 3 | 4 | import givenInstances.Ord 5 | 6 | //--- 7 | // Using Clauses 8 | 9 | // Scala 2 の implicit parameter に代わるもので、 10 | // メソッドに対して using 句を使用してコンテキストパラメータを暗黙的に指定する 11 | 12 | object no1: 13 | import givenInstances.given 14 | 15 | def max[T](x: T, y: T)(using ord: Ord[T]): T = 16 | if ord.compare(x, y) < 0 then y else x 17 | 18 | // Ord[T] の givens がスコープ内にあるので省略できる 19 | max(2, 3) 20 | max(List(1, 2, 3), Nil) 21 | 22 | // もしも明示する場合は、このように書く 23 | max(2, 3)(using intOrd) 24 | 25 | //--- 26 | // Anonymous Context Parameters 27 | 28 | // using 句の名前は省略できる 29 | // コンテキストパラメータの名前は、多くの状況で他のコンテキストパラメータの合成された引数でのみ使用されるため、明示する必要はない 30 | 31 | object no2: 32 | import no1.max 33 | 34 | def maximum[T](xs: List[T])(using Ord[T]): T = 35 | xs.reduceLeft(max) 36 | 37 | // Varargパラメータは、using 句をサポートしない 38 | object no3: 39 | import no1.* 40 | 41 | // def maximum[T](xs: List[T])(using Ord[T]*): T = ??? // NG 42 | 43 | //--- 44 | // Inferring Complex Arguments 45 | 46 | object no4: 47 | import no1.max 48 | import no2.maximum 49 | import givenInstances.given 50 | 51 | val xs = List(1, 2, 3) 52 | 53 | def descending[T](using asc: Ord[T]): Ord[T] = new Ord[T]: 54 | def compare(x: T, y: T) = asc.compare(y, x) 55 | 56 | // minimum メソッドの右辺は、maximum(xs) への明示的な引数として descending を渡している 57 | def minimum[T](xs: List[T])(using Ord[T]) = 58 | maximum(xs)(using descending) 59 | 60 | // この設定により、以下の呼び出しはすべて最後の呼び出しに正規化される 61 | minimum(xs) 62 | maximum(xs)(using descending) 63 | // maximum(xs)(using descending(using listOrd)) 64 | // maximum(xs)(using descending(using listOrd(using intOrd))) 65 | 66 | //--- 67 | // Summoning Instances 68 | // summon は特定の型の given を返す 69 | 70 | // Ord[List[Int]] の given instance はこのように取得する 71 | def no5(): Unit = 72 | import givenInstances.given 73 | 74 | summon[Ord[List[Int]]] 75 | 76 | summon[Ord[List[Int]]] 77 | .compare(List(1, 2, 3), List(1, 2, 3)) 78 | 79 | // Scala 2 の implicitly は将来的に非推奨となり廃止される 80 | implicitly[Ord[List[Int]]] 81 | .compare(List(1, 2, 3), List(1, 2, 3)) 82 | 83 | //--- 84 | // 複数のコンテキストパラメータを指定する場合 85 | 86 | def no6(): Unit = 87 | trait C1[T] 88 | trait C2[T] 89 | 90 | def f[T](x: T)(using C1[T], C2[T]): T = ??? 91 | 92 | //--- 93 | // using は、Scala 2 の implicit parameter の構文上の欠点を解決している 94 | 95 | def no7(): Unit = 96 | trait Context 97 | given context: Context = ??? 98 | 99 | // Scala 2 の implicit parameter 100 | def currentMapLegacy(implicit ctx: Context): Map[String, Int] = ??? 101 | 102 | // currentMapLegacy("abc") // NG 103 | currentMapLegacy.apply("abc") // OK 104 | 105 | // Scala 3 の using 106 | def currentMap(using Context): Map[String, Int] = ??? 107 | 108 | currentMap("abc") // OK 109 | currentMap.apply("abc") // OK 110 | 111 | //--- 112 | // Scala 3 へのマイグレーションのために、Scala 2 の暗黙の定義との相互運用性を確認する 113 | 114 | // Scala 2 の implicit 定義でも using 句に渡すことができる 115 | def no8(): Unit = 116 | def max[T](x: T, y: T)(using ord: Ord[T]): T = 117 | if ord.compare(x, y) < 0 then y else x 118 | 119 | implicit val intOrd: Ord[Int] = new Ord[Int]: 120 | def compare(x: Int, y: Int) = 121 | if x < y then -1 else if x > y then +1 else 0 122 | 123 | max(2, 3) 124 | 125 | // given instance を Scala 2 の implicit parameter に渡すことができる 126 | def no9(): Unit = 127 | import givenInstances.given 128 | 129 | def max[T](x: T, y: T)(implicit ord: Ord[T]): T = 130 | if ord.compare(x, y) < 0 then y else x 131 | 132 | max(2, 3) 133 | --------------------------------------------------------------------------------