├── .git-blame-ignore-revs
├── .github
├── mergify.yml
└── workflows
│ └── scala.yml
├── .gitignore
├── .scalafix.conf
├── .scalafmt.conf
├── README.md
├── akka-stream
├── README.md
└── src
│ └── test
│ └── scala
│ └── dev
│ └── nomadblacky
│ └── scala_examples
│ └── akka_stream
│ └── SourceOperatorsSpec.scala
├── basics
├── README.md
└── src
│ └── test
│ ├── resources
│ └── xml_spec_01.html
│ └── scala
│ └── dev
│ └── nomadblacky
│ └── scala_examples
│ └── basics
│ ├── ClassSpec.scala
│ ├── EnumInScalaSpec.scala
│ ├── ExceptionSpec.scala
│ ├── ForSpec.scala
│ ├── FunctionSpec.scala
│ ├── FutureSpec.scala
│ ├── LazySpec.scala
│ ├── MatchSpec.scala
│ ├── OptionSpec.scala
│ ├── PartialFunctionSpec.scala
│ ├── StringInterpolationSpec.scala
│ ├── TraitSpec.scala
│ ├── TupleSpec.scala
│ ├── VariousFeaturesSpec.scala
│ └── XmlSpec.scala
├── build.sbt
├── collections
├── README.md
└── src
│ └── test
│ └── scala
│ └── dev
│ └── nomadblacky
│ └── scala_examples
│ └── collections
│ ├── IterableSpec.scala
│ ├── ListSpec.scala
│ ├── MapSpec.scala
│ ├── SetSpec.scala
│ └── TraversableSpec.scala
├── data
└── hightemp.txt
├── legacy
└── src
│ └── test
│ ├── java
│ └── org
│ │ └── nomadblacky
│ │ └── scala
│ │ └── samples
│ │ └── with_java
│ │ ├── JavaClass.java
│ │ └── UseScala.java
│ ├── resources
│ ├── application.conf
│ ├── logback.xml
│ └── org
│ │ └── nomadblacky
│ │ └── scala
│ │ └── samples
│ │ └── xml
│ │ └── xml_spec_01.html
│ └── scala
│ └── org
│ └── nomadblacky
│ └── scala
│ └── samples
│ ├── akka
│ ├── http
│ │ └── AkkaHttpSpec.scala
│ └── streams
│ │ └── AkkaStreamsSpec.scala
│ ├── ammonite
│ └── AmmoniteSpec.scala
│ ├── best_practice
│ ├── DesignPatternsInScalaSpec.scala
│ └── EffectiveScalaSpec.scala
│ ├── collections
│ ├── IterableSpec.scala
│ ├── ListSpec.scala
│ ├── MapSpec.scala
│ ├── SetSpec.scala
│ ├── TraversableSpec.scala
│ └── TupleSpec.scala
│ ├── event
│ └── understanding_scala
│ │ ├── UnderstandingScalaFormulaSpec.scala
│ │ ├── UnderstandingScalaImplicitSpec.scala
│ │ ├── UnderstandingScalaTrapSpec.scala
│ │ └── UnderstandingScalaTypeSpec.scala
│ ├── exceptions
│ ├── ExceptionSpec.scala
│ └── OptionSpec.scala
│ ├── functional
│ └── programming
│ │ └── in
│ │ └── scala
│ │ ├── chapter02
│ │ └── Chapter02Spec.scala
│ │ ├── chapter03
│ │ ├── Chapter03Spec.scala
│ │ └── MyList.scala
│ │ ├── chapter06
│ │ ├── Chapter06Spec.scala
│ │ ├── RNG.scala
│ │ ├── SimpleRNG.scala
│ │ └── State.scala
│ │ ├── chapter10
│ │ ├── Chapter10Spec.scala
│ │ └── Monoid.scala
│ │ └── chapter11
│ │ ├── Chapter11Spec.scala
│ │ └── Functor.scala
│ ├── libraries
│ ├── cats
│ │ └── CatsSpec.scala
│ ├── jfreechart
│ │ └── JFreeChartSpec.scala
│ ├── scalaz
│ │ ├── DisjunctionSpec.scala
│ │ └── NonEmptyListSpec.scala
│ ├── scalikejdbc
│ │ ├── ScalikeJDBCSpec.scala
│ │ └── ScalikeJDBCUnitTestSpec.scala
│ └── scopt
│ │ └── ScoptSpec.scala
│ ├── nlp100
│ ├── Chapter01Spec.scala
│ └── Chapter02Spec.scala
│ ├── parsercombinator
│ └── ParserCombinatorSpec.scala
│ ├── play
│ └── ws
│ │ └── PlayWSSpec.scala
│ ├── s99
│ └── S99Spec.scala
│ ├── scala
│ ├── JVMSpec.scala
│ ├── TypeClassSpec.scala
│ └── TypeParameterSpec.scala
│ ├── scala_kansai
│ └── y2018
│ │ ├── N01PatternMatching.scala
│ │ └── N02ForSyntax.scala
│ ├── testing
│ └── ScalaCheckSpec.scala
│ ├── with_java
│ └── WithJavaSpec.scala
│ └── xml
│ └── XmlSpec.scala
├── project
├── build.properties
└── plugins.sbt
├── reporter
└── src
│ ├── main
│ └── scala
│ │ └── org
│ │ └── nomadblacky
│ │ └── scala
│ │ └── reporter
│ │ ├── TableOfContentsReporter.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── org
│ └── nomadblacky
│ └── scala
│ └── reporter
│ └── UsingSpec.scala
├── sbt
├── scala3
├── README.md
└── src
│ └── test
│ └── scala
│ └── dev
│ └── nomadblacky
│ └── scala_examples
│ └── scala3
│ └── Scala3Test.scala
├── shapeless
├── README.md
└── src
│ └── test
│ └── scala
│ └── dev
│ └── nomadblacky
│ └── scala_examples
│ └── shapeless
│ └── ShapelessSpec.scala
├── tmp
└── sample.txt
└── update-readme.sh
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # Scala Steward: Reformat with scalafmt 3.7.5
2 | a599d16bf210a097a9b95fea06d96944752f2d9c
3 |
4 | # Scala Steward: Reformat with scalafmt 3.8.2
5 | a92ccbb81c858d333d0d6f290e5b3569a8522bd4
6 |
7 | # Scala Steward: Reformat with scalafmt 3.9.7
8 | 66b21014b9d2fbde721748792a18e3314ad14088
9 |
--------------------------------------------------------------------------------
/.github/mergify.yml:
--------------------------------------------------------------------------------
1 | pull_request_rules:
2 | - name: assign and label scala-steward's PRs
3 | conditions:
4 | - author=scala-steward
5 | actions:
6 | assign:
7 | users: [NomadBlacky]
8 | label:
9 | add: [dependencies]
10 | - name: merge scala-steward's PRs
11 | conditions:
12 | - author=scala-steward
13 | - check-success=scalafmt
14 | - check-success=test (11)
15 | actions:
16 | merge:
17 | method: squash
18 |
--------------------------------------------------------------------------------
/.github/workflows/scala.yml:
--------------------------------------------------------------------------------
1 | name: Scala CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | scalafmt:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - uses: coursier/cache-action@v6
11 | - name: Set up JDK 11
12 | uses: actions/setup-java@v2
13 | with:
14 | distribution: adopt
15 | java-version: 11
16 | - name: Check scalafmt
17 | run: ./sbt scalafmtCheckAll scalafmtSbtCheck
18 | test:
19 | strategy:
20 | matrix:
21 | java:
22 | - 11
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v2
26 | - uses: coursier/cache-action@v6
27 | - name: Set up JDK ${{ matrix.java }}
28 | uses: actions/setup-java@v2
29 | with:
30 | distribution: adopt
31 | java-version: ${{ matrix.java }}
32 | - name: Run tests
33 | run: ./sbt test
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/scala,intellij+all
3 |
4 | ### Intellij+all ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | # User-specific stuff:
9 | .idea/**/workspace.xml
10 | .idea/**/tasks.xml
11 | .idea/dictionaries
12 |
13 | # Sensitive or high-churn files:
14 | .idea/**/dataSources/
15 | .idea/**/dataSources.ids
16 | .idea/**/dataSources.xml
17 | .idea/**/dataSources.local.xml
18 | .idea/**/sqlDataSources.xml
19 | .idea/**/dynamic.xml
20 | .idea/**/uiDesigner.xml
21 |
22 | # Gradle:
23 | .idea/**/gradle.xml
24 | .idea/**/libraries
25 |
26 | # CMake
27 | cmake-build-debug/
28 |
29 | # Mongo Explorer plugin:
30 | .idea/**/mongoSettings.xml
31 |
32 | ## File-based project format:
33 | *.iws
34 |
35 | ## Plugin-specific files:
36 |
37 | # IntelliJ
38 | /out/
39 |
40 | # mpeltonen/sbt-idea plugin
41 | .idea_modules/
42 |
43 | # JIRA plugin
44 | atlassian-ide-plugin.xml
45 |
46 | # Cursive Clojure plugin
47 | .idea/replstate.xml
48 |
49 | # Ruby plugin and RubyMine
50 | /.rakeTasks
51 |
52 | # Crashlytics plugin (for Android Studio and IntelliJ)
53 | com_crashlytics_export_strings.xml
54 | crashlytics.properties
55 | crashlytics-build.properties
56 | fabric.properties
57 |
58 | ### Intellij+all Patch ###
59 | # Ignores the whole idea folder
60 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
61 |
62 | .idea/
63 |
64 | ### Scala ###
65 | *.class
66 | *.log
67 |
68 |
69 | # End of https://www.gitignore.io/api/scala,intellij+all
70 |
71 | .ensime
72 | .ensime_cache/
73 | target/
74 | tmp/
75 | sample.mv.db
76 | .bsp
77 | sample.trace.db
78 |
--------------------------------------------------------------------------------
/.scalafix.conf:
--------------------------------------------------------------------------------
1 | rules = [
2 | RemoveUnused
3 | ]
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | version = 3.9.7
2 | runner.dialect = scala213
3 | preset = defaultWithAlign
4 | maxColumn = 120
5 |
6 | fileOverride {
7 | "glob:**/scala3/**" {
8 | runner.dialect = scala3
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/akka-stream/README.md:
--------------------------------------------------------------------------------
1 | ## Scala の基本
2 |
3 | Scala で Reactive Streams を実現する Akka Streams についてまとめます。
4 |
5 | ### コード例
6 |
7 | Scala Version: 2.13.x
8 |
9 | + [Source のオペレータ](src/test/scala/dev/nomadblacky/scala_examples/akka_stream/SourceOperatorsSpec.scala)
10 |
--------------------------------------------------------------------------------
/akka-stream/src/test/scala/dev/nomadblacky/scala_examples/akka_stream/SourceOperatorsSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.akka_stream
2 |
3 | import akka.actor.ActorSystem
4 | import akka.stream.scaladsl.{Concat, Merge, Source}
5 | import akka.stream.testkit.scaladsl.TestSink
6 | import org.scalatest.funspec.AnyFunSpec
7 |
8 | class SourceOperatorsSpec extends AnyFunSpec {
9 |
10 | override def suiteName: String = "Source のオペレータ"
11 |
12 | private implicit val system: ActorSystem = ActorSystem("source-operations")
13 |
14 | it("apply - Iterable から Source をつくる") {
15 | val source = Source(Seq(1, 2, 3))
16 | source.runWith(TestSink[Int]()).request(3).expectNext(1, 2, 3).expectComplete()
17 | }
18 |
19 | describe("combine - 複数の Source を結合する") {
20 | val source1 = Source(1 to 3)
21 | val source2 = Source(4 to 6)
22 | val source3 = Source(7 to 9)
23 |
24 | it("Merge - 順不同で結合する") {
25 | val combined = Source.combine(source1, source2, source3)(Merge(_))
26 | combined.runWith(TestSink[Int]()).request(9).expectNextUnorderedN(1 to 9).expectComplete()
27 | }
28 |
29 | it("Concat - Source の順番で結合する") {
30 | val combined = Source.combine(source1, source2, source3)(Concat(_))
31 | combined.runWith(TestSink[Int]()).request(9).expectNextN(1 to 9).expectComplete()
32 | }
33 | }
34 |
35 | it("cycle - 要素を繰り返す") {
36 | val source = Source.cycle(() => Seq(1, 2, 3).iterator)
37 | source.runWith(TestSink[Int]()).request(6).expectNext(1, 2, 3, 1, 2, 3)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/basics/README.md:
--------------------------------------------------------------------------------
1 | ## Scala の基本
2 |
3 | Scala の基本的な機能や標準ライブラリについてまとめます。
4 |
5 | ### コード例
6 |
7 | Scala Version: 2.13.x
8 |
9 | + [Scala のクラス](src/test/scala/dev/nomadblacky/scala_examples/basics/ClassSpec.scala)
10 | + [Scala で列挙体 (Enum) を扱う](src/test/scala/dev/nomadblacky/scala_examples/basics/EnumInScalaSpec.scala)
11 | + [例外処理](src/test/scala/dev/nomadblacky/scala_examples/basics/ExceptionSpec.scala)
12 | + [for 式](src/test/scala/dev/nomadblacky/scala_examples/basics/ForSpec.scala)
13 | + [Scala の関数](src/test/scala/dev/nomadblacky/scala_examples/basics/FunctionSpec.scala)
14 | + [Future による非同期プログラミング](src/test/scala/dev/nomadblacky/scala_examples/basics/FutureSpec.scala)
15 | + [lazy による遅延評価](src/test/scala/dev/nomadblacky/scala_examples/basics/LazySpec.scala)
16 | + [match 式](src/test/scala/dev/nomadblacky/scala_examples/basics/MatchSpec.scala)
17 | + [Option - 値が存在しない可能性があることを表すクラス](src/test/scala/dev/nomadblacky/scala_examples/basics/OptionSpec.scala)
18 | + [Partial Function (部分関数)](src/test/scala/dev/nomadblacky/scala_examples/basics/PartialFunctionSpec.scala)
19 | + [String Interpolation (文字列の補完)](src/test/scala/dev/nomadblacky/scala_examples/basics/StringInterpolationSpec.scala)
20 | + [Trait (トレイト)](src/test/scala/dev/nomadblacky/scala_examples/basics/TraitSpec.scala)
21 | + [Tuple (タプル)](src/test/scala/dev/nomadblacky/scala_examples/basics/TupleSpec.scala)
22 | + [XMLを扱う](src/test/scala/dev/nomadblacky/scala_examples/basics/XmlSpec.scala)
23 | + [その他の細かな機能やAPI](src/test/scala/dev/nomadblacky/scala_examples/basics/VariousFeaturesSpec.scala)
24 |
--------------------------------------------------------------------------------
/basics/src/test/resources/xml_spec_01.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | title
4 |
5 |
6 | h1
7 | h2
8 |
9 |
p1
10 |
p2
11 |
p3
12 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/ClassSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import com.github.ghik.silencer.silent
4 | import org.scalatest.funspec.AnyFunSpec
5 |
6 | /** Created by blacky on 16/10/20.
7 | */
8 | class ClassSpec extends AnyFunSpec {
9 |
10 | override def suiteName: String = "Scalaのクラス"
11 |
12 | it("require ... 引数を検証する") {
13 | class Programmer(val language: String) {
14 | require(language != null && language.nonEmpty)
15 | }
16 |
17 | new Programmer("Scala")
18 |
19 | intercept[IllegalArgumentException] {
20 | new Programmer("")
21 | }
22 | }
23 |
24 | it("unapplyを使用したパターンマッチ") {
25 | class Designer
26 | object Designer {
27 | def unapply(arg: Any): Boolean = {
28 | if (arg.isInstanceOf[Designer]) true else false
29 | }
30 | }
31 | class Programmer(val language: String)
32 | object Programmer {
33 | def apply(language: String): Programmer = new Programmer(language)
34 | def unapply(arg: Programmer): Option[String] = Some(arg.language)
35 | }
36 |
37 | def matchTest(value: Any): String = {
38 | value match {
39 | case Designer => "designer"
40 | case Programmer("scala") => "scala programmer"
41 | case _ => "other"
42 | }
43 | }
44 |
45 | assert(matchTest(Designer) == "designer")
46 | assert(matchTest(Programmer("scala")) == "scala programmer")
47 | assert(matchTest(Programmer("java")) == "other")
48 | }
49 |
50 | it("caseクラスとパターンマッチ") {
51 | case class Point(x: Int, y: Int)
52 |
53 | def matchTest(value: Any): Int = {
54 | value match {
55 | case Point(10, y) => y + 1
56 | case _ => 0
57 | }
58 | }
59 |
60 | val p1 = Point(10, 30)
61 | assert(matchTest(p1) == 31)
62 |
63 | val p2 = p1.copy(y = 99)
64 | assert(p2.x == 10)
65 | assert(matchTest(p2) == 100)
66 | }
67 |
68 | it("抽象クラス") {
69 | abstract class Engineer {
70 | def work(): String
71 | def study(): String = "Do study."
72 | }
73 |
74 | class Programmer(name: String) extends Engineer {
75 | def work() = "%s is writing a program code.".format(name)
76 | // 具象メソッドをオーバーライドするときは override キーワードが必須
77 | override def study() = "%s is studying programming.".format(name)
78 | }
79 |
80 | val programmer = new Programmer("Tom")
81 | assert(programmer.work() == "Tom is writing a program code.")
82 | assert(programmer.study() == "Tom is studying programming.")
83 | }
84 |
85 | it("メソッドをvalでオーバーライドする") {
86 | // Scala ではフィールドとメソッドに同じ名前で定義できない。
87 | // 引数なしのメソッドを val でオーバーライドできるようにするため。
88 |
89 | abstract class Parent {
90 | def method: String = "Parent."
91 | }
92 |
93 | class Child extends Parent {
94 | override val method: String = "Child."
95 | }
96 |
97 | assert(new Child().method == "Child.")
98 | }
99 |
100 | it("sealedによる継承制限とパターンマッチ") {
101 | // クラス定義に sealed を付けられたクラスは、別ファイル内で定義されたクラスから継承できない。
102 | // (ただし、シールドクラスを継承したクラスは別ファイルから継承可能)
103 |
104 | sealed abstract class TeamMember
105 | case class Programmer() extends TeamMember
106 | case class Designer() extends TeamMember
107 | case class Manager() extends TeamMember
108 |
109 | val member: TeamMember = new Programmer
110 |
111 | @silent("match may not be exhaustive")
112 | def foo(): Unit = {
113 | // パターンマッチを記述する際に役に立つ
114 | member match {
115 | case p: Programmer => println("Programmer.")
116 | case d: Designer => println("Designer.")
117 | // Managerに対するcase式または、デフォルト式が足りてないので警告が出る。
118 | }
119 |
120 | // 警告が煩わしい場合は、 @unchecked で無効にできる
121 | (member: @unchecked) match {
122 | case p: Programmer => println("Programmer.")
123 | case d: Designer => println("Designer.")
124 | }
125 | }
126 | foo()
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/EnumInScalaSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 | import org.scalatest.matchers.should.Matchers
5 |
6 | class EnumInScalaSpec extends AnyFunSpec with Matchers {
7 |
8 | override def suiteName: String = "Scalaで列挙型を扱う"
9 |
10 | it("Enumerationを使った方法") {
11 | // 単純な列挙型を扱う場合はEnumerationを使う
12 | object Enum extends Enumeration {
13 | val One, Two, Three = Value
14 | }
15 |
16 | // 列挙型はそれぞれidを持つ
17 | import Enum._
18 | One.id shouldBe 0
19 | Two.id shouldBe 1
20 |
21 | // 列挙体は順序を持つ
22 | One < One shouldBe false
23 | One <= One shouldBe true
24 | One < Two shouldBe true
25 | One <= Two shouldBe true
26 |
27 | // valuesで列挙体のSetが返る
28 | Enum.values shouldBe Enum.ValueSet(One, Two, Three)
29 | }
30 |
31 | it("余計な `Value` を排除する") {
32 | object Enum extends Enumeration {
33 | // `type` を使うことで、型につく余計な `Value` を排除できる
34 | type Enum = Value
35 | val One, Two, Three = Value
36 | }
37 | import Enum._
38 |
39 | // val enumOne: Enum.Value = Enum.One
40 | // ↑これが余計
41 | val enumOne: Enum = One
42 |
43 | enumOne.id shouldBe 0
44 | }
45 |
46 | it("列挙型に値をもたせる") {
47 | object Enum extends Enumeration {
48 | type Enum = Value
49 |
50 | // Valを継承したクラスを定義して、列挙型とする
51 | case class EnumVal(toDouble: Double) extends Val
52 |
53 | val One = EnumVal(1.0)
54 | val Two = EnumVal(2.0)
55 | val Three = EnumVal(3.0)
56 | }
57 | import Enum._
58 |
59 | One.toDouble shouldBe 1.0
60 | Two.toDouble shouldBe 2.0
61 | Three.toDouble shouldBe 3.0
62 |
63 | // しかし、valuesで取ってきた列挙型はEnum.Valueのため、直接値は取れない…
64 | val enumOne: Enum.Value = Enum.values.head
65 | // コンパイルエラー
66 | // enumOne.toDouble
67 |
68 | // こう…?
69 | enumOne match {
70 | case EnumVal(d) => d shouldBe 1.0
71 | case _ => fail()
72 | }
73 | }
74 |
75 | it("sealed trait と case objectを使った列挙型") {
76 | sealed trait Enum
77 | case object One extends Enum
78 | case object Two extends Enum
79 | case object Three extends Enum
80 |
81 | val enum: Enum = One
82 | enum match {
83 | case One => succeed
84 | case Two => fail()
85 | case Three => fail()
86 | }
87 | }
88 |
89 | it("sealed abstract classで値と振る舞いをもたせる") {
90 | // この場合、列挙型というよりも代数的データ型である
91 | sealed abstract class Enum(val value: Int) {
92 | def describe: String = s"value: $value"
93 | }
94 | case object One extends Enum(1)
95 | case object Two extends Enum(2)
96 | final case class Three(otherName: String) extends Enum(3)
97 |
98 | val enum: Enum = Three("drei")
99 | enum match {
100 | case One => fail()
101 | case Two => fail()
102 | case t: Three =>
103 | t.value shouldBe 3
104 | t.otherName shouldBe "drei"
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/ExceptionSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | /** try-catch-finally
6 | *
7 | * try { 式 } catch { case 変数:例外クラスの型 => 例外処理の式 ... } finally { 式 }
8 | */
9 | class ExceptionSpec extends AnyFunSpec {
10 |
11 | override def suiteName: String = "例外処理"
12 |
13 | it("基本的なtry-catch-finally") {
14 | try {
15 | "a".toInt
16 | } catch {
17 | case e: NumberFormatException => {
18 | println("exception!")
19 | -1
20 | }
21 | } finally {
22 | println("finally!")
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/ForSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import com.github.ghik.silencer.silent
4 | import org.scalatest.funspec.AnyFunSpec
5 |
6 | import scala.collection.mutable.ListBuffer
7 |
8 | /** for ( [ジェネレータ] [フィルタ] ) 式
9 | *
10 | * ※フィルタは任意 ※処理に複数の式を記述したい場合{}で囲む
11 | */
12 | class ForSpec extends AnyFunSpec {
13 |
14 | override def suiteName: String = "for式 (for内包表記)"
15 |
16 | it("コレクションのイテレート") {
17 | for (i <- List(1, 2, 3, 4, 5)) {
18 | println(i)
19 | }
20 | }
21 |
22 | it("要素のフィルタ") {
23 | val l: ListBuffer[Int] = ListBuffer.empty
24 | for (i <- List(1, 2, 3, 4, 5) if 3 < i) {
25 | l += i
26 | }
27 | assert(l == ListBuffer(4, 5))
28 | }
29 |
30 | it("yield ... forの結果を新しいコレクションとして返す") {
31 | val l = for (name <- List("Taro", "Jiro")) yield "I am " + name
32 | assert(l == List("I am Taro", "I am Jiro"))
33 | }
34 |
35 | it("for式のforeach展開") {
36 | class Hoge {
37 | type A = Int
38 | def foreach[U](f: A => U): Unit = {
39 | f(1); f(2); f(3)
40 | }
41 | }
42 | // yield を持たないfor式は、foreachとして展開される
43 | // (foreachを実装していればfor式で利用できる)
44 | for (x <- new Hoge) {
45 | println(x)
46 | }
47 | // new Hoge.foreach(println(_)) と同義
48 | }
49 |
50 | it("for式のmap展開") {
51 | class Hoge {
52 | type A = Int
53 | def map[B](f: A => B): IterableOnce[B] = {
54 | List(f(1), f(2), f(3))
55 | }
56 | }
57 | // yield を持つfor式は、mapとして展開される
58 | val result = for (x <- new Hoge) yield x.toString * x
59 | // new Hoge().map{ x => x.toString * x } と同義
60 |
61 | assert(result == List("1", "22", "333"))
62 | }
63 |
64 | /** すべてのfor式は、map,flatMap,withFilterの3つの高階関数で表現できる。
65 | */
66 | it("ジェネレータが1個のときの変換") {
67 | val a = for (x <- List(1, 2, 3)) yield x * 2
68 | val b = List(1, 2, 3).map(x => x * 2)
69 | assert(a == b)
70 | }
71 |
72 | it("1個のジェネレータと1個のフィルターで始まるfor式の変換") {
73 | val a = for (x <- List(1, 2, 3) if 1 < x) yield x * 2
74 | val b = for (x <- List(1, 2, 3) withFilter (x => 1 < x)) yield x * 2
75 | val c = List(1, 2, 3) withFilter (x => 1 < x) map (x => x * 2)
76 | assert(a == b)
77 | assert(b == c)
78 | }
79 |
80 | it("2個のジェネレータで始まるfor式の変換") {
81 | val a = for {
82 | x <- List(1, 2)
83 | y <- List(3, 4)
84 | } yield x * y
85 | val b = List(1, 2).flatMap(x => for (y <- List(3, 4)) yield x * y)
86 | val c = List(1, 2).flatMap(x => List(3, 4).map(y => x * y))
87 | assert(a == b)
88 | assert(b == c)
89 | }
90 |
91 | it("ジェネレータに含まれるパターンの変換 - タプルの場合") {
92 | val a = for ((x, y) <- List((1, 2), (3, 4))) yield x * y
93 | val b = List((1, 2), (3, 4)).map { case (x, y) => x * y }
94 | assert(a == b)
95 | }
96 |
97 | it("ジェネレータに含まれるパターンの変換 - その他パターンの場合") {
98 | @silent("match may not be exhaustive")
99 | def foo(): Unit = {
100 | val list = List(Some(1), None, Some(3))
101 | val a = for (Some(i) <- list) yield i
102 | // desugared
103 | val b = list.withFilter {
104 | case Some(i) => true
105 | case _ => false
106 | } map { case Some(i) =>
107 | i
108 | }
109 | assert(a == b)
110 | }
111 | foo()
112 | }
113 |
114 | it("[Sample] 2つのコレクションを同じ順序で取り出して処理する") {
115 | val l = for ((a, b) <- (List(1, 2, 3) zip List(3, 4, 5))) yield a * b
116 | assert(l == List(3, 8, 15))
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/FunctionSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | class FunctionSpec extends AnyFunSpec {
6 |
7 | override def suiteName: String = "Scalaの関数"
8 |
9 | it("関数リテラル") {
10 | val func = (x: Int, y: Int) => x + y
11 | assert(func(1, 2) == 3)
12 | }
13 |
14 | it("Functionトレイト") {
15 | val func: Function2[Int, Int, Int] = (x: Int, y: Int) => x + y
16 | assert(func(1, 2) == 3)
17 | }
18 |
19 | it("関数を引数に取る関数") {
20 | def calc(f: (Int, Int) => Int, num: Int): Int = f(num, num)
21 | val result = calc((x, y) => x * y, 2)
22 | assert(result == 4)
23 | }
24 |
25 | it("プレースホルダ構文") {
26 | // プレースホルダ…
27 | // 実際の内容をあとから挿入するために、仮に確保した場所のこと。
28 |
29 | // 関数の引数の記述を _ で省略できる。
30 | val func1: (Int, Int) => Int = (x: Int, y: Int) => x + y
31 | val func2: (Int, Int) => Int = _ + _
32 | val func3 = (_: Int) + (_: Int)
33 | assert(func1(1, 2) == func2(1, 2))
34 | assert(func2(1, 2) == func3(1, 2))
35 | assert(func1(1, 2) == func3(1, 2))
36 | }
37 |
38 | it("プレースホルダ構文2") {
39 | val func1: (String) => String = _.replaceAll("hoge", "foo")
40 | val func2 = (_: String).replaceAll(_: String, _: String)
41 | assert(func1("aaahogebbb") == func2("aaahogebbb", "hoge", "foo"))
42 | }
43 |
44 | it("関数の部分適用") {
45 | // 引数の一部を確定させた、新しい関数を返すこと。
46 |
47 | def add(x: Int, y: Int): Int = x + y
48 | // 関数の後ろに _ をつけると、関数オブジェクトに変換できる。
49 | val addFunc = add _
50 | val func = addFunc(_: Int, 5)
51 |
52 | assert(func(3) == add(3, 5))
53 | }
54 |
55 | it("関数のカリー化") {
56 | // 複数の引数を持つ関数を、ひとつの引数をもつ関数のチェーンに変換すること。
57 | def sumCurry(a: Int) = (b: Int) => (c: Int) => a + b + c
58 | def func1 = sumCurry(1)
59 | def func2 = func1(2)
60 | def value = func2(3)
61 |
62 | assert(sumCurry(1)(2)(3) == 6)
63 | assert(value == 6)
64 | }
65 |
66 | it("関数のカリー化2") {
67 | def sum(a: Int, b: Int, c: Int) = a + b + c
68 | def currySum = (sum _).curried
69 |
70 | assert(currySum(1)(2)(3) == 6)
71 | }
72 |
73 | it("関数の引数を遅延評価する") {
74 | def myWhile(conditional: => Boolean)(f: => Unit): Unit = {
75 | if (conditional) {
76 | f
77 | myWhile(conditional)(f)
78 | }
79 | }
80 |
81 | var count = 0
82 | myWhile(count < 5) {
83 | count += 1
84 | }
85 | assert(count == 5)
86 | }
87 |
88 | it("scratch01") {
89 | def f(a: Int)(b: Int): Int = a + b
90 | def g(c: Int): Int = c + 10
91 |
92 | // これをやりたい
93 | val k = (x: Int, y: Int) => g(f(x)(y))
94 | assert(k(10, 20) == 40)
95 |
96 | // これはだめ
97 | // val l = f(_).compose(g)
98 |
99 | // method と FunctionN は異なる
100 | // method は第一級ではない (下記をいずれも満たさない)
101 | // 第一級...
102 | // * リテラルがある
103 | // * 実行時に生成できる
104 | // * 手続きや関数の結果として返すことができる
105 | // * 変数に入れて使える
106 | // * 手続きや関数に引数として与えることができる
107 | // Reference: http://tkawachi.github.io/blog/2014/11/26/1/
108 |
109 | // _ を使って明示的に変換する必要がある
110 | val l = f _ compose g
111 | assert(l(10)(20) == 40)
112 |
113 | // 翻訳
114 | val ll1: (Int) => (Int) => Int = f _
115 | val ll2: (Int) => Int = g
116 | val ll3 = ll1 compose ll2
117 | assert(ll3(10)(20) == 40)
118 | }
119 |
120 | it("scratch01 by Mr.aiya000") {
121 | def comp[A, B, C](g: B => C)(f: A => B)(x: A): C = g(f(x))
122 | def f(a: Int)(b: Int): Int = a + b
123 | def g(c: Int): Int = c + 10
124 |
125 | val k = comp(f)(g)(_)
126 | assert(k(10)(20) == 40)
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/FutureSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 | import org.scalatest.matchers.should.Matchers
5 |
6 | import scala.concurrent._
7 | import scala.concurrent.duration._
8 | import scala.util.{Failure, Success}
9 |
10 | /** Created by blacky on 17/03/31.
11 | */
12 | class FutureSpec extends AnyFunSpec with Matchers {
13 |
14 | override def suiteName: String = "Futureの使い方"
15 |
16 | /** ExecutionContextとは? ・Runnableのインスタンスを渡すと、よしなに非同期実行してくれる仕組み。 ・スレッド利用の効率化のため、タスク(Runnable)をスレッドに振り分ける役目をする。
17 | * ・実行タイミング・スレッドへの配分などは実装により異なる。 ・参考: http://mashi.hatenablog.com/entry/2014/11/24/010417
18 | * ・ExecutionContext.Implicits.globalは通常、最大でCPUの論理プロセッサ数ぶんのスレッドを立ち上げ、処理する。 ※オプションで「論理プロセッサ数 x N倍」に設定できる。
19 | */
20 | import ExecutionContext.Implicits.global
21 |
22 | it("Futureの基本") {
23 | // Future#apply で Futureインスタンスを生成
24 | // ここで、ExcutionContextが入ってくる
25 | val f = Future {
26 | // インスタンス生成と同時に別スレッドで実行される
27 | println("A")
28 | Thread.sleep(1000)
29 | println("B")
30 | "ok"
31 | }
32 | println("C")
33 |
34 | // Future#isComplete ... Futureの処理が完了したか
35 | assert(!f.isCompleted)
36 |
37 | // Future#value ... 現在時点のの値をOption[Try[T]]で取得する
38 | // 未完了 → None 完了 → Some[Try[T]]
39 | assert(f.value.isEmpty)
40 |
41 | println("D")
42 |
43 | Thread.sleep(2000)
44 |
45 | assert(f.isCompleted)
46 | assert(f.value.get.get == "ok")
47 | }
48 |
49 | it("Await ... Futureの終了を待機する") {
50 | val f = Future {
51 | Thread.sleep(1000)
52 | "ok"
53 | }
54 | // Await.result でFutureの終了を待機して結果を受け取る
55 | val result = Await.result(f, 3.seconds)
56 | assert(result == "ok")
57 |
58 | val f2: Future[String] = Future {
59 | Thread.sleep(1000)
60 | throw new RuntimeException()
61 | }
62 | // Futureの例外を受け取りたい場合は Await.ready で処理を終えてからFutureのメソッドで結果を受け取る
63 | Await.ready(f2, 3.seconds)
64 | f2.value.get match {
65 | case Success(_) => fail()
66 | case Failure(ex) => assert(ex.isInstanceOf[RuntimeException])
67 | }
68 | }
69 |
70 | it("onComplete ... コールバック関数を定義する") {
71 | val f = Future {
72 | Thread.sleep(1000)
73 | "ok"
74 | }
75 | // onComplete はExecutionContextを必要とする(implicit parameter)
76 | // これ → import ExecutionContext.Implicits.global
77 | // メインスレッド、Futureのスレッド、コールバックのスレッドは基本的に別のスレッドで実行される
78 | f.onComplete {
79 | case Success(result) => assert(result == "ok")
80 | case Failure(_) => fail()
81 | }
82 | Await.ready(f, 3.seconds)
83 | }
84 |
85 | it("map ... Futureの計算結果を処理するFutureを取得する") {
86 | { // Futureの結果がネストしてきたない
87 | val f1 = Future {
88 | "ok"
89 | }
90 | f1 onComplete {
91 | case Success(str) =>
92 | val f2 = Future {
93 | if (str == "ok") 1
94 | else 0
95 | }
96 | val result = Await.result(f2, 3.seconds)
97 | assert(result == 1)
98 | case Failure(_) => fail()
99 | }
100 | }
101 | { // mapを使うとよい
102 | val f1 = Future {
103 | "ok"
104 | }
105 | val f2: Future[Int] = f1 map { str =>
106 | if (str == "ok") 1
107 | else fail()
108 | }
109 | val result = Await.result(f2, 3.seconds)
110 | assert(result == 1)
111 | }
112 | }
113 |
114 | it("sequence ... Seq[Future] を Future[Seq] に変換する") {
115 | val futures1: Seq[Future[Int]] = Seq(Future.successful(1), Future.successful(2), Future.successful(3))
116 | val resultF1: Future[Seq[Int]] = Future.sequence(futures1)
117 | Await.result(resultF1, 1.seconds) shouldBe Seq(1, 2, 3)
118 |
119 | // Failureが含まれる場合は失敗に寄せられる
120 | val futures2: Seq[Future[Int]] =
121 | Seq(Future.successful(1), Future.failed(new RuntimeException), Future.successful(3))
122 | val resultF2: Future[Seq[Int]] = Future.sequence(futures2)
123 | a[RuntimeException] shouldBe thrownBy(Await.result(resultF2, 3.seconds))
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/LazySpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | class LazySpec extends AnyFunSpec {
6 |
7 | override def suiteName: String = "遅延評価"
8 |
9 | it("lazy ... 変数を遅延評価する") {
10 | val x = 1
11 | lazy val lazyX = { println("initialize!"); x + 1 }
12 | println(lazyX)
13 | println(lazyX)
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/MatchSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 | import org.scalatest.matchers.should.Matchers
5 |
6 | /** match式
7 | *
8 | * x match { case [選択肢1] => [xが選択肢1にmatchした時の処理] case [選択肢2] => [xが選択肢2にmatchした時の処理] case _ => [xが選択肢にmatchしなかった時の処理] }
9 | */
10 | class MatchSpec extends AnyFunSpec with Matchers {
11 |
12 | override def suiteName: String = "match式とパターンマッチング"
13 |
14 | it("基本的なマッチング") {
15 | def func(i: Int): String = i match {
16 | case 1 => "one"
17 | case 2 => "two"
18 | case _ => "other"
19 | }
20 |
21 | func(1) shouldBe "one"
22 | func(2) shouldBe "two"
23 | func(3) shouldBe "other"
24 | }
25 |
26 | it("型のマッチング") {
27 | def func(a: Any): String = a match {
28 | case i: Int => s"OK: $i"
29 | case s: String => s"OK: $s"
30 | case _ => "other"
31 | }
32 |
33 | func(1) shouldBe "OK: 1"
34 | func("hoge") shouldBe "OK: hoge"
35 | func(1.0) shouldBe "other"
36 | }
37 |
38 | it("パターンガード") {
39 | def func(i: Int): String = i match {
40 | case i: Int if 100 <= i => "more than 100"
41 | case _ => "other"
42 | }
43 |
44 | func(1) shouldBe "other"
45 | func(99) shouldBe "other"
46 | func(100) shouldBe "more than 100"
47 | }
48 |
49 | it("リストのマッチング") {
50 | val list = List(1, 2, 3, 4, 5)
51 | val actual = list match {
52 | // リストの2番目の要素を変数に束縛して、それ以外を捨てる
53 | case List(_, i, _*) => i
54 | case _ => fail()
55 | }
56 | actual shouldBe 2
57 | }
58 |
59 | it("複数のパターンをまとめる") {
60 | // `|` を使うことで、複数のパターンをまとめることができる。
61 | // switch文のフォールスルーのような場合を書きたいときに使える。
62 | def func(i: Int): String = i match {
63 | case 1 | 2 => "one or two"
64 | // パターンガードは複数書けない
65 | case x if x == 3 /* | y if y == 4 */ => "three"
66 | case y if y == 4 | y == 5 => "four or five"
67 | case _ => "other"
68 | }
69 |
70 | func(1) shouldBe "one or two"
71 | func(2) shouldBe "one or two"
72 | func(3) shouldBe "three"
73 | func(4) shouldBe "four or five"
74 | func(5) shouldBe "four or five"
75 | func(6) shouldBe "other"
76 | }
77 |
78 | it("パターンマッチでFizzBuzz") {
79 | def fizzbuzz(i: Int): Seq[String] = {
80 | (1 to i).map { x =>
81 | (x % 3, x % 5) match {
82 | case (0, 0) => "FizzBuzz"
83 | case (0, _) => "Fizz"
84 | case (_, 0) => "Buzz"
85 | case _ => x.toString
86 | }
87 | }
88 | }
89 |
90 | fizzbuzz(15) shouldBe Seq(
91 | "1",
92 | "2",
93 | "Fizz",
94 | "4",
95 | "Buzz",
96 | "Fizz",
97 | "7",
98 | "8",
99 | "Fizz",
100 | "Buzz",
101 | "11",
102 | "Fizz",
103 | "13",
104 | "14",
105 | "FizzBuzz"
106 | )
107 | }
108 |
109 | it("unapply ... 独自のパターンを定義する") {
110 | object SSH {
111 | // Optionに入ったTupleを返す、unapplyメソッドを実装する
112 | def unapply(arg: String): Option[(String, String)] = {
113 | val strs = arg.split('@')
114 | for {
115 | user <- strs.headOption
116 | host <- strs.tail.headOption
117 | } yield (user, host)
118 | }
119 | }
120 |
121 | "sshuser@192.168.3.31" match {
122 | case SSH(user, host) =>
123 | user shouldBe "sshuser"
124 | host shouldBe "192.168.3.31"
125 | case _ => fail()
126 | }
127 |
128 | "hogehoge" match {
129 | case SSH(_, _) => fail()
130 | case _ => // OK
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/OptionSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 | import org.scalatest.matchers.should.Matchers
5 |
6 | /** Created by blacky on 16/12/04.
7 | */
8 | class OptionSpec extends AnyFunSpec with Matchers {
9 |
10 | override def suiteName: String = "Option - 値が存在しない可能性があることを表すクラス"
11 |
12 | it("Optionの基本") {
13 | // Option[+A] ... 値が存在しない可能性があることを表すクラス
14 |
15 | // Some の場合は値が存在し、
16 | val o1: Option[Int] = Some(1)
17 |
18 | // None の場合は値が存在しない
19 | val o2: Option[Int] = None
20 |
21 | o1.get shouldBe 1
22 |
23 | // Noneのときにgetするとエラー
24 | assertThrows[NoSuchElementException](o2.get)
25 | }
26 |
27 | it("Optionから値を取り出す") {
28 | val o1 = Some(1)
29 | val o2 = None
30 |
31 | // 主にmatch式や、
32 | o1 match {
33 | case Some(i) => i shouldBe 1
34 | case _ => fail()
35 | }
36 | o2 match {
37 | case None =>
38 | case _ => fail()
39 | }
40 |
41 | // getOrElseを使う
42 | o1.getOrElse(2) shouldBe 1
43 | o2.getOrElse(2) shouldBe 2
44 | }
45 |
46 | it("foreach ... Optionに値が含まれる場合のみに実行させる") {
47 | val o1 = Some(1)
48 | val o2 = None
49 |
50 | // foreach を使うと、Someの場合のみに実行させるといったことができる
51 | // 要素数1のリストとイメージすると foreach という命名がわかりやすい
52 | o1 foreach { i =>
53 | i shouldBe 1
54 | }
55 | o2 foreach { _ =>
56 | fail()
57 | }
58 | }
59 |
60 | it("map ... 中身の値を関数に適用し値を変換する") {
61 | val o1: Option[Int] = Some(1)
62 | val o2: Option[Int] = None
63 |
64 | o1.map(_ + 10) shouldBe Some(11)
65 | o2.map(_ + 10) shouldBe None
66 | }
67 |
68 | it("flatMap ... 中身の値を関数に適用し、SomeならSomeを、NoneならNoneを返す") {
69 | val o1: Option[Int] = Some(1)
70 | val o2: Option[Int] = None
71 |
72 | o1.flatMap(i => Some(i + 10)) shouldBe Some(11)
73 | o1.flatMap(_ => None) shouldBe None
74 | o2.flatMap(i => Some(i + 10)) shouldBe None
75 | o2.flatMap(_ => None) shouldBe None
76 | }
77 |
78 | it("collect ... PartialFunctionを適用し、値が返る場合はその結果をSomeに包んで返す") {
79 | val o1: Option[Int] = Some(1)
80 | val o2: Option[Int] = Some(2)
81 | val none: Option[Int] = None
82 |
83 | val pf: PartialFunction[Int, String] = { case 1 =>
84 | "one"
85 | }
86 | o1.collect(pf) shouldBe Some("one")
87 | o2.collect(pf) shouldBe None
88 | none.collect(pf) shouldBe None
89 |
90 | // Someを返す関数を渡すflatMapはcollectで簡略化できる
91 | val pf2: PartialFunction[Int, Option[String]] = {
92 | case 1 => Some("one")
93 | case _ => None
94 | }
95 | o1.flatMap(pf2) shouldBe Some("one")
96 | o2.flatMap(pf2) shouldBe None
97 | none.flatMap(pf2) shouldBe None
98 | }
99 |
100 | it("fold ... Noneなら初期値を、Someなら関数を適用した値を返す") {
101 | val o1: Option[Int] = Some(10)
102 | val o2: Option[Int] = None
103 |
104 | o1.fold(-1)(_ * 10) shouldBe 100
105 | o2.fold(-1)(_ * 10) shouldBe -1
106 |
107 | // map と getOrElse を使った場合と同義
108 | o1.fold(-1)(_ * 10) shouldBe o1.map(_ * 10).getOrElse(-1)
109 | o2.fold(-1)(_ * 10) shouldBe o2.map(_ * 10).getOrElse(-1)
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/PartialFunctionSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 | import org.scalatest.matchers.should.Matchers
5 |
6 | import scala.util.{Failure, Success, Try}
7 |
8 | /** Created by blacky on 17/07/09.
9 | *
10 | * PartialFunction ... 部分関数
11 | *
12 | * 特定の引数に対してのみ結果を返す関数。 引数により値を返さない場合がある。
13 | */
14 | class PartialFunctionSpec extends AnyFunSpec with Matchers {
15 |
16 | override def suiteName: String = "部分関数"
17 |
18 | it("PartialFunctionを定義する") {
19 | val pf: PartialFunction[Int, String] = {
20 | case 1 => "one"
21 | case 2 => "two"
22 | }
23 | pf(1) shouldBe "one"
24 | pf(2) shouldBe "two"
25 | // 引数がマッチしない場合はMatchErrorが投げられる
26 | assertThrows[MatchError](pf(3))
27 | }
28 |
29 | it("caseはPartialFunctionのシンタックスシュガー") {
30 | val pf1: PartialFunction[Int, String] = { case 1 =>
31 | "one"
32 | }
33 | val pf2: PartialFunction[Int, String] = new PartialFunction[Int, String] {
34 | override def isDefinedAt(x: Int): Boolean = x match {
35 | case 1 => true
36 | case _ => false
37 | }
38 | override def apply(v1: Int): String = v1 match {
39 | case 1 => "one"
40 | case _ => throw new MatchError(v1)
41 | }
42 | }
43 |
44 | pf1.isDefinedAt(1) shouldBe true
45 | pf2.isDefinedAt(1) shouldBe true
46 |
47 | pf1.isDefinedAt(2) shouldBe false
48 | pf2.isDefinedAt(2) shouldBe false
49 |
50 | pf1(1) shouldBe "one"
51 | pf2(1) shouldBe "one"
52 |
53 | assertThrows[MatchError](pf1(2))
54 | assertThrows[MatchError](pf2(2))
55 | }
56 |
57 | it("isDefinedAt ... 引数に対して値が返される場合はtrueを返す") {
58 | val pf: PartialFunction[Int, String] = {
59 | case i if i % 2 == 0 => "even"
60 | }
61 | pf.isDefinedAt(1) shouldBe false
62 | pf.isDefinedAt(2) shouldBe true
63 | pf.isDefinedAt(3) shouldBe false
64 | pf.isDefinedAt(4) shouldBe true
65 | }
66 |
67 | it("andThen ... PartialFunction合成する") {
68 | val pf1: PartialFunction[Int, String] = {
69 | case 1 => "one"
70 | case 2 => "two"
71 | }
72 | val pf2: PartialFunction[String, Int] = {
73 | case "one" => 1
74 | case "three" => 3
75 | }
76 | val andThen = pf1 andThen pf2
77 | andThen.isDefinedAt(1) shouldBe true
78 | andThen(1) shouldBe 1
79 |
80 | andThen.isDefinedAt(2) shouldBe false
81 | assertThrows[MatchError](andThen(2))
82 |
83 | andThen.isDefinedAt(3) shouldBe false
84 | assertThrows[MatchError](andThen(3))
85 | }
86 |
87 | it("compose ... PartialFunctionを合成する") {
88 | val pf1: PartialFunction[Int, String] = {
89 | case 1 => "one"
90 | case 2 => "two"
91 | }
92 | val pf2: PartialFunction[String, Int] = {
93 | case "one" => 1
94 | case "three" => 3
95 | }
96 | // Function1( PartialFunction(x) )
97 | // という形で合成されるので、戻り値の型はFunction1となる
98 | val compose = pf1 compose pf2
99 | compose("one") shouldBe "one"
100 | assertThrows[MatchError](compose("two"))
101 | assertThrows[MatchError](compose("three"))
102 | }
103 |
104 | it("orElse ... 部分関数にマッチしなかった引数を次の部分関数にマッチさせる関数合成") {
105 | val pf1: PartialFunction[Int, String] = { case 1 =>
106 | "one"
107 | }
108 | val pf2: PartialFunction[Int, String] = { case 2 =>
109 | "two"
110 | }
111 | val pf = pf1 orElse pf2
112 |
113 | pf(1) shouldBe "one"
114 |
115 | pf.isDefinedAt(2) shouldBe true
116 | pf(2) shouldBe "two"
117 |
118 | pf.isDefinedAt(3) shouldBe false
119 | assertThrows[MatchError](pf(3))
120 | }
121 |
122 | it("runWith ... 部分関数の結果を利用する関数と合成する") {
123 | val pf: PartialFunction[Int, String] = { case 1 =>
124 | "one"
125 | }
126 | pf.runWith(s => s.length)(1) shouldBe true
127 | pf.runWith(_ => fail())(2) shouldBe false
128 | }
129 |
130 | it("lift ... 関数の結果をOptionで返す関数に変換する") {
131 | val pf: PartialFunction[Int, String] = {
132 | case 1 => "one"
133 | case 2 => "two"
134 | }
135 | val f: (Int) => Option[String] = pf.lift
136 | f(1) shouldBe Some("one")
137 | f(2) shouldBe Some("two")
138 | f(3) shouldBe None
139 | }
140 |
141 | it("applyOrElse ... 引数がマッチすればその結果を返し、マッチしなければデフォルト値を返す") {
142 | val pf: PartialFunction[Int, String] = { case 1 =>
143 | "one"
144 | }
145 | pf.applyOrElse(1, (i: Int) => i.toString) shouldBe "one"
146 | pf.applyOrElse(2, (i: Int) => i.toString) shouldBe "2"
147 | // 同義
148 | val arg = 2
149 | val result = if (pf.isDefinedAt(arg)) pf(arg) else arg.toString
150 | result shouldBe "2"
151 | }
152 |
153 | it("[Usage] ふつうの関数(全域関数)の代わりに使う") {
154 | // PartialFunctionはFunction1を継承しているので、
155 | // Function1と同様に使用することができる。
156 | val list: List[(Int, Int)] = List((1, 2), (3, 4), (5, 6))
157 |
158 | // この場合、引数がタプルであることは自明なので、このように書ける。
159 | val result1 = list.map { case (i, j) => i * j }
160 | // 全域関数を使った場合
161 | val result2 = list.map { t =>
162 | t._1 * t._2
163 | }
164 |
165 | result1 shouldBe List(2, 12, 30)
166 | result2 shouldBe result1
167 | }
168 |
169 | it("[Usage] TraversableLike#collect") {
170 | val list = List(1, 2, 3)
171 | // filterとmapを組み合わせたようなメソッド
172 | // 部分関数にマッチしたものだけを取り出し、変換する。
173 | val result = list.collect {
174 | case 1 => "one"
175 | case 2 => "two"
176 | }
177 | result shouldBe List("one", "two")
178 | }
179 |
180 | it("[Usage] TraversableOnce#collectFirst") {
181 | // 部分関数に最初にマッチした要素を取り出し、変換する。
182 | val pf: PartialFunction[Int, String] = { case 2 =>
183 | "two"
184 | }
185 | List(1, 2, 3).collectFirst(pf) shouldBe Some("two")
186 | List(4, 5, 6).collectFirst(pf) shouldBe None
187 | }
188 |
189 | it("[Usage] Try#collect") {
190 | val result1: Try[String] = Try { 1 }.collect { case 1 => "one" }
191 | val result2: Try[String] = Try { 1 }.collect { case 2 => "two" }
192 | val result3: Try[String] = Try { throw new RuntimeException(); 1 }.collect { case 1 => "one" }
193 |
194 | result1 shouldBe Success("one")
195 | result2 shouldBe a[Failure[_]]
196 | result3 shouldBe a[Failure[_]]
197 | }
198 |
199 | it("ListはPartialFunction[Int,A]をmix-inしている") {
200 | val list = List("a", "b", "c")
201 | list(0) shouldBe "a"
202 | list(1) shouldBe "b"
203 | list(2) shouldBe "c"
204 |
205 | val result = List(2, 1, 0) map list
206 | result shouldBe List("c", "b", "a")
207 | }
208 |
209 | it("MapはPartialFunction[K,V]をmix-inしている") {
210 | val map = Map("one" -> 1, "two" -> 2, "three" -> 3)
211 | map("one") shouldBe 1
212 | map("two") shouldBe 2
213 | map("three") shouldBe 3
214 |
215 | val result = List("two", "three", "four") collect map
216 | result shouldBe List(2, 3)
217 | }
218 |
219 | }
220 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/StringInterpolationSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import com.github.ghik.silencer.silent
4 | import org.scalatest.funspec.AnyFunSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | /** 文字列の補間(String Interpolation) データを利用して文字列を加工できる。
8 | */
9 | class StringInterpolationSpec extends AnyFunSpec with Matchers {
10 |
11 | override def suiteName: String = "文字列の補間 (String Interpolation)"
12 |
13 | it("s補間子") {
14 | // 文字列リテラルで直接変数を扱ったり、式を埋め込むことができる
15 | val age = 20
16 | s"I'm $age years old." shouldBe "I'm 20 years old."
17 | s"I'm ${age / 2} years old." shouldBe "I'm 10 years old."
18 | }
19 |
20 | it("f補間子") {
21 | // printf のような形式で書式を設定できる
22 | val name = "Mike"
23 | val height = 160.5
24 | f"$name%s is $height%2.2f meters tall." shouldBe "Mike is 160.50 meters tall."
25 | }
26 |
27 | it("raw補間子") {
28 | // エスケープを実行しない、生の文字列を定義する
29 | raw"hoge\nfoo" shouldBe "hoge\\nfoo"
30 | raw"hoge\nfoo" shouldBe """hoge\nfoo"""
31 | }
32 |
33 | it("自分で実装する") {
34 | import StringInterpolationSpec._
35 |
36 | val num = 1
37 | val str = "STR"
38 |
39 | spacing"hoge" shouldBe "h o g e"
40 | getParts"hoge, $num, foo, ${num + 1}, bar, $str" shouldBe Seq("hoge, ", ", foo, ", ", bar, ", "")
41 | args"hoge, $num, foo, ${num + 1}, bar, $str" shouldBe Seq(1, 2, "STR")
42 | double"hoge, $num, foo, ${num + 1}, bar, $str" shouldBe "hoge, 2, foo, 4, bar, STRSTR"
43 | }
44 | }
45 |
46 | object StringInterpolationSpec {
47 |
48 | implicit class MyStringInterpolation(val sc: StringContext) extends AnyVal {
49 | def spacing(args: Any*): String = sc.parts.flatten.mkString(" ")
50 | def getParts(args: Any*): Seq[String] = sc.parts
51 | def args(args: Any*): Seq[Any] = args
52 | @silent("match may not be exhaustive")
53 | def double(args: Any*): String = {
54 | val ai = args.iterator
55 | val f = (a: Any) =>
56 | a match {
57 | case i: Int => i * 2
58 | case s: String => s + s
59 | }
60 | sc.parts.reduceLeft { (acc, s) =>
61 | acc + f(ai.next()) + s
62 | }
63 | }
64 |
65 | // ↓ 利用するとコンパイル通らない。なんで?
66 | def parts(args: Any*): Seq[String] = sc.parts
67 | /*
68 | * [error] /home/blacky/projects/scala/samples/src/test/scala/org/nomadblacky/scala/samples/scala/StringInterpolationSpec.scala:41:13: not enough arguments for method apply: (idx: Int)String in trait SeqLike.
69 | * [error] Unspecified value parameter idx.
70 | * [error] parts"hoge, $num, foo, ${num + 1}, bar, $str"
71 | * [error] ^
72 | */
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/TraitSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import com.github.ghik.silencer.silent
4 | import org.scalatest.funspec.AnyFunSpec
5 |
6 | /** Created by blacky on 16/11/05.
7 | *
8 | * Javaでいう interface の機能。 トレイト自体に実装を持つことができる。
9 | *
10 | * trait ... (人・ものの) 特性、特色、特徴
11 | */
12 | class TraitSpec extends AnyFunSpec {
13 |
14 | override def suiteName: String = "トレイトの使い方"
15 |
16 | it("トレイトの基本") {
17 | trait Programmer {
18 | def coding: String = "I'm writing a program code."
19 | }
20 |
21 | // クラスが明示的に継承をしない場合、 extends キーワードでミックスインする
22 | class Person(val name: String) extends Programmer
23 |
24 | assert(new Person("Tom").coding == "I'm writing a program code.")
25 | }
26 |
27 | it("複数のトレイトをミックスインする") {
28 | trait Programmer {
29 | def coding: String = "I'm writing a program code."
30 | }
31 | trait Designer {
32 | def design: String = "I'm making a design."
33 | }
34 |
35 | // with キーワードでミックスインする
36 | class Person(val name: String) extends Programmer with Designer
37 | val p = new Person("Alex")
38 |
39 | assert(p.coding == "I'm writing a program code.")
40 | assert(p.design == "I'm making a design.")
41 | }
42 |
43 | it("インスタンス化のタイミングでミックスインする") {
44 | trait Programmer {
45 | def coding: String = "I'm writing a program code."
46 | }
47 | class Person(val name: String)
48 |
49 | val p = new Person("Tom") with Programmer
50 |
51 | assert(p.coding == "I'm writing a program code.")
52 | }
53 |
54 | it("同じシグネチャのメソッドを複数ミックスインした場合") {
55 | trait Programmer {
56 | def write: String = "I'm writing a program code."
57 | }
58 | trait Writer {
59 | def write: String = "I'm writing a blog."
60 | }
61 |
62 | @silent("never used")
63 | class Person extends Programmer with Writer {
64 | // メソッドを必ずオーバーライドしなくてはならない。(エラーになる。)
65 | override def write: String = "I'm writing a document."
66 | }
67 | }
68 |
69 | it("superで呼び出すトレイトのメソッドを指定する") {
70 | trait Programmer {
71 | def write: String = "I'm writing a program code."
72 | }
73 | trait Writer {
74 | def write: String = "I'm writing a blog."
75 | }
76 | class Person
77 |
78 | val p1 = new Person with Programmer with Writer {
79 | override def write: String = super[Programmer].write
80 | }
81 | val p2 = new Person with Programmer with Writer {
82 | override def write: String = super[Writer].write
83 | }
84 |
85 | assert(p1.write == "I'm writing a program code.")
86 | assert(p2.write == "I'm writing a blog.")
87 | }
88 |
89 | it("トレイトを単体で利用する") {
90 | trait T1 {
91 | def method1: String = "You are calling method1."
92 | }
93 | trait T2 {
94 | def method2: String = "You are calling method2."
95 | }
96 |
97 | // {} はトレイトをミックスインした無名クラスを作成することを意味している。
98 | val instance1 = new T1 {}
99 | assert(instance1.method1 == "You are calling method1.")
100 |
101 | // 複数のトレイトをミックスインした無名クラスを作成できる。
102 | val instance2 = new T1 with T2 // この場合の {} は不要
103 |
104 | assert(instance2.method1 == "You are calling method1.")
105 | assert(instance2.method2 == "You are calling method2.")
106 | }
107 |
108 | it("abstract override で既存のメソッドに新しい処理を追加する") {
109 | abstract class Engineer {
110 | def work(time: Int): String
111 | }
112 | class Person extends Engineer {
113 | def work(time: Int): String = {
114 | "Finish work in %d minutes.".format(time)
115 | }
116 | }
117 | trait Programmer extends Engineer {
118 | abstract override def work(time: Int): String = {
119 | // trait内でsuperを使う場合、
120 | // 具象実装をあとでミックスインする必要がある。
121 | // この場合、メソッドに abstract override が必要。
122 | super.work(time - 15)
123 | }
124 | }
125 |
126 | val p = new Person with Programmer
127 |
128 | // Programmer -> Person の順で実行される。
129 | assert(p.work(60) == "Finish work in 45 minutes.")
130 | }
131 |
132 | it("トレイトの指定順序") {
133 | abstract class Engineer {
134 | def work(time: Int): String
135 | }
136 | class Person extends Engineer {
137 | def work(time: Int): String = {
138 | "Finish work in %d minutes.".format(time)
139 | }
140 | }
141 | trait Programmer extends Engineer {
142 | abstract override def work(time: Int): String = {
143 | super.work(time - 15)
144 | }
145 | }
146 | trait Agiler extends Engineer {
147 | abstract override def work(time: Int): String = {
148 | super.work(time / 2)
149 | }
150 | }
151 |
152 | val p1 = new Person with Programmer with Agiler
153 | val p2 = new Person with Agiler with Programmer
154 |
155 | // Agiler -> Programmer
156 | assert(p1.work(60) == "Finish work in 15 minutes.")
157 | // Programmer -> Agiler
158 | assert(p2.work(60) == "Finish work in 22 minutes.")
159 |
160 | // 重なったトレイトはおおよそ右から実行される。
161 | }
162 |
163 | it("自分型アノテーション") {
164 | // class/object には自分型と言われる型が存在する。
165 | // (this の型のこと)
166 | // 自分型は全てのスーパークラスに適合しなければならない。
167 | // 適合する ... A <: B という関係(代入関係)が成り立つこと。
168 |
169 | trait MyService {
170 | def findAll(): String
171 | }
172 | trait MyServiceImpl extends MyService {
173 | override def findAll(): String = "MyServiceImpl#findAll"
174 | }
175 | class MyController {
176 | // self: Type => と書くと、自分型はTypeを継承したと認識される。
177 | self: MyService =>
178 | def execute = {
179 | findAll()
180 | }
181 | }
182 |
183 | // 自分型の条件を満たせないインスタンスを作成しようとするとコンパイルエラーになる。
184 | // val c = new MyController
185 |
186 | val c = new MyController with MyServiceImpl
187 | assert(c.execute == "MyServiceImpl#findAll")
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/TupleSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | class TupleSpec extends AnyFunSpec {
6 |
7 | override def suiteName: String = "Tuple - 任意の複数の値を保持するクラス"
8 |
9 | it("タプルを生成する") {
10 | (1, "hoge", 1.0)
11 | }
12 |
13 | it("タプルから値を取り出す") {
14 | val t = (1, "hoge", 1.0)
15 | assert(t._1 == 1)
16 | assert(t._2 == "hoge")
17 | assert(t._3 == 1.0)
18 | }
19 |
20 | it("タプルの要素に意味付けをする") {
21 | val t = ("hoge", 20)
22 |
23 | // tuple._1 などとした場合、要素についての情報を何も伝えられないので、
24 | assert(t._1 == "hoge")
25 | assert(t._2 == 20)
26 |
27 | // このようにするとよい
28 | val (name, age) = t
29 | assert(name == "hoge")
30 | assert(age == 20)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/VariousFeaturesSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import com.github.ghik.silencer.silent
4 | import org.scalatest.funspec.AnyFunSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class VariousFeaturesSpec extends AnyFunSpec with Matchers {
8 |
9 | override def suiteName: String = "その他細かな機能やAPI"
10 |
11 | describe("scala.language.Dynamic ... 動的言語のような構文をサポートする") {
12 | // Dynamicの構文を利用するのに必要
13 | import scala.language.dynamics
14 |
15 | it("applyDynamic") {
16 | case class MyMap[V](m: Map[String, V]) extends Dynamic {
17 | def applyDynamic(key: String)(default: V): V = m.getOrElse(key, default)
18 | }
19 | val mymap = MyMap(Map("aaa" -> 10))
20 |
21 | // フィールドに対してapplyしているような構文を書ける
22 | mymap.aaa(-1) shouldBe 10
23 | mymap.bbb(-1) shouldBe -1
24 | }
25 |
26 | it("applyDynamicNamed") {
27 | case class MyMap[V](m: Map[String, V]) extends Dynamic {
28 | def applyDynamicNamed[U](key: String)(args: (String, V => U)*): Map[String, U] = {
29 | val kvs = for {
30 | v <- m.lift(key).toSeq
31 | (k, f) <- args
32 | } yield (k, f(v))
33 | kvs.toMap
34 | }
35 | }
36 | val mymap = MyMap(Map("aaa" -> 10))
37 |
38 | val f = (i: Int) => i * 2
39 | val g = (i: Int) => i * 10
40 | mymap.aaa(x = f, y = g) shouldBe Map("x" -> 20, "y" -> 100)
41 | mymap.bbb(x = f, y = g) shouldBe Map()
42 | }
43 |
44 | it("selectDynamic") {
45 | case class MyMap[V](m: Map[String, V]) extends Dynamic {
46 | def selectDynamic(key: String): Option[V] = m.get(key)
47 | }
48 | val mymap = MyMap(Map("aaa" -> 10))
49 |
50 | mymap.aaa shouldBe Some(10)
51 | mymap.bbb shouldBe None
52 | }
53 |
54 | it("updateDynamic") {
55 | case class MyMap[V](m: Map[String, V]) extends Dynamic {
56 | def updateDynamic(key: String)(value: V): Map[String, V] = m + (key -> value)
57 | }
58 | val mymap = MyMap(Map("aaa" -> 10))
59 |
60 | (mymap.aaa = 100) shouldBe Map("aaa" -> 100)
61 | (mymap.bbb = 200) shouldBe Map("aaa" -> 10, "bbb" -> 200)
62 | }
63 | }
64 |
65 | describe("String Interpolation") {
66 | it("s補間子") {
67 | // 文字列リテラルで直接変数を扱ったり、式を埋め込むことができる
68 | val age = 20
69 | s"I'm $age years old." shouldBe "I'm 20 years old."
70 | s"I'm ${age / 2} years old." shouldBe "I'm 10 years old."
71 | }
72 |
73 | it("f補間子") {
74 | // printf のような形式で書式を設定できる
75 | val name = "Mike"
76 | val height = 160.5
77 | f"$name%s is $height%2.2f meters tall." shouldBe "Mike is 160.50 meters tall."
78 | }
79 |
80 | it("raw補間子") {
81 | // エスケープを実行しない、生の文字列を定義する
82 | raw"hoge\nfoo" shouldBe "hoge\\nfoo"
83 | raw"hoge\nfoo" shouldBe """hoge\nfoo"""
84 | }
85 |
86 | it("自分で実装する") {
87 | import StringInterpolations._
88 |
89 | val num = 1
90 | val str = "STR"
91 |
92 | spacing"hoge" shouldBe "h o g e"
93 | getParts"hoge, $num, foo, ${num + 1}, bar, $str" shouldBe Seq("hoge, ", ", foo, ", ", bar, ", "")
94 | args"hoge, $num, foo, ${num + 1}, bar, $str" shouldBe Seq(1, 2, "STR")
95 | double"hoge, $num, foo, ${num + 1}, bar, $str" shouldBe "hoge, 2, foo, 4, bar, STRSTR"
96 | }
97 | }
98 | }
99 |
100 | object StringInterpolations {
101 | implicit class MyStringInterpolation(val sc: StringContext) extends AnyVal {
102 | def spacing(args: Any*): String = sc.parts.flatten.mkString(" ")
103 |
104 | def getParts(args: Any*): Seq[String] = sc.parts
105 |
106 | def args(args: Any*): Seq[Any] = args
107 |
108 | @silent("match may not be exhaustive")
109 | def double(args: Any*): String = {
110 | val ai = args.iterator
111 | val f = (a: Any) =>
112 | a match {
113 | case i: Int => i * 2
114 | case s: String => s + s
115 | }
116 | sc.parts.reduceLeft { (acc, s) =>
117 | acc + f(ai.next()) + s
118 | }
119 | }
120 |
121 | // ↓ 利用するとコンパイル通らない。なんで?
122 | def parts(args: Any*): Seq[String] = sc.parts
123 | /*
124 | * [error] /home/blacky/projects/scala/samples/src/test/scala/org/nomadblacky/scala/samples/scala/StringInterpolationSpec.scala:41:13: not enough arguments for method apply: (idx: Int)String in trait SeqLike.
125 | * [error] Unspecified value parameter idx.
126 | * [error] parts"hoge, $num, foo, ${num + 1}, bar, $str"
127 | * [error] ^
128 | */
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/basics/src/test/scala/dev/nomadblacky/scala_examples/basics/XmlSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.basics
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | import scala.xml.{Elem, XML}
6 |
7 | class XmlSpec extends AnyFunSpec {
8 |
9 | override def suiteName: String = "XMLを扱う"
10 |
11 | it("xmlリテラル") {
12 | val xml = title
13 | assert(xml.getClass == classOf[Elem])
14 | assert(xml.toString() == "title
")
15 | }
16 |
17 | it("値を埋め込む") {
18 | val a = 100
19 | val xml = {a}
20 | assert(xml.toString() == "100
")
21 | }
22 |
23 | it("ファイルから読み込む") {
24 | val xml = XML.load(getClass.getResource("/xml_spec_01.html"))
25 | assert(xml.getClass == classOf[Elem])
26 | println(xml)
27 | }
28 |
29 | it("要素を取得する1") {
30 | val xml = XML.load(getClass.getResource("/xml_spec_01.html"))
31 | val body = xml \ "body"
32 | println(body)
33 | }
34 |
35 | it("要素を取得する2") {
36 | val xml = XML.load(getClass.getResource("/xml_spec_01.html"))
37 | val pTags = xml \ "body" \ "div" \ "p"
38 | assert(pTags.size == 5)
39 | println(pTags)
40 | }
41 |
42 | it("要素を取得する3") {
43 | val xml = XML.load(getClass.getResource("/xml_spec_01.html"))
44 | val aTags = xml \\ "a"
45 | assert(aTags.size == 2)
46 | println(aTags)
47 | }
48 |
49 | it("属性から要素を取得する") {
50 | val xml = XML.load(getClass.getResource("/xml_spec_01.html"))
51 | // これだとうまくいかない
52 | // val p = xml \\ "p" \ "@class"
53 | val p = xml \\ "p" \\ "@class"
54 | assert(p.size == 1)
55 | println(p)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | inThisBuild(
2 | List(
3 | semanticdbEnabled := true,
4 | semanticdbVersion := scalafixSemanticdb.revision
5 | )
6 | )
7 |
8 | val Scala2_12 = "2.12.19"
9 | val Scala2_13 = "2.13.6"
10 | val Scala3 = "3.0.1"
11 |
12 | val versions = new {
13 | val scalikejdbc = "3.3.5"
14 | val silencer = "1.17.13"
15 | }
16 |
17 | lazy val TableOfContents = config("tableOfContents").extend(Test)
18 |
19 | lazy val legacyCommonSettings = Seq(
20 | scalacOptions ++= Seq(
21 | "-Yrangepos",
22 | "-Ywarn-unused:imports"
23 | )
24 | )
25 |
26 | def createProject(path: String, scalaVer: String): sbt.Project = {
27 | val isScala3 = scalaVer.startsWith("3")
28 | Project(path, file(path))
29 | .settings(
30 | scalaVersion := scalaVer,
31 | scalacOptions ++= mkScalacOptions(isScala3),
32 | libraryDependencies ++= mkLibraryDependencies(isScala3)
33 | )
34 | }
35 |
36 | def mkScalacOptions(isScala3: Boolean): Seq[String] = {
37 | Seq(
38 | "-deprecation",
39 | "-feature",
40 | "-unchecked",
41 | "-Xfatal-warnings"
42 | ) ++ {
43 | if (isScala3) {
44 | Seq()
45 | } else {
46 | Seq(
47 | "-Xlint",
48 | "-P:silencer:checkUnused"
49 | )
50 | }
51 | }
52 | }
53 |
54 | def mkLibraryDependencies(isScala3: Boolean): Seq[ModuleID] = {
55 | Seq(
56 | "org.scalatest" %% "scalatest-funspec" % "3.2.11" % Test,
57 | "org.scalatest" %% "scalatest-shouldmatchers" % "3.2.11" % Test
58 | ) ++ {
59 | if (isScala3) {
60 | Seq()
61 | } else {
62 | Seq(
63 | compilerPlugin("com.github.ghik" % "silencer-plugin" % versions.silencer cross CrossVersion.full),
64 | "com.github.ghik" % "silencer-lib" % versions.silencer % Provided cross CrossVersion.full
65 | )
66 | }
67 | }
68 | }
69 |
70 | lazy val reporter = (project in file("reporter"))
71 | .settings(legacyCommonSettings)
72 | .settings(
73 | libraryDependencies ++= Seq(
74 | "org.scalatest" %% "scalatest" % "3.0.5"
75 | )
76 | )
77 |
78 | lazy val legacy = (project in file("legacy"))
79 | .dependsOn(reporter)
80 | .settings(legacyCommonSettings)
81 | .configs(TableOfContents)
82 | .settings(inConfig(TableOfContents)(Defaults.testTasks): _*)
83 | .settings(
84 | name := "scala_samples",
85 | scalaVersion := Scala2_12,
86 | version := "1.0",
87 | TableOfContents / testOptions ++= Seq(
88 | Tests.Argument(
89 | TestFrameworks.ScalaTest,
90 | "-C",
91 | "org.nomadblacky.scala.reporter.TableOfContentsReporter"
92 | )
93 | ),
94 | libraryDependencies ++= Seq(
95 | "org.scalactic" %% "scalactic" % "3.0.9",
96 | "org.scalatest" %% "scalatest" % "3.0.9" % "test",
97 | "org.scalacheck" %% "scalacheck" % "1.18.1" % "test",
98 | "com.github.scopt" %% "scopt" % "4.1.0",
99 | "org.pegdown" % "pegdown" % "1.6.0",
100 | "org.scala-lang" % "scala-reflect" % scalaVersion.value,
101 | "org.jfree" % "jfreechart" % "1.5.0",
102 | "com.github.pathikrit" %% "better-files" % "3.9.2",
103 | "org.scalaz" %% "scalaz-core" % "7.3.8",
104 | "com.typesafe.akka" %% "akka-http-core" % "10.2.10",
105 | "com.typesafe.akka" %% "akka-stream" % "2.6.20",
106 | "org.typelevel" %% "cats-core" % "2.13.0",
107 | "com.lihaoyi" %% "ammonite-ops" % "2.4.1",
108 | "com.typesafe.play" %% "play-ahc-ws-standalone" % "2.1.11",
109 | "org.scalikejdbc" %% "scalikejdbc" % versions.scalikejdbc,
110 | "org.scalikejdbc" %% "scalikejdbc-config" % versions.scalikejdbc,
111 | "org.scalikejdbc" %% "scalikejdbc-test" % versions.scalikejdbc % "test",
112 | "org.skinny-framework" %% "skinny-orm" % "3.1.0",
113 | "com.h2database" % "h2" % "2.3.232",
114 | "ch.qos.logback" % "logback-classic" % "1.5.18"
115 | )
116 | )
117 |
118 | lazy val akkaStream = createProject("akka-stream", Scala2_13)
119 | .settings(
120 | libraryDependencies ++= Seq(
121 | "com.typesafe.akka" %% "akka-stream" % "2.6.20",
122 | "com.typesafe.akka" %% "akka-stream-testkit" % "2.6.20" % Test
123 | )
124 | )
125 |
126 | lazy val basics = createProject("basics", Scala2_13)
127 |
128 | lazy val collections = createProject("collections", Scala2_13)
129 |
130 | lazy val scala3 = createProject("scala3", Scala3)
131 | .settings(
132 | libraryDependencies ++= Seq(
133 | "org.scalameta" %% "munit" % "0.7.27" % Test
134 | )
135 | )
136 |
137 | lazy val shapeless = createProject("shapeless", Scala2_13)
138 | .settings(
139 | libraryDependencies ++= Seq(
140 | "com.chuusai" %% "shapeless" % "2.3.13"
141 | )
142 | )
143 |
--------------------------------------------------------------------------------
/collections/README.md:
--------------------------------------------------------------------------------
1 | ## コレクションライブラリ
2 |
3 | Scala のコレクションライブラリについてまとめます。
4 |
5 | ### コード例
6 |
7 | Scala Version: 2.13.x
8 |
9 | + [Traversable - コレクションの最上位に位置するトレイト](src/test/scala/dev/nomadblacky/scala_examples/collections/TraversableSpec.scala)
10 | + [Iterable - コレクションの要素をひとつずつ返すトレイト](src/test/scala/dev/nomadblacky/scala_examples/collections/IterableSpec.scala)
11 | + [Set - 重複する要素を含まないコレクション](src/test/scala/dev/nomadblacky/scala_examples/collections/SetSpec.scala)
12 | + [List - 単方向リスト](src/test/scala/dev/nomadblacky/scala_examples/collections/ListSpec.scala)
13 | + [Map - キーと値で構成されるコレクション](src/test/scala/dev/nomadblacky/scala_examples/collections/MapSpec.scala)
14 |
--------------------------------------------------------------------------------
/collections/src/test/scala/dev/nomadblacky/scala_examples/collections/IterableSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.collections
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | /** Created by blacky on 17/02/06.
6 | */
7 | class IterableSpec extends AnyFunSpec {
8 |
9 | override def suiteName: String = "Iterable - コレクションの要素をひとつずつ返すトレイト"
10 |
11 | it("grouped ... 指定サイズのListにまとめたIteratorを返す") {
12 | val itr: Iterator[List[Int]] = List(1, 2, 3, 4).grouped(2)
13 | assert(itr.next() == List(1, 2))
14 | assert(itr.next() == List(3, 4))
15 | assert(itr.hasNext == false)
16 |
17 | val itr2: Iterator[List[Int]] = List(1, 2, 3, 4).grouped(3)
18 | assert(itr2.next() == List(1, 2, 3))
19 | assert(itr2.next() == List(4))
20 | assert(itr2.hasNext == false)
21 | }
22 |
23 | it("sliding ... ウィンドウをずらしながら参照するIteratorを返す") {
24 | val itr: Iterator[List[Int]] = List(1, 2, 3, 4).sliding(2)
25 | assert(itr.next() == List(1, 2))
26 | assert(itr.next() == List(2, 3))
27 | assert(itr.next() == List(3, 4))
28 | assert(itr.hasNext == false)
29 |
30 | val itr2: Iterator[List[Int]] = List(1, 2, 3, 4).sliding(2, 2)
31 | assert(itr2.next() == List(1, 2))
32 | assert(itr2.next() == List(3, 4))
33 | assert(itr2.hasNext == false)
34 | }
35 |
36 | it("takeRight ... コレクションの最後のn個の要素を取り出す") {
37 | assert(List(1, 2, 3, 4, 5).takeRight(3) == List(3, 4, 5))
38 | }
39 |
40 | it("dropRight ... コレクションの最後のn個の要素を取り除く") {
41 | assert(List(1, 2, 3, 4, 5).dropRight(3) == List(1, 2))
42 | }
43 |
44 | it("zip ... 2つのコレクションから対応する要素をペアにする") {
45 | assert(List(1, 2, 3).zip(List(10, 20, 30)) == List((1, 10), (2, 20), (3, 30)))
46 | assert(List(1, 2).zip(List(10, 20, 30)) == List((1, 10), (2, 20)))
47 | assert(List(1, 2, 3).zip(List(10, 20)) == List((1, 10), (2, 20)))
48 | }
49 |
50 | it("zipAll ... 2つのコレクションから対応する要素をペアにする") {
51 | assert(List(1, 2, 3).zipAll(List(10, 20, 30), 9, 99) == List((1, 10), (2, 20), (3, 30)))
52 | assert(List(1, 2).zipAll(List(10, 20, 30), 9, 99) == List((1, 10), (2, 20), (9, 30)))
53 | assert(List(1, 2, 3).zipAll(List(10, 20), 9, 99) == List((1, 10), (2, 20), (3, 99)))
54 | }
55 |
56 | it("zipWithIndex ... コレクションの要素と添字をペアにしたIterableを返す") {
57 | assert(List(1, 2, 3).zipWithIndex == List((1, 0), (2, 1), (3, 2)))
58 | }
59 |
60 | it("sameElements ... 2つのコレクションが同じ要素を同じ順序で格納しているかを返す") {
61 | assert(List(1, 2, 3).sameElements(List(1, 2, 3)) == true)
62 | assert(List(1, 2, 3).sameElements(List(3, 2, 1)) == false)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/collections/src/test/scala/dev/nomadblacky/scala_examples/collections/ListSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.collections
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | import scala.collection.immutable.{List, Nil}
6 | import scala.collection.mutable.ListBuffer
7 |
8 | /** 要素同士を連結するデータ構造のリスト。 順次呼び出しや再帰アクセスに強い。 ランダムアクセスは遅い。
9 | */
10 | class ListSpec extends AnyFunSpec {
11 |
12 | override def suiteName: String = "List - 単方向リスト"
13 |
14 | it("List.apply() ... Listを生成する") {
15 | List(1, 2, 3)
16 | }
17 |
18 | it("List#apply ... Listから値を取り出す") {
19 | val l: List[Int] = List(1, 2, 3)
20 | assert(l(0) == 1)
21 | assert(l(1) == 2)
22 | assert(l(2) == 3)
23 | }
24 |
25 | it("Nil ... 空のListを作成する") {
26 | val l: List[Int] = Nil
27 | assert(l.isEmpty == true)
28 | }
29 |
30 | it("ListBuffer ... ミュータブルなList") {
31 | val l: ListBuffer[Int] = ListBuffer.empty
32 | l += 1
33 | l += 2
34 | assert(l == ListBuffer(1, 2))
35 | l(0) = 10
36 | assert(l == ListBuffer(10, 2))
37 | }
38 |
39 | it(":: :+ ... Listに値を追加する") {
40 | val l: List[String] = List("b")
41 | // 先頭に追加
42 | assert(("a" :: l) == List("a", "b"))
43 | // 末尾に追加
44 | assert((l :+ "c") == List("b", "c"))
45 | }
46 |
47 | it("::: ... List同士を連結する") {
48 | val l1 = List(1, 2, 3)
49 | val l2 = List(4, 5, 6)
50 | assert((l1 ::: l2) == List(1, 2, 3, 4, 5, 6))
51 | }
52 |
53 | it("withFilter ... 中間データを作らない") {
54 | val list = List(1, 2, 3, 4, 5)
55 |
56 | // filterの時点で中間データが作成されてしまう
57 | assert(list.filter { _ % 2 == 0 }.map { _ * 2 } == List(4, 8))
58 |
59 | // withFilterを使うと中間データを作らずメモリにやさしい
60 | assert(list.withFilter { _ % 2 == 0 }.map { _ * 2 } == List(4, 8))
61 | }
62 |
63 | it("view ... none-strict(中間データを作らない)なコレクションに変換する") {
64 | val list = List(1, 2, 3, 4, 5)
65 |
66 | // strict → (view) → none-strict → (toList) → strict
67 | assert(list.view.filter { _ % 2 == 0 }.map { _ * 2 }.toList == List(4, 8))
68 | }
69 |
70 | it("lengthCompare ... コレクションの要素数と引数の長さを比較する") {
71 | // lengthを使って比較するよりも高速
72 | val list = List(1, 2, 3)
73 | assert(list.lengthCompare(1) == 1)
74 | assert(list.lengthCompare(2) == 1)
75 | assert(list.lengthCompare(3) == 0)
76 | assert(list.lengthCompare(4) == -1)
77 | assert(list.lengthCompare(5) == -1)
78 | }
79 |
80 | it("lift") {
81 | val list = List(1, 2, 3)
82 | val i = 3
83 | val a = if (i < list.length) Some(list(i)) else None
84 | val b = list.lift(i)
85 | assert(a == b)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/collections/src/test/scala/dev/nomadblacky/scala_examples/collections/MapSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.collections
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | import scala.collection.immutable.Set
6 |
7 | class MapSpec extends AnyFunSpec {
8 |
9 | override def suiteName: String = "Map - キーと値で構成されるコレクション"
10 |
11 | it("Map.apply() ... Mapを生成する") {
12 | Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
13 | }
14 |
15 | it("Map.empty ... 空のMapを生成する") {
16 | val m: Map[Int, String] = Map.empty
17 | assert(m.isEmpty == true)
18 | }
19 |
20 | it("Map#apply ... Mapから値を取り出す") {
21 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
22 | assert(m(1) == "a")
23 | assert(m(2) == "b")
24 | assert(m(3) == "c")
25 | }
26 |
27 | it("+ ... 指定したキーと値のペアを追加したMapを返す") {
28 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
29 | assert((m + (4 -> "d")) == Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d"))
30 | }
31 |
32 | it("- ... 指定したキーの要素を抜いたMapを返す") {
33 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
34 | assert((m - 3) == Map[Int, String](1 -> "a", 2 -> "b"))
35 | }
36 |
37 | it("ミュータブルなMap") {
38 | val m: scala.collection.mutable.Map[Int, String] =
39 | scala.collection.mutable.Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
40 | m += (4 -> "d")
41 | assert(m(4) == "d")
42 | }
43 |
44 | it("keySet ... キーのSetを返す") {
45 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
46 | assert(m.keySet == Set(1, 2, 3))
47 | }
48 |
49 | it("values ... 値をIterableで返す") {
50 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
51 | // ↓だとだめ! (http://www.ne.jp/asahi/hishidama/home/tech/scala/expression.html#h_notice)
52 | // assert(m.values.toArray == Array("a", "b", "c"))
53 | assert(m.values.toArray.sameElements(Array("a", "b", "c")))
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/collections/src/test/scala/dev/nomadblacky/scala_examples/collections/SetSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.collections
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | /** 集合 http://docs.scala-lang.org/ja/overviews/collections/sets
6 | *
7 | * Created by blacky on 17/03/18.
8 | */
9 | class SetSpec extends AnyFunSpec {
10 |
11 | override def suiteName: String = "Set - 重複する要素を含まないコレクション"
12 |
13 | val s1 = Set(1, 2, 3)
14 | val s2 = Set(3, 4, 5)
15 |
16 | it("apply, contains ... 要素が含まれるかを調べる") {
17 | assert(s1.contains(1) == true)
18 | assert(s1.contains(9) == false)
19 | assert(s2(3) == true)
20 | assert(s2(9) == false)
21 | }
22 |
23 | it("subsetOf ... 部分集合であるか調べる") {
24 | assert(Set(1, 2).subsetOf(s1) == true)
25 | assert(Set(3, 4).subsetOf(s1) == false)
26 | assert(Set(8, 9).subsetOf(s1) == false)
27 | }
28 |
29 | it("+ ... 渡された要素を追加した集合を返す") {
30 | assert(s1 + 4 == Set(1, 2, 3, 4))
31 | assert(s1 + 1 == Set(1, 2, 3))
32 | }
33 |
34 | it("++ ... 渡された集合を追加した集合を返す") {
35 | assert(s1 ++ s2 == Set(1, 2, 3, 4, 5))
36 | }
37 |
38 | it("- ... 渡された要素を除いた集合を返す") {
39 | assert(s1 - 1 == Set(2, 3))
40 | assert(s1 - 4 == Set(1, 2, 3))
41 | }
42 |
43 | it("-- ... 渡された集合のすべての要素を除いた集合を返す") {
44 | assert(s1 -- Set(1, 2) == Set(3))
45 | assert(s1 -- s2 == Set(1, 2))
46 | }
47 |
48 | it("empty ... 集合と同じクラスの空集合を返す") {
49 | assert(Set(1, 2, 3).empty == Set[Int]())
50 | assert(Set("a", "b", "c").empty == Set[String]())
51 | }
52 |
53 | it("&, intersect ... 積集合") {
54 | assert((s1 & s2) == Set(3))
55 | assert((s1 intersect s2) == Set(3))
56 | }
57 |
58 | it("|, union ... 和集合") {
59 | assert((s1 | s2) == Set(1, 2, 3, 4, 5))
60 | assert((s1 union s2) == Set(1, 2, 3, 4, 5))
61 | }
62 |
63 | it("&~, diff ... 差集合") {
64 | assert((s1 &~ s2) == Set(1, 2))
65 | assert((s1 diff s2) == Set(1, 2))
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/collections/src/test/scala/dev/nomadblacky/scala_examples/collections/TraversableSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.collections
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 |
5 | import scala.collection.mutable.ArrayBuffer
6 |
7 | /** Created by blacky on 17/01/16.
8 | *
9 | * Traversable ... 操作可能
10 | */
11 | class TraversableSpec extends AnyFunSpec {
12 |
13 | override def suiteName: String = "Traversable - コレクションの最上位に位置するトレイト"
14 |
15 | it("++ ... Traversableを連結する") {
16 | assert((List(1, 2) ++ List(3, 4)) == List(1, 2, 3, 4))
17 | }
18 |
19 | it("head / headOptional ... 先頭の要素を取得する") {
20 | assert(List(1, 2, 3).head == 1)
21 | assert(List(1, 2, 3).headOption == Some(1))
22 | assertThrows[NoSuchElementException] {
23 | List().head
24 | }
25 | }
26 |
27 | it("last / lastOption ... 最後の要素を取得する") {
28 | assert(List(1, 2, 3).last == 3)
29 | assert(List(1, 2, 3).lastOption == Some(3))
30 | assertThrows[NoSuchElementException] {
31 | List().last
32 | }
33 | }
34 |
35 | it("init ... 最後以外の要素を取得する") {
36 | assert(List(1, 2, 3).init == List(1, 2))
37 | assertThrows[UnsupportedOperationException] {
38 | List().init
39 | }
40 | }
41 |
42 | it("tail ... 最初以外の要素を取得する") {
43 | assert(List(1, 2, 3).tail == List(2, 3))
44 | assertThrows[UnsupportedOperationException] {
45 | List().tail
46 | }
47 | }
48 |
49 | it("foldLeft ... 要素の先頭から畳み込みを行う") {
50 | val itr = List(0, 1, 3).iterator
51 | val result = List(1, 2, 3).foldLeft(0) { (sum, i) =>
52 | assert(sum == itr.next())
53 | sum + i
54 | }
55 | assert(result == 6)
56 | }
57 |
58 | it("foldRight ... 要素の最後から畳み込みを行う") {
59 | val itr = List(0, 3, 5).iterator
60 | // foldLeftと引数の順番が違うので気をつける!
61 | val result = List(1, 2, 3).foldRight(0) { (i, sum) =>
62 | assert(sum == itr.next())
63 | sum + i
64 | }
65 | assert(result == 6)
66 | }
67 |
68 | it("reduceLeft ... 最初の要素を初期値として畳み込みを行う") {
69 | val itr = List(1, 3).iterator
70 | val result = List(1, 2, 3).reduceLeft { (sum, i) =>
71 | assert(sum == itr.next())
72 | sum + i
73 | }
74 | assert(result == 6)
75 | }
76 |
77 | it("reduceRight ... 最後の要素を初期値として畳み込みを行う") {
78 | val itr = List(3, 5).iterator
79 | // reduceLeftと引数の順番が違うので気をつける!
80 | val result = List(1, 2, 3).reduceRight { (i, sum) =>
81 | assert(sum == itr.next())
82 | sum + i
83 | }
84 | assert(result == 6)
85 | }
86 |
87 | it("foreach ... 戻り値なしで全ての要素を処理する") {
88 | val itr = List(1, 2, 3).iterator
89 |
90 | List(1, 2, 3).foreach { i =>
91 | assert(i == itr.next())
92 | }
93 | }
94 |
95 | it("filter ... 条件に一致する要素のみを抜き出す") {
96 | val result = List(1, 2, 3).filter { 2 <= _ }
97 | assert(result == List(2, 3))
98 | }
99 |
100 | it("filter ... 条件に一致しない要素のみを抜き出す") {
101 | val result = List(1, 2, 3).filterNot { 2 <= _ }
102 | assert(result == List(1))
103 | }
104 |
105 | it("drop ... 指定した数の要素を先頭から取り除く") {
106 | assert(List(1, 2, 3).drop(0) == List(1, 2, 3))
107 | assert(List(1, 2, 3).drop(1) == List(2, 3))
108 | assert(List(1, 2, 3).drop(2) == List(3))
109 | assert(List(1, 2, 3).drop(3) == List())
110 | assert(List(1, 2, 3).drop(4) == List())
111 | }
112 |
113 | it("dropWhile ... 条件がfalseになるまで要素を取り除く") {
114 | assert(List(1, 2, 3).dropWhile { _ <= 2 } == List(3))
115 | assert(List(1, 2, 3).dropWhile { i =>
116 | i == 1 || i == 3
117 | } == List(2, 3))
118 | }
119 |
120 | it("take ... 指定した数の要素を先頭から取り出す") {
121 | assert(List(1, 2, 3).take(0) == List())
122 | assert(List(1, 2, 3).take(1) == List(1))
123 | assert(List(1, 2, 3).take(2) == List(1, 2))
124 | assert(List(1, 2, 3).take(3) == List(1, 2, 3))
125 | assert(List(1, 2, 3).take(4) == List(1, 2, 3))
126 | }
127 |
128 | it("takeWhile ... 条件がfalseになるまで要素を取り出す") {
129 | assert(List(1, 2, 3).takeWhile { _ <= 2 } == List(1, 2))
130 | assert(List(1, 2, 3).takeWhile { i =>
131 | i == 1 || i == 3
132 | } == List(1))
133 | }
134 |
135 | it("map ... 要素に関数を適用して新しいコレクションを返す") {
136 | assert(List(1, 2, 3).map(_ * 2) == List(2, 4, 6))
137 | }
138 |
139 | it("flatMap ... 要素に関数を適用して新しいコレクションを返しflattenする") {
140 | assert(List(1, 2, 3).flatMap(i => List(i, i * 2)) == List(1, 2, 2, 4, 3, 6))
141 | }
142 |
143 | it("flatten ... 入れ子になったコレクションを1次元にする") {
144 | assert(List(List(1, 2), List(3, 4)).flatten == List(1, 2, 3, 4))
145 | }
146 |
147 | it("splitAt ... コレクションを分割する") {
148 | val expect = (List(1, 2), List(3, 4))
149 | assert(List(1, 2, 3, 4).splitAt(2) == expect)
150 | }
151 |
152 | it("slice ... コレクションの一部を抜き出す") {
153 | assert(List(1, 2, 3, 4, 5).slice(1, 3) == List(2, 3))
154 | }
155 |
156 | it("partition ... 条件を満たす要素とそうでない要素に分割する") {
157 | val expect1 = (List(1, 2), List(3, 4, 5))
158 | assert(List(1, 2, 3, 4, 5).partition(_ < 3) == expect1)
159 |
160 | val expect2 = (List(4, 5), List(1, 2, 3))
161 | assert(List(1, 2, 3, 4, 5).partition(_ > 3) == expect2)
162 | }
163 |
164 | it("span ... 条件がfalseとなった要素を堺にコレクションを分割する") {
165 | val expect1 = (List(1, 2), List(3, 4, 5))
166 | assert(List(1, 2, 3, 4, 5).span(_ < 3) == expect1)
167 |
168 | val expect2 = (List(), List(1, 2, 3, 4, 5))
169 | assert(List(1, 2, 3, 4, 5).span(_ > 3) == expect2)
170 | }
171 |
172 | it("groupBy ... 関数の結果をキーとして要素をMapにして返す") {
173 | assert(List("hoge", "foo", "bar").groupBy(_.length) == Map(3 -> List("foo", "bar"), 4 -> List("hoge")))
174 | }
175 |
176 | it("grouped ... N件ごとにコレクションを分割する") {
177 | val list = List(1, 2, 3, 4, 5)
178 | assert(list.grouped(2).toList == List(List(1, 2), List(3, 4), List(5)))
179 | assert(list.grouped(3).toList == List(List(1, 2, 3), List(4, 5)))
180 | assertThrows[IllegalArgumentException](list.grouped(0))
181 | }
182 |
183 | it("unzip ... 要素を2つのコレクションに分割する") {
184 | val expect1 = (List(1, 2, 3), List("one", "two", "three"))
185 | assert(List((1, "one"), (2, "two"), (3, "three")).unzip == expect1)
186 |
187 | val expect2 = (List(1, 2, 3), List("one", "two", "three"))
188 | assert(Map(1 -> "one", 2 -> "two", 3 -> "three").unzip == expect2)
189 | }
190 |
191 | it("unzip3 ... 要素を3つのコレクションに分割する") {
192 | val expect = (
193 | List(1, 2, 3),
194 | List("one", "two", "three"),
195 | List("hoge", "foo", "bar")
196 | )
197 | assert(List((1, "one", "hoge"), (2, "two", "foo"), (3, "three", "bar")).unzip3 == expect)
198 | }
199 |
200 | it("find ... 最初に条件を満たした要素をOptionで返す") {
201 | assert(List(1, 2, 3).find(1 < _) == Some(2))
202 | assert(List(1, 2, 3).find(2 < _) == Some(3))
203 | assert(List(1, 2, 3).find(3 < _) == None)
204 | }
205 |
206 | it("exists ... 条件を満たす要素があるか判定する") {
207 | assert(List(1, 2, 3).exists(_ == 1) == true)
208 | assert(List(1, 2, 3).exists(_ == 5) == false)
209 | }
210 |
211 | it("forall ... 全ての要素が条件を満たすか判定する") {
212 | assert(List(1, 2, 3).forall(0 < _) == true)
213 | assert(List(1, 2, 3).forall(0 > _) == false)
214 | }
215 |
216 | it("count ... 条件を満たす要素の個数を返す") {
217 | assert(List(1, 2, 3).count(1 < _) == 2)
218 | assert(List(1, 2, 3).count(3 < _) == 0)
219 | }
220 |
221 | it("size/length ... 要素の個数を返す") {
222 | assert(List(1, 2, 3).size == 3)
223 | assert(List(1, 2, 3).length == 3)
224 | }
225 |
226 | it("min ... 最小値を返す") {
227 | assert(List(1, 2, 3).min == 1)
228 | assert(List("a", "b", "c").min == "a")
229 | }
230 |
231 | it("max ... 最大値を返す") {
232 | assert(List(1, 2, 3).max == 3)
233 | assert(List("a", "b", "c").max == "c")
234 | }
235 |
236 | it("sum ... 合計値を返す") {
237 | assert(List(1, 2, 3).sum == 6)
238 | // error
239 | // assert(List("a", "b", "c").sum == "")
240 | }
241 |
242 | it("mkString ... コレクションから文字列を作る") {
243 | assert(List(1, 2, 3).mkString == "123")
244 | assert(List(1, 2, 3).mkString("-") == "1-2-3")
245 | assert(List(1, 2, 3).mkString("[", "-", "]") == "[1-2-3]")
246 | }
247 |
248 | it("toArray ... コレクションをArrayに変換する") {
249 | // ScalaのArrayはJava配列で実装されているので == の比較はポインタの比較になる?
250 | // assert(List(1, 2, 3).toArray == Array(1, 2, 3))
251 | assert(List(1, 2, 3).toArray sameElements Array(1, 2, 3))
252 | }
253 |
254 | it("toBuffer ... コレクションをBufferに変換する") {
255 | assert(List(1, 2, 3).toBuffer == ArrayBuffer(1, 2, 3))
256 | }
257 |
258 | it("toIndexedSeq ... コレクションをIndexedSeqに変換する") {
259 | assert(List(1, 2, 3).toIndexedSeq == IndexedSeq(1, 2, 3))
260 | }
261 |
262 | it("toList ... コレクションをListに変換する") {
263 | assert(Array(1, 2, 3).toList == List(1, 2, 3))
264 | }
265 |
266 | it("view/force ... コレクションの操作を遅延評価させる(中間データを作らない)") {
267 | assert(List(1, 2, 3).view.map(_ * 2).toList == List(2, 4, 6))
268 | }
269 |
270 | it("collect ... PartialFunctionを適用して要素を変換する") {
271 | val list = List(1, 2, 3, 4, 5)
272 | val result = list.collect {
273 | case 1 => "one"
274 | case 2 => "two"
275 | case 3 => "three"
276 | case _ => "-"
277 | }
278 | assert(result == List("one", "two", "three", "-", "-"))
279 | }
280 |
281 | it("collectFirst ... PartialFunctionに最初に一致した値を取得する") {
282 | val list = List(9, 7, 4, 2, 1, 3, 9)
283 | val result = list.collectFirst {
284 | case 1 => "one"
285 | case 2 => "two"
286 | case 3 => "three"
287 | }
288 | assert(result == Some("two"))
289 | }
290 |
291 | }
292 |
--------------------------------------------------------------------------------
/data/hightemp.txt:
--------------------------------------------------------------------------------
1 | 高知県 江川崎 41 2013-08-12
2 | 埼玉県 熊谷 40.9 2007-08-16
3 | 岐阜県 多治見 40.9 2007-08-16
4 | 山形県 山形 40.8 1933-07-25
5 | 山梨県 甲府 40.7 2013-08-10
6 | 和歌山県 かつらぎ 40.6 1994-08-08
7 | 静岡県 天竜 40.6 1994-08-04
8 | 山梨県 勝沼 40.5 2013-08-10
9 | 埼玉県 越谷 40.4 2007-08-16
10 | 群馬県 館林 40.3 2007-08-16
11 | 群馬県 上里見 40.3 1998-07-04
12 | 愛知県 愛西 40.3 1994-08-05
13 | 千葉県 牛久 40.2 2004-07-20
14 | 静岡県 佐久間 40.2 2001-07-24
15 | 愛媛県 宇和島 40.2 1927-07-22
16 | 山形県 酒田 40.1 1978-08-03
17 | 岐阜県 美濃 40 2007-08-16
18 | 群馬県 前橋 40 2001-07-24
19 | 千葉県 茂原 39.9 2013-08-11
20 | 埼玉県 鳩山 39.9 1997-07-05
21 | 大阪府 豊中 39.9 1994-08-08
22 | 山梨県 大月 39.9 1990-07-19
23 | 山形県 鶴岡 39.9 1978-08-03
24 | 愛知県 名古屋 39.9 1942-08-02
25 |
--------------------------------------------------------------------------------
/legacy/src/test/java/org/nomadblacky/scala/samples/with_java/JavaClass.java:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.with_java;
2 |
3 | public class JavaClass {
4 | private final int i;
5 |
6 | public JavaClass(int i) {
7 | this.i = i;
8 | }
9 |
10 | public String f() {
11 | return String.valueOf(i);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/legacy/src/test/java/org/nomadblacky/scala/samples/with_java/UseScala.java:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.with_java;
2 |
3 | public class UseScala {
4 | private final ScalaClass scalaClass;
5 |
6 | public UseScala(ScalaClass scalaClass) {
7 | this.scalaClass = scalaClass;
8 | }
9 |
10 | public int f() {
11 | return scalaClass.i();
12 | }
13 |
14 | public String f2(ScalaCaseClass caseClass) {
15 | return caseClass.toString();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/legacy/src/test/resources/application.conf:
--------------------------------------------------------------------------------
1 | db.default.url = "jdbc:h2:./sample"
2 | db.default.driver = "org.h2.Driver"
3 | db.default.user = "user"
4 | db.default.password = "password"
5 |
6 | db.sample1.url = "jdbc:h2:mem:sample1;DB_CLOSE_DELAY=-1"
7 | db.sample1.driver = "org.h2.Driver"
8 | db.sample1.user = "user"
9 | db.sample1.password = "password"
10 |
--------------------------------------------------------------------------------
/legacy/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/legacy/src/test/resources/org/nomadblacky/scala/samples/xml/xml_spec_01.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | title
4 |
5 |
6 | h1
7 | h2
8 |
9 |
p1
10 |
p2
11 |
p3
12 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/akka/http/AkkaHttpSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.akka.http
2 |
3 | import akka.actor.ActorSystem
4 | import akka.http.scaladsl.Http
5 | import akka.http.scaladsl.model.{HttpRequest, HttpResponse, StatusCodes}
6 | import akka.stream.ActorMaterializer
7 | import akka.util.ByteString
8 | import org.scalatest.FunSpec
9 |
10 | import scala.concurrent.duration.Duration
11 | import scala.concurrent.{Await, Future}
12 |
13 | import scala.concurrent.ExecutionContext.Implicits.global
14 |
15 | /** Created by blacky on 17/05/29.
16 | */
17 | class AkkaHttpSpec extends FunSpec {
18 |
19 | override def suiteName: String = "Akka HTTP"
20 |
21 | it("クライアントAPI") {
22 | implicit val system = ActorSystem()
23 | implicit val materializer = ActorMaterializer()
24 |
25 | val responseFuture: Future[HttpResponse] =
26 | Http().singleRequest(HttpRequest(uri = "https://www.google.co.jp/"))
27 |
28 | responseFuture.foreach {
29 | case HttpResponse(StatusCodes.OK, _, entity, _) =>
30 | entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
31 | println(body.utf8String)
32 | }
33 | case _ => throw new IllegalStateException("fail")
34 | }
35 |
36 | Await.ready(responseFuture, Duration.Inf)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/akka/streams/AkkaStreamsSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.akka.streams
2 |
3 | import akka.actor.ActorSystem
4 | import org.scalatest.{FunSpec, Matchers}
5 |
6 | /** Akka Streams 非同期ストリーム処理のライブラリ
7 | *
8 | * 特徴 + バックプレッシャー … サブスクライバが処理できる量をパブリッシャに送ることで、無駄なく処理ができる。 + リアクティブストリーム … 異なるストリーム処理ツール間でもバックプレッシャを実現できる。 + 非同期 +
9 | * 直感的なAPI + DSLによるグラフ処理
10 | */
11 | class AkkaStreamsSpec extends FunSpec with Matchers {
12 |
13 | override def suiteName: String = "Akka Streams"
14 |
15 | import akka.stream._
16 | import akka.stream.scaladsl._
17 |
18 | it("ことはじめ") {
19 | // Actorを使っているため、ActorSystemが必要
20 | implicit val actorSystem: ActorSystem = ActorSystem()
21 | // 設定・ロジックなど
22 | implicit val materializer: ActorMaterializer = ActorMaterializer()
23 |
24 | val source = Source(1 to 10) // Publisher
25 | val sink = Sink.foreach(println) // Subscriber
26 |
27 | source
28 | .map(_ * 2) // Stage (Actorが立ち上がる)
29 | .runWith(sink) // Sinkで結果を受け取る
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/ammonite/AmmoniteSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.ammonite
2 |
3 | import ammonite.ops._
4 | import org.scalatest.{BeforeAndAfterAll, FunSpec, Matchers}
5 |
6 | /** http://ammonite.io/#Ammonite-Ops
7 | */
8 | class AmmoniteSpec extends FunSpec with Matchers with BeforeAndAfterAll {
9 |
10 | override def suiteName: String = "Ammonite-Ops"
11 |
12 | override protected def beforeAll(): Unit = {
13 | val f = pwd / 'tmp / "hoge.txt"
14 | if (exists ! f) rm ! f
15 | }
16 |
17 | it("パスを参照する") {
18 | pwd // カレントディレクトリ
19 | home // ホームディレクトリ
20 | root // ルートディレクトリ
21 |
22 | // スラッシュで下の階層へ
23 | // Symbol か String でパスを指定できる
24 | val directory: FilePath = root / 'home
25 | val file: FilePath = root / 'tmp / "sample.scala"
26 |
27 | directory.toString shouldBe "/home"
28 | file.toString shouldBe "/tmp/sample.scala"
29 | }
30 |
31 | it("基本的なファイル操作") {
32 | // いわゆる ls コマンド
33 | // ちなみに `ls` がobjectで `!` がメソッドである
34 | // ( `!` メソッド内で `apply` を呼び出している)
35 | val files: LsSeq = ls ! pwd
36 |
37 | ls.rec ! pwd // いわゆる find
38 | mkdir ! pwd / 'tmp / "hoge" // ディレクトリ作成
39 |
40 | cp(pwd / 'tmp / "sample.txt", pwd / 'tmp / "sample2.txt") // コピー
41 | mv(pwd / 'tmp / "sample2.txt", pwd / 'tmp / "sample3.txt") // 移動
42 | rm ! pwd / 'tmp / "sample3.txt" // 削除
43 | }
44 |
45 | it("ファイルの読み書き") {
46 | // http://ammonite.io/#Operations
47 |
48 | // テキストファイルの読み込み
49 | read(pwd / 'tmp / "sample.txt") shouldBe "Scala de Scala\n"
50 | read.bytes ! pwd / 'tmp / "sample.txt" // Array[Byte]
51 | read.lines ! pwd / 'tmp / "sample.txt" // Vector[String]
52 |
53 | // ファイルの書き込み
54 | write(pwd / 'tmp / "hoge.txt", "Foo!")
55 | write.over(pwd / 'tmp / "foo.txt", "Yaa!") // 上書き
56 | write.append(pwd / 'tmp / "foo.txt", "Boo!") // 追記
57 | }
58 |
59 | it("拡張演算子とワンライナー") {
60 | // http://ammonite.io/#Extensions
61 |
62 | // Traversableの拡張
63 | val seq = Seq(1, 2, 3)
64 | seq | (_ * 2) shouldBe seq.map(_ * 2)
65 | seq || (i => Seq(i, i * 2)) shouldBe seq.flatMap(i => Seq(i, i * 2))
66 | seq |? (_ % 2 == 0) shouldBe seq.filter(_ % 2 == 0)
67 | seq |& (_ + _) shouldBe seq.reduce(_ * _)
68 | seq |! println // foreach
69 |
70 | // Pipeable
71 | val func = (i: Int) => i * 2
72 | 100 |> func shouldBe 200 // func(100)
73 |
74 | // Callable
75 | func ! 100 shouldBe 200 // func(100)
76 |
77 | // ワーキングディレクトリ以下の".scala"ファイルを読み込む
78 | ls.rec ! pwd |? (_.ext == "scala") | read
79 | }
80 |
81 | it("サブプロセスの実行") {
82 | // ワーキングディレクトリを implicit parameter として渡す
83 | import ammonite.ops.ImplicitWd._
84 |
85 | // % %% でコマンドを実行できる
86 | // 一見、コンパイルが通らなそうな見た目だが、これはDynamic#applyDynamicを使用しているので正しいコードである。
87 | // http://www.ne.jp/asahi/hishidama/home/tech/scala/dynamic.html
88 | % ls
89 | val res = %%('echo, "foo!")
90 | res.exitCode shouldBe 0
91 | res.out.string shouldBe "foo!\n"
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/best_practice/DesignPatternsInScalaSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.best_practice
2 |
3 | import java.awt.Point
4 | import java.io.File
5 | import java.util.Scanner
6 |
7 | import org.scalatest.{FunSpec, Matchers}
8 |
9 | import scala.annotation.tailrec
10 |
11 | class DesignPatternsInScalaSpec extends FunSpec with Matchers {
12 |
13 | override def suiteName: String = "[BestPractice] Scalaでのデザインパターン"
14 |
15 | it("Pimp My Library パターンで既存クラスにメソッドを追加する") {
16 | implicit class RichPoint(val point: Point) {
17 | def +(other: Point): Point = new Point(point.x + other.x, point.y + other.y)
18 | def -(other: Point): Point = new Point(point.x - other.x, point.y - other.y)
19 | }
20 |
21 | val p1 = new Point(1, 4)
22 | val p2 = new Point(2, 3)
23 |
24 | p1 + p2 shouldBe new Point(3, 7)
25 | p1 - p2 shouldBe new Point(-1, 1)
26 | }
27 |
28 | it("LoanパターンでAutoClosingを実装する") {
29 | def using[Resource <: AutoCloseable, A](r: Resource)(f: Resource => A): A =
30 | try {
31 | f(r)
32 | } finally {
33 | r.close()
34 | }
35 |
36 | def linesCount(scanner: Scanner): Int = {
37 | @tailrec def loop(scanner: Scanner, i: Int): Int =
38 | if (scanner.hasNextLine) {
39 | scanner.nextLine()
40 | loop(scanner, i + 1)
41 | } else {
42 | i
43 | }
44 | loop(scanner, 0)
45 | }
46 |
47 | val scanner = new Scanner(new File("data/hightemp.txt"), "UTF-8")
48 |
49 | val result = using(scanner)(linesCount)
50 | result shouldBe 24
51 |
52 | val caught =
53 | intercept[IllegalStateException](scanner.nextLine())
54 | caught.getMessage shouldBe "Scanner closed"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/best_practice/EffectiveScalaSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.best_practice
2 |
3 | import java.util.concurrent.{ConcurrentHashMap, ConcurrentLinkedQueue}
4 |
5 | import org.scalatest.{FunSpec, Matchers}
6 |
7 | import scala.collection.mutable
8 |
9 | /** Effective Scala http://twitter.github.io/effectivescala/index-ja.html
10 | */
11 | class EffectiveScalaSpec extends FunSpec with Matchers {
12 |
13 | it("関数定義の中で直接パターンマッチを使う") {
14 | // これは
15 | def sample1(list: Seq[Option[Int]], default: Int): Seq[Int] = {
16 | list map { item =>
17 | item match {
18 | case Some(x) => x
19 | case None => default
20 | }
21 | }
22 | }
23 |
24 | // こう書ける
25 | def sample2(list: Seq[Option[Int]], default: Int): Seq[Int] = {
26 | list map {
27 | case Some(x) => x
28 | case None => default
29 | }
30 | }
31 |
32 | sample1(List(Some(1), None, Some(3)), 0) shouldBe List(1, 0, 3)
33 | sample2(List(Some(1), None, Some(3)), 0) shouldBe List(1, 0, 3)
34 | }
35 |
36 | it("型エイリアスを使う") {
37 | // 型情報が複雑になる場合は、型エイリアスで簡潔にできる
38 | class ConcurrentPool[K, V] {
39 | type Queue = ConcurrentLinkedQueue[V]
40 | type Map = ConcurrentHashMap[K, Queue]
41 | }
42 | // ※型エイリアスは新しい型ではない。その型に置換しているだけ。
43 |
44 | // 型エイリアスが使える場合、サブクラスにしてはいけない
45 |
46 | trait IntToStr1 extends (Int => String)
47 | // コンパイルエラー
48 | // val factory: IntToStr1 = i => i.toString
49 |
50 | // IntToStr2 として関数リテラルが書ける
51 | type IntToStr2 = Int => String
52 | val factory: IntToStr2 = i => i.toString
53 | }
54 |
55 | it("コレクション処理の可読性を保つ") {
56 | val votes = Seq(("scala", 1), ("java", 4), ("scala", 10), ("scala", 1), ("python", 10))
57 |
58 | // 正しい処理ではあるが、読み手にとって理解しづらい
59 | val orderedVotes = votes
60 | .groupBy(_._1)
61 | .map { case (which, counts) =>
62 | (which, counts.foldLeft(0)(_ + _._2))
63 | }
64 | .toSeq
65 | .sortBy(_._2)
66 | .reverse
67 |
68 | // 中間結果やパラメータに名前をつけることで可読性を保つ
69 | val voteByLang = votes groupBy { case (lang, _) => lang }
70 | val sumByLang = voteByLang map { case (lang, counts) =>
71 | val countsOnly = counts map { case (_, count) => count }
72 | (lang, countsOnly.sum)
73 | }
74 | val orderedVotes2 = sumByLang.toSeq.sortBy { case (_, count) => count }.reverse
75 |
76 | // 名前空間を汚したくなければ、式を {} でグループ化する
77 | val orderedVotes3 = {
78 | val voteByLang = votes groupBy { case (lang, _) => lang }
79 | val sumByLang = voteByLang map { case (lang, counts) =>
80 | val countsOnly = counts map { case (_, count) => count }
81 | (lang, countsOnly.sum)
82 | }
83 | sumByLang.toSeq.sortBy { case (_, count) => count }.reverse
84 | }
85 | }
86 |
87 | it("Javaコレクションとの相互変換") {
88 | // JavaConvertersは、asJava メソッドと asScala メソッドを追加する
89 | // ※暗黙の変換を行うJavaConversionsは非推奨となっている。
90 | import scala.collection.JavaConverters._
91 |
92 | // Scala → Java
93 | val jList: java.util.List[Int] = Seq(1, 2, 3).asJava
94 | val jMap: java.util.Map[Int, String] = Map(1 -> "a", 2 -> "b").asJava
95 |
96 | // Java → Scala
97 | val sList: Seq[Int] = java.util.Arrays.asList(1, 2, 3).asScala
98 | val sMap: mutable.Map[Int, String] = new java.util.HashMap[Int, String]() {
99 | put(1, "a")
100 | put(2, "b")
101 | }.asScala
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/collections/IterableSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.collections
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** Created by blacky on 17/02/06.
6 | */
7 | class IterableSpec extends FunSpec {
8 |
9 | override def suiteName: String = "Iterable - コレクションの要素をひとつずつ返すトレイト"
10 |
11 | it("grouped ... 指定サイズのListにまとめたIteratorを返す") {
12 | val itr: Iterator[List[Int]] = List(1, 2, 3, 4).grouped(2)
13 | assert(itr.next() == List(1, 2))
14 | assert(itr.next() == List(3, 4))
15 | assert(itr.hasNext == false)
16 |
17 | val itr2: Iterator[List[Int]] = List(1, 2, 3, 4).grouped(3)
18 | assert(itr2.next() == List(1, 2, 3))
19 | assert(itr2.next() == List(4))
20 | assert(itr2.hasNext == false)
21 | }
22 |
23 | it("sliding ... ウィンドウをずらしながら参照するIteratorを返す") {
24 | val itr: Iterator[List[Int]] = List(1, 2, 3, 4).sliding(2)
25 | assert(itr.next() == List(1, 2))
26 | assert(itr.next() == List(2, 3))
27 | assert(itr.next() == List(3, 4))
28 | assert(itr.hasNext == false)
29 |
30 | val itr2: Iterator[List[Int]] = List(1, 2, 3, 4).sliding(2, 2)
31 | assert(itr2.next() == List(1, 2))
32 | assert(itr2.next() == List(3, 4))
33 | assert(itr2.hasNext == false)
34 | }
35 |
36 | it("takeRight ... コレクションの最後のn個の要素を取り出す") {
37 | assert(List(1, 2, 3, 4, 5).takeRight(3) == List(3, 4, 5))
38 | }
39 |
40 | it("dropRight ... コレクションの最後のn個の要素を取り除く") {
41 | assert(List(1, 2, 3, 4, 5).dropRight(3) == List(1, 2))
42 | }
43 |
44 | it("zip ... 2つのコレクションから対応する要素をペアにする") {
45 | assert(List(1, 2, 3).zip(List(10, 20, 30)) == List((1, 10), (2, 20), (3, 30)))
46 | assert(List(1, 2).zip(List(10, 20, 30)) == List((1, 10), (2, 20)))
47 | assert(List(1, 2, 3).zip(List(10, 20)) == List((1, 10), (2, 20)))
48 | }
49 |
50 | it("zipAll ... 2つのコレクションから対応する要素をペアにする") {
51 | assert(List(1, 2, 3).zipAll(List(10, 20, 30), 9, 99) == List((1, 10), (2, 20), (3, 30)))
52 | assert(List(1, 2).zipAll(List(10, 20, 30), 9, 99) == List((1, 10), (2, 20), (9, 30)))
53 | assert(List(1, 2, 3).zipAll(List(10, 20), 9, 99) == List((1, 10), (2, 20), (3, 99)))
54 | }
55 |
56 | it("zipWithIndex ... コレクションの要素と添字をペアにしたIterableを返す") {
57 | assert(List(1, 2, 3).zipWithIndex == List((1, 0), (2, 1), (3, 2)))
58 | }
59 |
60 | it("sameElements ... 2つのコレクションが同じ要素を同じ順序で格納しているかを返す") {
61 | assert(List(1, 2, 3).sameElements(List(1, 2, 3)) == true)
62 | assert(List(1, 2, 3).sameElements(List(3, 2, 1)) == false)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/collections/ListSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.collections
2 |
3 | import org.scalatest.FunSpec
4 | import scala.collection.immutable.List
5 | import scala.collection.immutable.Nil
6 | import scala.collection.mutable.ListBuffer
7 |
8 | /** 要素同士を連結するデータ構造のリスト。 順次呼び出しや再帰アクセスに強い。 ランダムアクセスは遅い。
9 | */
10 | class ListSpec extends FunSpec {
11 |
12 | override def suiteName: String = "List - 単方向リスト"
13 |
14 | it("List() ... Listを生成する") {
15 | val l: List[Int] = List(1, 2, 3)
16 | }
17 |
18 | it("() ... Listから値を取り出す") {
19 | val l: List[Int] = List(1, 2, 3)
20 | assert(l(0) == 1)
21 | assert(l(1) == 2)
22 | assert(l(2) == 3)
23 | }
24 |
25 | it("Nil ... 空のListを作成する") {
26 | val l: List[Int] = Nil
27 | assert(l.isEmpty == true)
28 | }
29 |
30 | it("ListBuffer ... ミュータブルなList") {
31 | val l: ListBuffer[Int] = ListBuffer.empty
32 | l += 1
33 | l += 2
34 | assert(l == ListBuffer(1, 2))
35 | l(0) = 10
36 | assert(l == ListBuffer(10, 2))
37 | }
38 |
39 | it(":: :+ ... Listに値を追加する") {
40 | val l: List[String] = List("b")
41 | // 先頭に追加
42 | assert(("a" :: l) == List("a", "b"))
43 | // 末尾に追加
44 | assert((l :+ "c") == List("b", "c"))
45 | }
46 |
47 | it("::: ... List同士を連結する") {
48 | val l1 = List(1, 2, 3)
49 | val l2 = List(4, 5, 6)
50 | assert((l1 ::: l2) == List(1, 2, 3, 4, 5, 6))
51 | }
52 |
53 | it("withFilter ... 中間データを作らない") {
54 | val list = List(1, 2, 3, 4, 5)
55 |
56 | // filterの時点で中間データが作成されてしまう
57 | assert(list.filter { _ % 2 == 0 }.map { _ * 2 } == List(4, 8))
58 |
59 | // withFilterを使うと中間データを作らずメモリにやさしい
60 | assert(list.withFilter { _ % 2 == 0 }.map { _ * 2 } == List(4, 8))
61 | }
62 |
63 | it("view ... none-strict(中間データを作らない)なコレクションに変換する") {
64 | val list = List(1, 2, 3, 4, 5)
65 |
66 | // strict → (view) → none-strict → (force) → strict
67 | assert(list.view.filter { _ % 2 == 0 }.map { _ * 2 }.force == List(4, 8))
68 | }
69 |
70 | it("lengthCompare ... コレクションの要素数と引数の長さを比較する") {
71 | // lengthを使って比較するよりも高速
72 | val list = List(1, 2, 3)
73 | assert(list.lengthCompare(1) == 1)
74 | assert(list.lengthCompare(2) == 1)
75 | assert(list.lengthCompare(3) == 0)
76 | assert(list.lengthCompare(4) == -1)
77 | assert(list.lengthCompare(5) == -1)
78 | }
79 |
80 | it("lift") {
81 | val list = List(1, 2, 3)
82 | val i = 3
83 | val a = if (i < list.length) Some(list(i)) else None
84 | val b = list.lift(i)
85 | assert(a == b)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/collections/MapSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.collections
2 |
3 | import org.scalatest.FunSpec
4 | import scala.collection.immutable.Set
5 |
6 | class MapSpec extends FunSpec {
7 |
8 | override def suiteName: String = "Map - キーと値で構成されるコレクション"
9 |
10 | it("Map() ... Mapを生成する") {
11 | val m: Map[Int, String] = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
12 | }
13 |
14 | it("Map.empty ... 空のMapを生成する") {
15 | val m: Map[Int, String] = Map.empty
16 | assert(m.isEmpty == true)
17 | }
18 |
19 | it("() ... Mapから値を取り出す") {
20 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
21 | assert(m(1) == "a")
22 | assert(m(2) == "b")
23 | assert(m(3) == "c")
24 | }
25 |
26 | it("+ ... 指定したキーと値のペアを追加したMapを返す") {
27 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
28 | assert((m + (4 -> "d")) == Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c", 4 -> "d"))
29 | }
30 |
31 | it("- ... 指定したキーの要素を抜いたMapを返す") {
32 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
33 | assert((m - 3) == Map[Int, String](1 -> "a", 2 -> "b"))
34 | }
35 |
36 | it("ミュータブルなMap") {
37 | val m: scala.collection.mutable.Map[Int, String] =
38 | scala.collection.mutable.Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
39 | m += (4 -> "d")
40 | assert(m(4) == "d")
41 | }
42 |
43 | it("keySet ... キーのSetを返す") {
44 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
45 | assert(m.keySet == Set(1, 2, 3))
46 | }
47 |
48 | it("values ... 値をIterableで返す") {
49 | val m = Map[Int, String](1 -> "a", 2 -> "b", 3 -> "c")
50 | // ↓だとだめ! (http://www.ne.jp/asahi/hishidama/home/tech/scala/expression.html#h_notice)
51 | // assert(m.values.toArray == Array("a", "b", "c"))
52 | assert(m.values.toArray.sameElements(Array("a", "b", "c")))
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/collections/SetSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.collections
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** 集合 http://docs.scala-lang.org/ja/overviews/collections/sets
6 | *
7 | * Created by blacky on 17/03/18.
8 | */
9 | class SetSpec extends FunSpec {
10 |
11 | override def suiteName: String = "Set - 重複する要素を含まないコレクション"
12 |
13 | val s1 = Set(1, 2, 3)
14 | val s2 = Set(3, 4, 5)
15 |
16 | it("apply, contains ... 要素が含まれるかを調べる") {
17 | assert(s1.contains(1) == true)
18 | assert(s1.contains(9) == false)
19 | assert(s2(3) == true)
20 | assert(s2(9) == false)
21 | }
22 |
23 | it("subsetOf ... 部分集合であるか調べる") {
24 | assert(Set(1, 2).subsetOf(s1) == true)
25 | assert(Set(3, 4).subsetOf(s1) == false)
26 | assert(Set(8, 9).subsetOf(s1) == false)
27 | }
28 |
29 | it("+ ... 渡された要素を追加した集合を返す") {
30 | assert(s1 + 4 == Set(1, 2, 3, 4))
31 | assert(s1 + 1 == Set(1, 2, 3))
32 | assert(s1 + (3, 4, 5) == Set(1, 2, 3, 4, 5))
33 | }
34 |
35 | it("++ ... 渡された集合を追加した集合を返す") {
36 | assert(s1 ++ s2 == Set(1, 2, 3, 4, 5))
37 | }
38 |
39 | it("- ... 渡された要素を除いた集合を返す") {
40 | assert(s1 - 1 == Set(2, 3))
41 | assert(s1 - 4 == Set(1, 2, 3))
42 | assert(s1 - (2, 3, 4) == Set(1))
43 | }
44 |
45 | it("-- ... 渡された集合のすべての要素を除いた集合を返す") {
46 | assert(s1 -- Set(1, 2) == Set(3))
47 | assert(s1 -- s2 == Set(1, 2))
48 | }
49 |
50 | it("empty ... 集合と同じクラスの空集合を返す") {
51 | assert(Set(1, 2, 3).empty == Set[Int]())
52 | assert(Set("a", "b", "c").empty == Set[String]())
53 | }
54 |
55 | it("&, intersect ... 積集合") {
56 | assert((s1 & s2) == Set(3))
57 | assert((s1 intersect s2) == Set(3))
58 | }
59 |
60 | it("|, union ... 和集合") {
61 | assert((s1 | s2) == Set(1, 2, 3, 4, 5))
62 | assert((s1 union s2) == Set(1, 2, 3, 4, 5))
63 | }
64 |
65 | it("&~, diff ... 差集合") {
66 | assert((s1 &~ s2) == Set(1, 2))
67 | assert((s1 diff s2) == Set(1, 2))
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/collections/TraversableSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.collections
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scala.collection.mutable.ArrayBuffer
6 |
7 | /** Created by blacky on 17/01/16.
8 | *
9 | * Traversable ... 操作可能
10 | */
11 | class TraversableSpec extends FunSpec {
12 |
13 | override def suiteName: String = "Traversable - コレクションの最上位に位置するトレイト"
14 |
15 | it("++ ... Traversableを連結する") {
16 | assert((List(1, 2) ++ List(3, 4)) == List(1, 2, 3, 4))
17 | }
18 |
19 | it("head / headOptional ... 先頭の要素を取得する") {
20 | assert(List(1, 2, 3).head == 1)
21 | assert(List(1, 2, 3).headOption == Some(1))
22 | assertThrows[NoSuchElementException] {
23 | List().head
24 | }
25 | }
26 |
27 | it("last / lastOption ... 最後の要素を取得する") {
28 | assert(List(1, 2, 3).last == 3)
29 | assert(List(1, 2, 3).lastOption == Some(3))
30 | assertThrows[NoSuchElementException] {
31 | List().last
32 | }
33 | }
34 |
35 | it("init ... 最後以外の要素を取得する") {
36 | assert(List(1, 2, 3).init == List(1, 2))
37 | assertThrows[UnsupportedOperationException] {
38 | List().init
39 | }
40 | }
41 |
42 | it("tail ... 最初以外の要素を取得する") {
43 | assert(List(1, 2, 3).tail == List(2, 3))
44 | assertThrows[UnsupportedOperationException] {
45 | List().tail
46 | }
47 | }
48 |
49 | it("foldLeft ... 要素の先頭から畳み込みを行う") {
50 | val itr = List(0, 1, 3).iterator
51 | val result = List(1, 2, 3).foldLeft(0) { (sum, i) =>
52 | assert(sum == itr.next())
53 | sum + i
54 | }
55 | assert(result == 6)
56 |
57 | // 省略
58 | assert((0 /: List(1, 2, 3)) { (sum, i) =>
59 | sum + i
60 | } == 6)
61 | }
62 |
63 | it("foldRight ... 要素の最後から畳み込みを行う") {
64 | val itr = List(0, 3, 5).iterator
65 | // foldLeftと引数の順番が違うので気をつける!
66 | val result = List(1, 2, 3).foldRight(0) { (i, sum) =>
67 | assert(sum == itr.next())
68 | sum + i
69 | }
70 | assert(result == 6)
71 |
72 | // 省略
73 | assert((List(1, 2, 3) :\ 0) { (sum, i) =>
74 | sum + i
75 | } == 6)
76 | }
77 |
78 | it("reduceLeft ... 最初の要素を初期値として畳み込みを行う") {
79 | val itr = List(1, 3).iterator
80 | val result = List(1, 2, 3).reduceLeft { (sum, i) =>
81 | assert(sum == itr.next())
82 | sum + i
83 | }
84 | assert(result == 6)
85 | }
86 |
87 | it("reduceRight ... 最後の要素を初期値として畳み込みを行う") {
88 | val itr = List(3, 5).iterator
89 | // reduceLeftと引数の順番が違うので気をつける!
90 | val result = List(1, 2, 3).reduceRight { (i, sum) =>
91 | assert(sum == itr.next())
92 | sum + i
93 | }
94 | assert(result == 6)
95 | }
96 |
97 | it("foreach ... 戻り値なしで全ての要素を処理する") {
98 | val itr = List(1, 2, 3).iterator
99 |
100 | List(1, 2, 3).foreach { i =>
101 | assert(i == itr.next())
102 | }
103 | }
104 |
105 | it("filter ... 条件に一致する要素のみを抜き出す") {
106 | val result = List(1, 2, 3).filter { 2 <= _ }
107 | assert(result == List(2, 3))
108 | }
109 |
110 | it("filter ... 条件に一致しない要素のみを抜き出す") {
111 | val result = List(1, 2, 3).filterNot { 2 <= _ }
112 | assert(result == List(1))
113 | }
114 |
115 | it("drop ... 指定した数の要素を先頭から取り除く") {
116 | assert(List(1, 2, 3).drop(0) == List(1, 2, 3))
117 | assert(List(1, 2, 3).drop(1) == List(2, 3))
118 | assert(List(1, 2, 3).drop(2) == List(3))
119 | assert(List(1, 2, 3).drop(3) == List())
120 | assert(List(1, 2, 3).drop(4) == List())
121 | }
122 |
123 | it("dropWhile ... 条件がfalseになるまで要素を取り除く") {
124 | assert(List(1, 2, 3).dropWhile { _ <= 2 } == List(3))
125 | assert(List(1, 2, 3).dropWhile { i =>
126 | i == 1 || i == 3
127 | } == List(2, 3))
128 | }
129 |
130 | it("take ... 指定した数の要素を先頭から取り出す") {
131 | assert(List(1, 2, 3).take(0) == List())
132 | assert(List(1, 2, 3).take(1) == List(1))
133 | assert(List(1, 2, 3).take(2) == List(1, 2))
134 | assert(List(1, 2, 3).take(3) == List(1, 2, 3))
135 | assert(List(1, 2, 3).take(4) == List(1, 2, 3))
136 | }
137 |
138 | it("takeWhile ... 条件がfalseになるまで要素を取り出す") {
139 | assert(List(1, 2, 3).takeWhile { _ <= 2 } == List(1, 2))
140 | assert(List(1, 2, 3).takeWhile { i =>
141 | i == 1 || i == 3
142 | } == List(1))
143 | }
144 |
145 | it("map ... 要素に関数を適用して新しいコレクションを返す") {
146 | assert(List(1, 2, 3).map(_ * 2) == List(2, 4, 6))
147 | }
148 |
149 | it("flatMap ... 要素に関数を適用して新しいコレクションを返しflattenする") {
150 | assert(List(1, 2, 3).flatMap(i => List(i, i * 2)) == List(1, 2, 2, 4, 3, 6))
151 | }
152 |
153 | it("flatten ... 入れ子になったコレクションを1次元にする") {
154 | assert(List(List(1, 2), List(3, 4)).flatten == List(1, 2, 3, 4))
155 | }
156 |
157 | it("splitAt ... コレクションを分割する") {
158 | assert(List(1, 2, 3, 4).splitAt(2) == (List(1, 2), List(3, 4)))
159 | }
160 |
161 | it("slice ... コレクションの一部を抜き出す") {
162 | assert(List(1, 2, 3, 4, 5).slice(1, 3) == List(2, 3))
163 | }
164 |
165 | it("partition ... 条件を満たす要素とそうでない要素に分割する") {
166 | assert(List(1, 2, 3, 4, 5).partition(_ < 3) == (List(1, 2), List(3, 4, 5)))
167 | assert(List(1, 2, 3, 4, 5).partition(_ > 3) == (List(4, 5), List(1, 2, 3)))
168 | }
169 |
170 | it("span ... 条件がfalseとなった要素を堺にコレクションを分割する") {
171 | assert(List(1, 2, 3, 4, 5).span(_ < 3) == (List(1, 2), List(3, 4, 5)))
172 | assert(List(1, 2, 3, 4, 5).span(_ > 3) == (List(), List(1, 2, 3, 4, 5)))
173 | }
174 |
175 | it("groupBy ... 関数の結果をキーとして要素をMapにして返す") {
176 | assert(List("hoge", "foo", "bar").groupBy(_.length) == Map(3 -> List("foo", "bar"), 4 -> List("hoge")))
177 | }
178 |
179 | it("grouped ... N件ごとにコレクションを分割する") {
180 | val list = List(1, 2, 3, 4, 5)
181 | assert(list.grouped(2).toList == List(List(1, 2), List(3, 4), List(5)))
182 | assert(list.grouped(3).toList == List(List(1, 2, 3), List(4, 5)))
183 | assertThrows[IllegalArgumentException](list.grouped(0))
184 | }
185 |
186 | it("unzip ... 要素を2つのコレクションに分割する") {
187 | assert(List((1, "one"), (2, "two"), (3, "three")).unzip == (List(1, 2, 3), List("one", "two", "three")))
188 | assert(Map(1 -> "one", 2 -> "two", 3 -> "three").unzip == (List(1, 2, 3), List("one", "two", "three")))
189 | }
190 |
191 | it("unzip3 ... 要素を3つのコレクションに分割する") {
192 | assert(
193 | List((1, "one", "hoge"), (2, "two", "foo"), (3, "three", "bar")).unzip3 == (
194 | List(1, 2, 3),
195 | List(
196 | "one",
197 | "two",
198 | "three"
199 | ),
200 | List("hoge", "foo", "bar")
201 | )
202 | )
203 | }
204 |
205 | it("find ... 最初に条件を満たした要素をOptionで返す") {
206 | assert(List(1, 2, 3).find(1 < _) == Some(2))
207 | assert(List(1, 2, 3).find(2 < _) == Some(3))
208 | assert(List(1, 2, 3).find(3 < _) == None)
209 | }
210 |
211 | it("exists ... 条件を満たす要素があるか判定する") {
212 | assert(List(1, 2, 3).exists(_ == 1) == true)
213 | assert(List(1, 2, 3).exists(_ == 5) == false)
214 | }
215 |
216 | it("forall ... 全ての要素が条件を満たすか判定する") {
217 | assert(List(1, 2, 3).forall(0 < _) == true)
218 | assert(List(1, 2, 3).forall(0 > _) == false)
219 | }
220 |
221 | it("count ... 条件を満たす要素の個数を返す") {
222 | assert(List(1, 2, 3).count(1 < _) == 2)
223 | assert(List(1, 2, 3).count(3 < _) == 0)
224 | }
225 |
226 | it("size/length ... 要素の個数を返す") {
227 | assert(List(1, 2, 3).size == 3)
228 | assert(List(1, 2, 3).length == 3)
229 | }
230 |
231 | it("min ... 最小値を返す") {
232 | assert(List(1, 2, 3).min == 1)
233 | assert(List("a", "b", "c").min == "a")
234 | }
235 |
236 | it("max ... 最大値を返す") {
237 | assert(List(1, 2, 3).max == 3)
238 | assert(List("a", "b", "c").max == "c")
239 | }
240 |
241 | it("sum ... 合計値を返す") {
242 | assert(List(1, 2, 3).sum == 6)
243 | // error
244 | // assert(List("a", "b", "c").sum == "")
245 | }
246 |
247 | it("mkString ... コレクションから文字列を作る") {
248 | assert(List(1, 2, 3).mkString == "123")
249 | assert(List(1, 2, 3).mkString("-") == "1-2-3")
250 | assert(List(1, 2, 3).mkString("[", "-", "]") == "[1-2-3]")
251 | }
252 |
253 | it("toArray ... コレクションをArrayに変換する") {
254 | // ScalaのArrayはJava配列で実装されているので == の比較はポインタの比較になる?
255 | // assert(List(1, 2, 3).toArray == Array(1, 2, 3))
256 | assert(List(1, 2, 3).toArray.deep == Array(1, 2, 3).deep)
257 | }
258 |
259 | it("toBuffer ... コレクションをBufferに変換する") {
260 | assert(List(1, 2, 3).toBuffer == ArrayBuffer(1, 2, 3))
261 | }
262 |
263 | it("toIndexedSeq ... コレクションをIndexedSeqに変換する") {
264 | assert(List(1, 2, 3).toIndexedSeq == IndexedSeq(1, 2, 3))
265 | }
266 |
267 | it("toList ... コレクションをListに変換する") {
268 | assert(Array(1, 2, 3).toList == List(1, 2, 3))
269 | }
270 |
271 | it("toStream ... コレクションをStreamに変換する") {
272 | assert(List(1, 2, 3).toStream == Stream(1, 2, 3))
273 | }
274 |
275 | it("par ... 並列コレクション(ParIterable)に変換する") {
276 | Range(1, 100).par.foreach(println(_))
277 | }
278 |
279 | it("view/force ... コレクションの操作を遅延評価させる(中間データを作らない)") {
280 | assert(List(1, 2, 3).view.map(_ * 2).force == List(2, 4, 6))
281 | }
282 |
283 | it("collect ... PartialFunctionを適用して要素を変換する") {
284 | val list = List(1, 2, 3, 4, 5)
285 | val result = list.collect {
286 | case 1 => "one"
287 | case 2 => "two"
288 | case 3 => "three"
289 | case _ => "-"
290 | }
291 | assert(result == List("one", "two", "three", "-", "-"))
292 | }
293 |
294 | it("collectFirst ... PartialFunctionに最初に一致した値を取得する") {
295 | val list = List(9, 7, 4, 2, 1, 3, 9)
296 | val result = list.collectFirst {
297 | case 1 => "one"
298 | case 2 => "two"
299 | case 3 => "three"
300 | }
301 | assert(result == Some("two"))
302 | }
303 |
304 | }
305 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/collections/TupleSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.collections
2 |
3 | import org.scalatest.FunSpec
4 |
5 | class TupleSpec extends FunSpec {
6 |
7 | override def suiteName: String = "Tuple - 任意の複数の値を保持するクラス"
8 |
9 | it("タプルを生成する") {
10 | val t = (1, "hoge", 1.0)
11 | }
12 |
13 | it("タプルから値を取り出す") {
14 | val t = (1, "hoge", 1.0)
15 | assert(t._1 == 1)
16 | assert(t._2 == "hoge")
17 | assert(t._3 == 1.0)
18 | }
19 |
20 | it("タプルの要素に意味付けをする") {
21 | val t = ("hoge", 20)
22 |
23 | // tuple._1 などとした場合、要素についての情報を何も伝えられないので、
24 | assert(t._1 == "hoge")
25 | assert(t._2 == 20)
26 |
27 | // このようにするとよい
28 | val (name, age) = t
29 | assert(name == "hoge")
30 | assert(age == 20)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/event/understanding_scala/UnderstandingScalaFormulaSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.event.understanding_scala
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scala.annotation.tailrec
6 |
7 | /** 6/10 Understanding Scala ~Scalaを理解しよう~ https://connpass.com/event/55308/
8 | *
9 | * Scalaの実行時の挙動を学ぶ http://kmizu.github.io/understanding_scala/semantics/#/22
10 | */
11 | class UnderstandingScalaFormulaSpec extends FunSpec {
12 |
13 | override def suiteName: String = "[勉強会] Understanding Scala - Scalaの実行時の挙動を学ぶ"
14 |
15 | it("メソッド呼び出し式") {
16 |
17 | /** 関数内関数を除いたすべての操作はメソッド呼び出し 演算子の優先順位は考慮される → 最初の一文字で決まる → :で終わるメソッドは右結合
18 | */
19 | // TODO: Add samples.
20 | }
21 |
22 | it("while式") {
23 |
24 | /** Javaと変わらない 式なので値を返す→Unit
25 | */
26 | var a = 1
27 | val unit: Unit = while (a < 3) {
28 | a += 1
29 | }
30 | }
31 |
32 | it("if式") {
33 |
34 | /** else部がない場合、Unitの値の()が補われる
35 | */
36 | val i = 1
37 | val a: String = if (0 <= i) "positive" else "negative"
38 | // StringとUnitの共通のスーパータイプAnyが返る
39 | val b: Any = if (0 <= i) "positive"
40 | }
41 |
42 | it("for式(1)") {
43 |
44 | /** foreach,map,flatMapなどのシンタックスシュガー
45 | */
46 | for {
47 | i <- 1 to 5
48 | j <- 1 to 5
49 | } {
50 | println(i, j)
51 | }
52 | // ↑同等↓
53 | (1 to 5).foreach { i =>
54 | (1 to 5).foreach { j =>
55 | println(i, j)
56 | }
57 | }
58 | }
59 |
60 | it("for式(2)") {
61 |
62 | /** yield
63 | */
64 | for (i <- 1 to 5) yield i
65 | // ↑同等↓
66 | (1 to 5).map { i =>
67 | i
68 | }
69 | }
70 |
71 | it("for式(3)") {
72 | for {
73 | i <- 1 to 5
74 | j <- 1 to 5
75 | } yield (i, j)
76 | // ↑同等↓
77 | (1 to 5).flatMap { i =>
78 | (1 to 5).map { j =>
79 | (i, j)
80 | }
81 | }
82 | }
83 |
84 | it("for式(4)") {
85 |
86 | /** 実際のfor式の意味は定義されたデータ型によって異なる + Future + Option + Try ...など 正確な理解nためには、どのように展開されるかを知る必要がある 参考
87 | * https://www.scala-lang.org/files/archive/spec/2.12/06-expressions.html#for-comprehensions-and-for-loops
88 | */
89 | // TODO: Add samples.
90 | }
91 |
92 | it("match式") {
93 | case class Hoge(a: Int, b: Int)
94 | Hoge(1, 2) match {
95 | // Scalaの言語仕様として、引数が2つのcase classなどのパターンマッチには
96 | // 型名を中置することができる
97 | case 1 Hoge 2 => println("ok")
98 | case Hoge(1, 2) => fail()
99 | }
100 |
101 | // ===
102 |
103 | // パターンマッチと(末尾)再帰関数と相性がいい
104 | def reverse[A](list: List[A]): List[A] = {
105 | @tailrec def go(acc: List[A], rest: List[A]): List[A] = rest match {
106 | case x :: xs => go(x :: acc, xs)
107 | case _ => acc
108 | }
109 | go(Nil, list)
110 | }
111 | assert(reverse(List(1, 2, 3)) == List(3, 2, 1))
112 |
113 | // ===
114 |
115 | // 自分で作ったデータ構造(特に木構造)い対するマッチを行うこともできる
116 | // 言語処理系を作る時にも役立つ機能
117 | trait E
118 | case class Add(e1: E, e2: E) extends E
119 | case class Sub(e1: E, e2: E) extends E
120 | case class Num(v: Int) extends E
121 | def eval(e: E): Int = e match {
122 | case Add(e1, e2) => eval(e1) + eval(e2)
123 | case Sub(e1, e2) => eval(e1) - eval(e2)
124 | case Num(v) => v
125 | }
126 | // 1 + 2 = 3
127 | assert(eval(Add(Num(1), Num(2))) == 3)
128 | // 10 - (5 + 5) = 0
129 | assert(eval(Sub(Num(10), Add(Num(5), Num(5)))) == 0)
130 |
131 | // ===
132 |
133 | // unapplyを定義したオブジェクトはパターンとして利用できる
134 | // Extractorという機能
135 | object PositiveNumber {
136 | def unapply(n: Int): Option[Int] =
137 | if (0 <= n) Some(n)
138 | else None
139 | }
140 | 1 match {
141 | case PositiveNumber(n) => assert(n == 1)
142 | case _ => fail()
143 | }
144 | -1 match {
145 | case PositiveNumber(_) => fail()
146 | case _ => println("ok")
147 | }
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/event/understanding_scala/UnderstandingScalaImplicitSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.event.understanding_scala
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** 6/10 Understanding Scala ~Scalaを理解しよう~ https://connpass.com/event/55308/
6 | *
7 | * Scalaのimplicit parameterを学ぶ http://kmizu.github.io/understanding_scala/implicit_parameter/#/14
8 | */
9 | class UnderstandingScalaImplicitSpec extends FunSpec {
10 |
11 | override def suiteName: String = "[勉強会] Understanding Scala - Scalaのimplicit parameterを学ぶ"
12 |
13 | it("implicit parameter") {
14 |
15 | /** implicit conversion とごっちゃにされやすい 正しく使えば非常に強力 こわくない!
16 | */
17 | /** 例題 + リストの要素をすべて足し合わせた値を返す関数 + ただし、あとから特定の型を足すことができること ++ 有理数(Rational)、複素数(Complext)など
18 | */
19 | // 素直な回答
20 | def sum(list: List[Int]): Int = list.foldLeft(0)(_ + _)
21 |
22 | assert(sum(List(1, 2, 3)) == 6)
23 | // だめ
24 | // sum(List(1.0, 2.0, 3.0) == 9.0)
25 | }
26 |
27 | it("Monoidを使ってsumを書き直す") {
28 | trait Monoid[A] {
29 | def plus(a: A, b: A): A
30 | def zero: A
31 | }
32 | def sum2[A](list: List[A])(m: Monoid[A]): A =
33 | list.foldLeft(m.zero)(m.plus)
34 | object IntMonoid extends Monoid[Int] {
35 | override def plus(a: Int, b: Int): Int = a + b
36 | override def zero: Int = 0
37 | }
38 | object DoubleMonoid extends Monoid[Double] {
39 | override def plus(a: Double, b: Double): Double = a + b
40 | override def zero: Double = 0.0
41 | }
42 | assert(sum2(List(1, 2, 3))(IntMonoid) == 6)
43 | assert(sum2(List(1.0, 2.0, 3.0))(DoubleMonoid) == 6.0)
44 | // ↑ 省略したい! ↑
45 | // そこでimplicit parameterを使う↓
46 | }
47 |
48 | it("implicit parameterの導入") {
49 | trait Monoid[A] {
50 | def plus(a: A, b: A): A
51 | def zero: A
52 | }
53 | object Monoid {
54 | implicit object IntMonoid extends Monoid[Int] {
55 | override def plus(a: Int, b: Int): Int = a + b
56 | override def zero: Int = 0
57 | }
58 | implicit object DoubleMonoid extends Monoid[Double] {
59 | override def plus(a: Double, b: Double): Double = a + b
60 | override def zero: Double = 0.0
61 | }
62 | }
63 | def sum3[A](list: List[A])(implicit m: Monoid[A]): A =
64 | list.foldLeft(m.zero)(m.plus)
65 | // 省略できた!
66 | assert(sum3(List(1, 2, 3)) == 6)
67 | assert(sum3(List(1.0, 2.0, 3.0)) == 6.0f)
68 | }
69 |
70 | it("implicit parameterの仕組み") {
71 |
72 | /** + implicit修飾子が付いた引数m: Monoid[A]があったときに Aが特定の型に確定していれば + MonoidのコンパニオンオブジェクトからMonoidのインスタンスを探す +
73 | * Monoidの型パラメータ(たとえばInt`)のコンパニオンオブジェクトを探す + importされたオブジェクトの下にimplicit宣言されたオブジェクトがないか探す
74 | */
75 | }
76 |
77 | it("Scala標準ライブラリにおけるimplicit parameterの例") {
78 | assert(List(1, 2, 3).sum == 6)
79 | assert(List(4, 5, 6).product == 120)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/event/understanding_scala/UnderstandingScalaTrapSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.event.understanding_scala
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scala.concurrent.Future
6 |
7 | /** 6/10 Understanding Scala ~Scalaを理解しよう~ https://connpass.com/event/55308/
8 | *
9 | * Scalaの謎・落とし穴を学ぶ http://kmizu.github.io/understanding_scala/pitfall/#/7
10 | */
11 | class UnderstandingScalaTrapSpec extends FunSpec {
12 |
13 | override def suiteName: String = "[勉強会] Understanding Scala - Scalaの落とし穴を学ぶ"
14 |
15 | it("Unitを返す関数を意図せず書いてしまう") {
16 | // Javaのノリで書くとUnitが返る関数ができる
17 | def double(x: Int) {
18 | x * 2
19 | }
20 | assert(double(1).isInstanceOf[Unit])
21 |
22 | // 解決策: 戻り値の型を明示的に書く
23 | def double2(x: Int): Int = {
24 | x * 2
25 | }
26 | assert(double2(1) == 2)
27 | }
28 |
29 | it("変更可能コレクションの変更しない操作を呼び出してしまう") {
30 | val buffer = scala.collection.mutable.Buffer(1, 2, 3)
31 | assert(buffer.drop(1) == scala.collection.mutable.Buffer(2, 3))
32 | assert(buffer == scala.collection.mutable.Buffer(1, 2, 3))
33 | }
34 |
35 | it("配列同士の==") {
36 | val x = Array(1, 2, 3)
37 | val y = Array(1, 2, 3)
38 | // 配列はJVMの配列が使われるので、参照が一致しているかが比較される
39 | assert(!(x == y))
40 | // Arrays.deepEqualsを使う
41 | // TODO: Fix the compile error ↓
42 | // assert(util.Arrays.deepEquals(x, y))
43 | }
44 |
45 | it("誤ったFutureの使い方") {
46 | import scala.concurrent.ExecutionContext.Implicits.global
47 |
48 | // 以下のコードでは、処理が平行に実行されない
49 | for {
50 | f1 <- Future(1 + 2)
51 | f2 <- Future(3 + 4)
52 | } yield f1 + f2
53 | // 先にFutureを格納する変数を用意する
54 | val f1 = Future(1 + 2)
55 | val f2 = Future(3 + 4)
56 | for {
57 | a <- f1
58 | b <- f2
59 | } yield a + b
60 | }
61 |
62 | it("意図しない結果のパターンマッチ") {
63 | // TODO: Add sample code.
64 | }
65 |
66 | it("誤った正規表現のパターンマッチ") {
67 | // TODO: Add sample code.
68 | }
69 |
70 | it("既存の型同士のimplicit conversionは使わない") {
71 | {
72 | // だめ!
73 | implicit def int2boolean(n: Int): Boolean = n != 0
74 |
75 | if (1) {
76 | // doSomething
77 | }
78 | }
79 | }
80 |
81 | it("改行とブロックの組み合わせに注意") {
82 | // TODO: Add sample code.
83 | }
84 |
85 | it("Javaのメソッドの返り値に注意") {
86 | // Javaのメソッドはnullが返って来る
87 | val m = new java.util.HashMap[Int, String]
88 | assertThrows[NullPointerException](m.get(1).toLowerCase)
89 | }
90 |
91 | it("Set#mapの罠") {
92 | // Scalaのコレクションは可能な限り自分と同じ型を返そうとする
93 | assert(Set(1, 2, 3).map(_ => 2) == Set(2))
94 | // 対策: toListなどで他のコレクションに変換する
95 | assert(Set(1, 2, 3).toList.map(_ => 2) == List(2, 2, 2))
96 | }
97 |
98 | it("インナークラスのインスタンス") {
99 | class Outer {
100 | class Inner
101 | val I = new Inner
102 | }
103 | val outer = new Outer
104 | val inner: Outer#Inner = outer.I
105 | }
106 |
107 | it("アンダースコア七変化") {
108 | // ワイルドカードインポート
109 | {}
110 |
111 | // 特定クラスを除外してインポート
112 | {
113 | import java.util.{List => _}
114 | }
115 |
116 | // ワイルドカードパターン
117 | 1 match {
118 | case _ => // 必ずマッチする
119 | }
120 |
121 | // 仮引数を使わない
122 | List(1, 2, 3).map(_ => 4)
123 |
124 | // 可変長引数にコレクションを分解して渡す
125 | // _単体では意味はなく、:_*の組み合わせで意味を持つ
126 | printf("%d", List(1): _*)
127 |
128 | // メソッドを関数の型に変換する
129 | // メソッドはファーストクラスの型ではない
130 | val f: () => List[Int] = List(1, 2, 3).reverse _
131 |
132 | // プレースホルダ構文
133 | // 構文解析に作用する点で特異
134 | List(1, 2, 3).map(_ * 2)
135 | }
136 |
137 | }
138 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/event/understanding_scala/UnderstandingScalaTypeSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.event.understanding_scala
2 |
3 | import java.io.FileInputStream
4 | import java.util
5 |
6 | import org.scalatest.FunSpec
7 |
8 | /** 6/10 Understanding Scala ~Scalaを理解しよう~ https://connpass.com/event/55308/
9 | *
10 | * Scalaの型システムを学ぶ http://kmizu.github.io/understanding_scala/type_system/#/14
11 | */
12 | class UnderstandingScalaTypeSpec extends FunSpec {
13 |
14 | override def suiteName: String = "[勉強会] Understanding Scala - Scalaの型システムを学ぶ"
15 |
16 | it("Any あらゆる型のスーパータイプ") {
17 |
18 | /** 最低限のメソッドのみを提供
19 | */
20 | val a: Any = 10
21 | assert(a == 10)
22 | assert(a != 20)
23 | assert(a.toString == "10")
24 | assert(a.hashCode == 10)
25 | assert(a.equals(10))
26 | }
27 |
28 | it("AnyVal: あらゆる値型のスーパータイプ") {
29 |
30 | /** Javaでいうプリミティブ型をまとめたもの AnyValにnullは代入できない 便宜上存在しているが、AnyValに意味があることは少ない
31 | */
32 | val int: AnyVal = 10
33 | val double: AnyVal = 1.0
34 | val char: AnyVal = 'a'
35 | val bool: AnyVal = true
36 | val unit: AnyVal = {}
37 | // だめ
38 | // val nul: AnyVal = null
39 | }
40 |
41 | it("AnyRef: あらゆる参照型のスーパータイプ") {
42 |
43 | /** Javaでいうjava.lang.Object 参照型のすべての値はAnyRefに代入できる
44 | */
45 | // だめ
46 | // val int: AnyRef = 10
47 | val string: AnyRef = "hoge"
48 | // eq ... 参照の等価性を判定する
49 | assert(string eq string)
50 | }
51 |
52 | it("Nothing: あらゆる型のサブタイプ") {
53 |
54 | /** Javaでは相当する型が存在しない あらゆる型のサブタイプ → あらゆる型の変数に代入可能 → Nothing型の値は存在しない 存在意義 → 必ず例外を投げて正常にreturnしないメソッドの型になる →
55 | * ジェネリクスと組み合わせて使う
56 | */
57 | def method(): Nothing = { throw new Exception() }
58 | }
59 |
60 | it("Null") {
61 |
62 | /** あらゆる参照型のサブタイプ 値はnullのみ nullはあらゆる参照型に代入可能
63 | */
64 | val string: String = null
65 | // だめ
66 | // val int: Int = null
67 | }
68 |
69 | it("ジェネリクス") {
70 |
71 | /** 最近の静的型付き言語のほとんどがもっている 型をパラメータとして取ること柔軟な型定義ができる
72 | */
73 | val strings = new util.ArrayList[String]
74 | strings.add("hoge")
75 | // コンパイルエラー↓
76 | // strings.add(1)
77 | }
78 |
79 | it("共変") {
80 | val x: Array[String] = Array("a", "b", "c")
81 | // コンパイルエラー↓
82 | // val y:Array[Any] = x
83 |
84 | // これを許すと、Stringの配列にIntが入るといった状況が生まれる
85 | // y(0) = 1
86 |
87 | /** + String と Any について、StringがAnyのサブタイプである場合、 Array[String]がArray[Any]のサブタイプであるとき、 Arrayは共変であるという。 +
88 | * 実際にはScalaのArrayは共変ではない(不変) 一方、Javaの配列は共変だが、実行時に例外が投げられる危険がある。 + 共変は便利だが、制限無しで取り扱うのは危険。 何らかの制限が必要→共変性に関する注釈をつける
89 | */
90 | sealed trait Link[+T] // + がだいじ
91 | case class Cons[T](head: T, tail: Link[T]) extends Link[T]
92 | // EmptyはどのようなLinkの変数にも代入できる
93 | // Linkの終端を表すのに使うことができる
94 | case object Empty extends Link[Nothing]
95 | // 1,2,3 からなる単方向連結リスト
96 | val cons = Cons(1, Cons(2, Cons(3, Empty)))
97 |
98 | cons match {
99 | case Cons(head, tail) =>
100 | assert(head == 1)
101 | assert(tail == Cons(2, Cons(3, Empty)))
102 | case _ => fail()
103 | }
104 | }
105 |
106 | it("反変") {
107 |
108 | /** 関数の型は引数の型に関して「共変ではない」
109 | */
110 | val x: Int => Any = { i =>
111 | i
112 | }
113 | // コンパイルエラー
114 | // val y: Any => Any = x
115 |
116 | // これを許すと、Int引数にStringを渡せてしまうという状況が生まれる。
117 | // y("hoge")
118 |
119 | /** 引数の型は引数の型に関して、「反変である」
120 | */
121 | val xx: Any => Any = { a =>
122 | a
123 | }
124 | val yy: Int => Any = x // OK
125 |
126 | /** 型の汎化(スーパタイプ)を許容する。 Scalaでは、[-T]のように、型パラメータに - を付けると反変になる。
127 | */
128 | class Animal
129 | class Human extends Animal
130 | class Kaban extends Human
131 | class JapariPark[-A] {
132 | def welcomeTo(arg: A): String = "ようこそジャパリパークへ"
133 | }
134 |
135 | val japari1: JapariPark[Kaban] = new JapariPark[Human]
136 | assert(japari1.welcomeTo(new Kaban) == "ようこそジャパリパークへ")
137 | val japari2: JapariPark[Human] = new JapariPark[Animal]
138 | assert(japari2.welcomeTo(new Human) == "ようこそジャパリパークへ")
139 | // だめ
140 | // val japari3: JapariPark[Human] = new JapariPark[Kaban]
141 | }
142 |
143 | it("構造的部分型") {
144 |
145 | /** 継承関係によらず、必要なメソッドを持っていれば要求を満たす、としたい場合がある。 動的型付け言語における、duck typing的な考え。
146 | */
147 | import scala.language.reflectiveCalls
148 |
149 | def using[T <: { def close() }, U](r: T)(f: T => U): U =
150 | try {
151 | f(r)
152 | } finally {
153 | r.close()
154 | }
155 |
156 | using(new FileInputStream("build.sbt")) { f =>
157 | // do something
158 | }
159 |
160 | /** 内部的にはリフレクションを使っているので多用に注意。 import scala.language.reflectiveCalls をつけないと警告が出る。
161 | */
162 | }
163 |
164 | it("高階多相") {
165 |
166 | /** List などのコレクションや Option など様々な型が map メソッドを持っている とにかく map を持っている型を抽象化したい そのような型 Mapper を定義してみる
167 | */
168 | trait Mapper[C] {
169 | def map[A, B](c: C)(f: C => C): C
170 | }
171 |
172 | /** C の要素の型は map の呼び出しによって変わるので通常のジェネリクスでは表現できない
173 | */
174 | trait Mapper2[C[_]] {
175 | def map[A, B](c: C[A])(f: A => B): C[B]
176 | }
177 | object Mapper2 {
178 | implicit object ListMapper extends Mapper2[List] {
179 | override def map[A, B](c: List[A])(f: (A) => B): List[B] = c.map(f)
180 | }
181 | implicit object OptionMapper extends Mapper2[Option] {
182 | override def map[A, B](c: Option[A])(f: (A) => B): Option[B] = c.map(f)
183 | }
184 | }
185 | def add2[C[_]](c: C[Int])(implicit m: Mapper2[C]): C[Int] = {
186 | m.map(c)(n => n + 2)
187 | }
188 | assert(add2(List(1, 2, 3)) == List(3, 4, 5))
189 | assert(add2(Option(1)) == Some(3))
190 |
191 | /** C[_]が肝心 型コンストラクタを引数に取ることを表す宣言 型コンストラクタ … ジェネリックなクラスに型が与えられる前の名前のこと + List[T] → List + Option[T] → Option
192 | */
193 | }
194 |
195 | }
196 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/exceptions/ExceptionSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.exceptions
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** try-catch-finally
6 | *
7 | * try { 式 } catch { case 変数:例外クラスの型 => 例外処理の式 ... } finally { 式 }
8 | */
9 | class ExceptionSpec extends FunSpec {
10 |
11 | override def suiteName: String = "例外処理"
12 |
13 | it("基本的なtry-catch-finally") {
14 | val result =
15 | try {
16 | "a".toInt
17 | } catch {
18 | case e: NumberFormatException => {
19 | println("exception!")
20 | -1
21 | }
22 | } finally {
23 | println("finally!")
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/exceptions/OptionSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.exceptions
2 |
3 | import org.scalatest.{FunSpec, Matchers}
4 |
5 | /** Created by blacky on 16/12/04.
6 | */
7 | class OptionSpec extends FunSpec with Matchers {
8 |
9 | override def suiteName: String = "Option - 値が存在しない可能性があることを表すクラス"
10 |
11 | it("Optionの基本") {
12 | // Option[+A] ... 値が存在しない可能性があることを表すクラス
13 |
14 | // Some の場合は値が存在し、
15 | val o1: Option[Int] = Some(1)
16 |
17 | // None の場合は値が存在しない
18 | val o2: Option[Int] = None
19 |
20 | o1.get shouldBe 1
21 |
22 | // Noneのときにgetするとエラー
23 | assertThrows[NoSuchElementException](o2.get)
24 | }
25 |
26 | it("Optionから値を取り出す") {
27 | val o1 = Some(1)
28 | val o2 = None
29 |
30 | // 主にmatch式や、
31 | o1 match {
32 | case Some(i) => i shouldBe 1
33 | case _ => fail()
34 | }
35 | o2 match {
36 | case None =>
37 | case _ => fail()
38 | }
39 |
40 | // getOrElseを使う
41 | o1.getOrElse(2) shouldBe 1
42 | o2.getOrElse(2) shouldBe 2
43 | }
44 |
45 | it("foreach ... Optionに値が含まれる場合のみに実行させる") {
46 | val o1 = Some(1)
47 | val o2 = None
48 |
49 | // foreach を使うと、Someの場合のみに実行させるといったことができる
50 | // 要素数1のリストとイメージすると foreach という命名がわかりやすい
51 | o1 foreach { i =>
52 | i shouldBe 1
53 | }
54 | o2 foreach { _ =>
55 | fail()
56 | }
57 | }
58 |
59 | it("map ... 中身の値を関数に適用し値を変換する") {
60 | val o1: Option[Int] = Some(1)
61 | val o2: Option[Int] = None
62 |
63 | o1.map(_ + 10) shouldBe Some(11)
64 | o2.map(_ + 10) shouldBe None
65 | }
66 |
67 | it("flatMap ... 中身の値を関数に適用し、SomeならSomeを、NoneならNoneを返す") {
68 | val o1: Option[Int] = Some(1)
69 | val o2: Option[Int] = None
70 |
71 | o1.flatMap(i => Some(i + 10)) shouldBe Some(11)
72 | o1.flatMap(_ => None) shouldBe None
73 | o2.flatMap(i => Some(i + 10)) shouldBe None
74 | o2.flatMap(_ => None) shouldBe None
75 | }
76 |
77 | it("collect ... PartialFunctionを適用し、値が返る場合はその結果をSomeに包んで返す") {
78 | val o1: Option[Int] = Some(1)
79 | val o2: Option[Int] = Some(2)
80 | val none: Option[Int] = None
81 |
82 | val pf: PartialFunction[Int, String] = { case 1 =>
83 | "one"
84 | }
85 | o1.collect(pf) shouldBe Some("one")
86 | o2.collect(pf) shouldBe None
87 | none.collect(pf) shouldBe None
88 |
89 | // Someを返す関数を渡すflatMapはcollectで簡略化できる
90 | val pf2: PartialFunction[Int, Option[String]] = {
91 | case 1 => Some("one")
92 | case _ => None
93 | }
94 | o1.flatMap(pf2) shouldBe Some("one")
95 | o2.flatMap(pf2) shouldBe None
96 | none.flatMap(pf2) shouldBe None
97 | }
98 |
99 | it("fold ... Noneなら初期値を、Someなら関数を適用した値を返す") {
100 | val o1: Option[Int] = Some(10)
101 | val o2: Option[Int] = None
102 |
103 | o1.fold(-1)(_ * 10) shouldBe 100
104 | o2.fold(-1)(_ * 10) shouldBe -1
105 |
106 | // map と getOrElse を使った場合と同義
107 | o1.fold(-1)(_ * 10) shouldBe o1.map(_ * 10).getOrElse(-1)
108 | o2.fold(-1)(_ * 10) shouldBe o2.map(_ * 10).getOrElse(-1)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter02/Chapter02Spec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter02
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** Created by blacky on 17/02/13.
6 | *
7 | * Scala関数型デザイン&プログラミング―Scalazコントリビューターによる関数型徹底ガイド
8 | * https://www.amazon.co.jp/dp/B00WM54V5Q/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
9 | */
10 | class Chapter02Spec extends FunSpec {
11 |
12 | override def suiteName: String = "[FP in Scala] 第2章 Scala関数型プログラミングの準備"
13 |
14 | it("[EXERCISE 2.1] フィボナッチ数") {
15 | def fib(n: Int): Int = {
16 | // 末尾呼び出しの除去が出来ない場合に、コンパイルエラーにするアノテーション
17 | @annotation.tailrec
18 | def go(i: Int, n: Int, a: Int, b: Int): Int =
19 | if (n <= i) a
20 | else go(i + 1, n, b, a + b)
21 | go(0, n, 0, 1)
22 | }
23 |
24 | List(0, 1, 1, 2, 3, 5, 8, 13, 21).zipWithIndex.foreach { case (actual, index) =>
25 | assert(fib(index) == actual)
26 | }
27 | }
28 |
29 | it("[EXERCISE 2.1] フィボナッチ数(Stream)") {
30 | def fibStream(a: Int, b: Int): Stream[Int] = a #:: fibStream(b, a + b)
31 |
32 | List(0, 1, 1, 2, 3, 5, 8, 13, 21).zipWithIndex.foreach { case (actual, index) =>
33 | assert(fibStream(0, 1)(index) == actual)
34 | }
35 | }
36 |
37 | it("[EXERCISE 2.2] isSortedの実装") {
38 | def isSorted[A](as: Array[A], ordered: (A, A) => Boolean): Boolean = {
39 | @annotation.tailrec
40 | def compare(i: Int): Boolean = {
41 | if (as.length <= i + 1) true
42 | else if (ordered(as(i), as(i + 1))) compare(i + 1)
43 | else false
44 | }
45 | compare(0)
46 | }
47 |
48 | assert(isSorted(Array(1, 2, 3), (a: Int, b: Int) => a <= b) == true)
49 | assert(isSorted(Array(1, 3, 2), (a: Int, b: Int) => a <= b) == false)
50 | assert(isSorted(Array("a", "bb", "ccc"), (a: String, b: String) => a.length <= b.length) == true)
51 | assert(isSorted(Array("aa", "bbb", "c"), (a: String, b: String) => a.length <= b.length) == false)
52 | }
53 |
54 | it("[EXERCISE 2.3] カリー化") {
55 | def curry[A, B, C](f: (A, B) => C): A => (B => C) = { (a: A) => (b: B) =>
56 | f(a, b)
57 | }
58 |
59 | val f1: (Int, Int) => String = (a: Int, b: Int) => (a + b).toString
60 | assert(f1(1, 2) == "3")
61 | assert(curry(f1)(1)(2) == "3")
62 |
63 | val f2 = (a: String, b: Int) => a * b
64 | assert(f2("a", 3) == "aaa")
65 | assert(curry(f2)("a")(3) == "aaa")
66 | }
67 |
68 | it("[EXERCISE 2.4] 逆カリー化") {
69 | def uncurry[A, B, C](f: A => B => C): (A, B) => C = { (a: A, b: B) =>
70 | f(a)(b)
71 | }
72 |
73 | val f1: (Int) => (Int) => String = (a: Int) => (b: Int) => (a + b).toString
74 | assert(f1(1)(2) == "3")
75 | assert(uncurry(f1)(1, 2) == "3")
76 |
77 | val f2 = (a: String) => (b: Int) => a * b
78 | assert(f2("a")(3) == "aaa")
79 | assert(uncurry(f2)("a", 3) == "aaa")
80 | }
81 |
82 | it("[EXERCISE 2.5] 関数の合成") {
83 | def compose[A, B, C](f: B => C, g: A => B): A => C = { (a: A) =>
84 | f(g(a))
85 | }
86 |
87 | val f1: (Int) => String = (b: Int) => b.toString
88 | val f2: (Int) => Int = (a: Int) => a + 10
89 | assert(f1(f2(1)) == "11")
90 | assert(compose(f1, f2)(1) == "11")
91 | assert(f1.compose(f2)(1) == "11")
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter03/Chapter03Spec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter03
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** Created by blacky on 17/02/13.
6 | *
7 | * Scala関数型デザイン&プログラミング―Scalazコントリビューターによる関数型徹底ガイド
8 | * https://www.amazon.co.jp/dp/B00WM54V5Q/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
9 | */
10 | class Chapter03Spec extends FunSpec {
11 |
12 | override def suiteName: String = "[FP in Scala] 第3章 関数型プログラミングのデータ構造"
13 |
14 | it("[EXERCISE 3.1] match式") {
15 | val v = MyList(1, 2, 3, 4, 5) match {
16 | case Cons(x, Cons(2, Cons(4, _))) => x
17 | case MyNil => 42
18 | case Cons(x, Cons(y, Cons(3, Cons(4, _)))) => x + y
19 | case Cons(h, t) => h + MyList.sum(t)
20 | case _ => 101
21 | }
22 |
23 | assert(v == 3)
24 | }
25 |
26 | it("[EXERCISE 3.2] tailの実装") {
27 | assert(MyList.tail(MyList(1, 2, 3)) == MyList(2, 3))
28 | assert(MyList.tail(MyList(1, 2)) == MyList(2))
29 | assert(MyList.tail(MyList(1)) == MyNil)
30 | assert(MyList.tail(MyNil) == MyNil)
31 | }
32 |
33 | it("[EXERCISE 3.3] setHeadの実装") {
34 | assert(MyList.setHead(MyList(1, 2, 3), 9) == MyList(9, 2, 3))
35 | assert(MyList.setHead(MyList(1, 2), 9) == MyList(9, 2))
36 | assert(MyList.setHead(MyList(1), 9) == MyList(9))
37 | assert(MyList.setHead(MyNil, 9) == MyNil)
38 | }
39 |
40 | it("[EXERCISE 3.4] dropの実装") {
41 | assert(MyList.drop(MyList(1, 2, 3), 0) == MyList(1, 2, 3))
42 | assert(MyList.drop(MyList(1, 2, 3), 1) == MyList(2, 3))
43 | assert(MyList.drop(MyList(1, 2, 3), 2) == MyList(3))
44 | assert(MyList.drop(MyList(1, 2, 3), 3) == MyNil)
45 | assert(MyList.drop(MyNil, 3) == MyNil)
46 | }
47 |
48 | it("[EXERCISE 3.5] dropWhileの実装") {
49 | assert(MyList.dropWhile(MyList(1, 2, 3), (i: Int) => i <= 3) == MyNil)
50 | assert(MyList.dropWhile(MyList(1, 2, 3), (i: Int) => i <= 2) == MyList(3))
51 | assert(MyList.dropWhile(MyList(1, 2, 3), (i: Int) => i <= 1) == MyList(2, 3))
52 | assert(MyList.dropWhile(MyList(1, 2, 3), (i: Int) => i <= 0) == MyList(1, 2, 3))
53 |
54 | // カリー化することで型推論が手助けされる
55 | // 引数リストの左の型パラメータが決まると、右の型パラメータが固定されるため
56 | assert(MyList.dropWhile2(MyList(1, 2, 3))(_ <= 3) == MyNil)
57 | assert(MyList.dropWhile2(MyList(1, 2, 3))(_ <= 2) == MyList(3))
58 | assert(MyList.dropWhile2(MyList(1, 2, 3))(_ <= 1) == MyList(2, 3))
59 | assert(MyList.dropWhile2(MyList(1, 2, 3))(_ <= 0) == MyList(1, 2, 3))
60 | }
61 |
62 | it("[EXERCISE 3.6] initの実装") {
63 | assert(MyList.init(MyList(1, 2, 3)) == MyList(1, 2))
64 | assert(MyList.init(MyList(1, 2)) == MyList(1))
65 | assert(MyList.init(MyList(1)) == MyNil)
66 | assert(MyList.init(MyNil) == MyNil)
67 | }
68 |
69 | it("[EXERCISE 3.9] lengthの実装") {
70 | assert(MyList.length(MyList(1, 2, 3)) == 3)
71 | assert(MyList.length(MyList(1, 2)) == 2)
72 | assert(MyList.length(MyList(1)) == 1)
73 | assert(MyList.length(MyNil) == 0)
74 | }
75 |
76 | it("[EXERCISE 3.10] foldLeftの実装") {
77 | assert(MyList.foldLeft(MyList(1, 2, 3, 4, 5), 0)(_ + _) == 15)
78 | assert(MyList.foldLeft(MyList(1, 2, 3, 4, 5), 1)(_ * _) == 120)
79 | }
80 |
81 | it("[EXERCISE 3.11] foldLeftを使った、sum, product, lengthの実装") {
82 | assert(MyList.sum2(MyList(1, 2, 3, 4, 5)) == 15)
83 | assert(MyList.product2(MyList(1, 2, 3, 4, 5)) == 120)
84 | assert(MyList.length(MyList(1, 2, 3, 4, 5)) == 5)
85 | }
86 |
87 | it("[EXERCISE 3.12] reverseの実装") {
88 | assert(MyList.reverse(MyList(1, 2, 3)) == MyList(3, 2, 1))
89 | assert(MyList.reverse(MyNil) == MyNil)
90 | }
91 |
92 | it("[EXERCISE 3.14] foldRitghtを利用したappendの実装") {
93 | assert(MyList.appendViaFoldRight(MyList(1, 2), MyList(3, 4)) == MyList(1, 2, 3, 4))
94 | }
95 |
96 | it("[EXERCISE 3.15] flattenの実装") {
97 | val listInList = MyList(MyList(1, 2), MyList(3, 4))
98 | assert(MyList.flatten(listInList) == MyList(1, 2, 3, 4))
99 | assert(MyList.flatten2(listInList) == MyList(1, 2, 3, 4))
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter03/MyList.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter03
2 |
3 | /** Created by blacky on 17/02/16.
4 | */
5 | sealed trait MyList[+A]
6 | case object MyNil extends MyList[Nothing]
7 | case class Cons[+A](head: A, tail: MyList[A]) extends MyList[A]
8 |
9 | object MyList {
10 |
11 | def sum(ints: MyList[Int]): Int = ints match {
12 | case MyNil => 0
13 | case Cons(x, xs) => x + sum(xs)
14 | }
15 |
16 | def product(ds: MyList[Double]): Double = ds match {
17 | case MyNil => 1.0
18 | case Cons(0.0, _) => 0.0
19 | case Cons(x, xs) => x * product(xs)
20 | }
21 |
22 | def apply[A](as: A*): MyList[A] =
23 | if (as.isEmpty) MyNil
24 | else Cons(as.head, apply(as.tail: _*))
25 |
26 | def tail[A](list: MyList[A]): MyList[A] = list match {
27 | case MyNil => MyNil
28 | case Cons(_, MyNil) => MyNil
29 | case Cons(_, Cons(x, y)) => Cons(x, y)
30 | }
31 |
32 | def setHead[A](list: MyList[A], a: A): MyList[A] = list match {
33 | case MyNil => MyNil
34 | case Cons(_, MyNil) => Cons(a, MyNil)
35 | case Cons(_, Cons(x, y)) => Cons(a, Cons(x, y))
36 | }
37 |
38 | def drop[A](list: MyList[A], i: Int): MyList[A] = {
39 | if (i <= 0) list
40 | else
41 | list match {
42 | case MyNil => MyNil
43 | case Cons(_, x) => drop(x, i - 1)
44 | }
45 | }
46 |
47 | def dropWhile[A](list: MyList[A], f: A => Boolean): MyList[A] = {
48 | list match {
49 | case Cons(x, y) =>
50 | if (f(x)) dropWhile(y, f)
51 | else list
52 | case MyNil => MyNil
53 | }
54 | }
55 |
56 | def append[A](list1: MyList[A], list2: MyList[A]): MyList[A] = {
57 | // 実行時間とメモリ使用量を決めるのはlist1の長さだけ。
58 | list1 match {
59 | case MyNil => list2
60 | case Cons(x, y) => Cons(x, append(y, list2))
61 | }
62 | }
63 |
64 | def init[A](list: MyList[A]): MyList[A] = {
65 | list match {
66 | case MyNil => MyNil
67 | case Cons(_, MyNil) => MyNil
68 | case Cons(x, y) => Cons(x, init(y))
69 | }
70 | }
71 |
72 | def dropWhile2[A](list: MyList[A])(f: A => Boolean): MyList[A] = {
73 | list match {
74 | case Cons(x, y) if f(x) => dropWhile2(y)(f)
75 | case _ => list
76 | }
77 | }
78 |
79 | def foldRight[A, B](list: MyList[A], z: B)(f: (A, B) => B): B = {
80 | list match {
81 | case MyNil => z
82 | case Cons(x, y) => f(x, foldRight(y, z)(f))
83 | }
84 | }
85 |
86 | def length[A](list: MyList[A]): Int = {
87 | foldRight(list, 0)((_, n) => n + 1)
88 | }
89 |
90 | def foldLeft[A, B](list: MyList[A], z: B)(f: (B, A) => B): B = {
91 | list match {
92 | case MyNil => z
93 | case Cons(x, y) => foldLeft(y, f(z, x))(f)
94 | }
95 | }
96 |
97 | def sum2(list: MyList[Int]) = foldLeft(list, 0)(_ + _)
98 | def product2(list: MyList[Double]) = foldLeft(list, 1.0)(_ * _)
99 | def length2[A](list: MyList[A]) = foldLeft(list, 0)((n, _) => n + 1)
100 |
101 | def reverse[A](list: MyList[A]): MyList[A] = {
102 | foldLeft(list, MyList[A]())((l, x) => Cons(x, l))
103 | }
104 |
105 | // https://github.com/fpinscala/fpinscala/blob/master/answerkey/datastructures/13.answer.scala#L9
106 | def foldRight2[A, B](list: MyList[A], z: B)(f: (A, B) => B): B = {
107 | foldLeft(list, (b: B) => b)((g, a) => b => g(f(a, b)))(z)
108 | }
109 |
110 | // https://github.com/fpinscala/fpinscala/blob/master/answerkey/datastructures/13.answer.scala#L12
111 | def foldLeft2[A, B](list: MyList[A], z: B)(f: (B, A) => B): B = {
112 | foldRight(list, (b: B) => b)((a, g) => b => g(f(b, a)))(z)
113 | }
114 |
115 | def appendViaFoldRight[A](list: MyList[A], list2: MyList[A]): MyList[A] = {
116 | foldRight(list, list2)((x, acc) => Cons(x, acc))
117 | }
118 |
119 | def flatten[A](listInList: MyList[MyList[A]]): MyList[A] = {
120 | foldRight(listInList, MyList[A]())((acc, l) => foldRight(acc, l)(Cons(_, _)))
121 | }
122 |
123 | // https://github.com/fpinscala/fpinscala/blob/master/answerkey/datastructures/15.answer.scala
124 | def flatten2[A](listInList: MyList[MyList[A]]): MyList[A] = {
125 | foldRight(listInList, MyNil: MyList[A])(appendViaFoldRight)
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter06/RNG.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter06
2 |
3 | trait RNG {
4 | def nextInt: (Int, RNG)
5 | }
6 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter06/SimpleRNG.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter06
2 |
3 | case class SimpleRNG(seed: Long) extends RNG {
4 | override def nextInt: (Int, RNG) = {
5 | val newSeed = (seed * 0x5deece66dL + 0xbL) & 0xffffffffffffL
6 | val nextRNG = SimpleRNG(newSeed)
7 | val n = (newSeed >>> 16).toInt
8 | (n, nextRNG)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter06/State.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter06
2 |
3 | /** クラスとして独立させ、関数を追加する。 この型を使い、ステートフルなプログラムの共通パターンを表現する関数を記述すればよい。
4 | *
5 | * [EXERCISE 6.10]
6 | */
7 | case class State[S, +A](run: S => (A, S)) {
8 | def map[B](f: A => B): State[S, B] =
9 | flatMap(a => State.unit(f(a)))
10 |
11 | def map2[B, C](sb: State[S, B])(f: (A, B) => C): State[S, C] =
12 | flatMap(a => sb.map(b => f(a, b)))
13 |
14 | def flatMap[B](f: A => State[S, B]): State[S, B] =
15 | State { (s: S) =>
16 | val (a, s2) = run(s)
17 | f(a).run(s2)
18 | }
19 | }
20 |
21 | /** [EXERCISE 6.10]
22 | */
23 | object State {
24 | def unit[S, A](a: A): State[S, A] =
25 | State(s => (a, s))
26 |
27 | def sequence[S, A](sas: List[State[S, A]]): State[S, List[A]] =
28 | sas.foldRight(unit[S, List[A]](List.empty))((st, acc) => st.map2(acc)(_ :: _))
29 | }
30 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter10/Chapter10Spec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter10
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** Created by blacky on 17/03/08.
6 | *
7 | * Scala関数型デザイン&プログラミング―Scalazコントリビューターによる関数型徹底ガイド
8 | * https://www.amazon.co.jp/dp/B00WM54V5Q/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
9 | *
10 | * Github https://github.com/fpinscala/fpinscala/tree/master/answerkey/monoids
11 | */
12 | class Chapter10Spec extends FunSpec {
13 |
14 | override def suiteName: String = "[FP in Scala] 第10章 モノイド"
15 |
16 | /** モノイドは以下の要素で構成される ・何らかの型A ・A型の2つの値を受け取り、それらをひとつにまとめる2項連想演算opがあり、 任意の x:A y:A z:A に対し、 op(op(x,y),z) == op(x, op(y,
17 | * z)) が成り立つ。 ・この演算の単位元である zero:A の値。 任意の x:A に対し、 op(x, zero) == x と、 op(zero, x) == x が成り立つ。
18 | */
19 | val stringMonoid = new Monoid[String] {
20 | override def op(a1: String, a2: String): String = a1 + a2
21 | override def zero: String = ""
22 | }
23 |
24 | def listMonoid[A] = new Monoid[List[A]] {
25 | override def op(a1: List[A], a2: List[A]): List[A] = a1 ++ a2
26 | override def zero: List[A] = Nil
27 | }
28 |
29 | it("[EXERCISE 10.1] 整数の加算、乗算、論理演算子に対するMonoidインスタンス") {
30 | val add: Monoid[Int] = new Monoid[Int] {
31 | override def op(a1: Int, a2: Int): Int = a1 + a2
32 | override def zero: Int = 0
33 | }
34 | val multi: Monoid[Int] = new Monoid[Int] {
35 | override def op(a1: Int, a2: Int): Int = a1 * a2
36 | override def zero: Int = 1
37 | }
38 | val or: Monoid[Boolean] = new Monoid[Boolean] {
39 | override def op(a1: Boolean, a2: Boolean): Boolean = a1 || a2
40 | override def zero: Boolean = false
41 | }
42 | val and: Monoid[Boolean] = new Monoid[Boolean] {
43 | override def op(a1: Boolean, a2: Boolean): Boolean = a1 && a2
44 | override def zero: Boolean = true
45 | }
46 |
47 | for {
48 | x <- 1 to 5
49 | y <- 1 to 5
50 | z <- 1 to 5
51 | } {
52 | assert(add.op(add.op(x, y), z) == add.op(x, add.op(y, z)))
53 | assert(add.op(add.zero, x) == add.op(x, add.zero))
54 | }
55 | for {
56 | x <- 1 to 5
57 | y <- 1 to 5
58 | z <- 1 to 5
59 | } {
60 | assert(multi.op(multi.op(x, y), z) == multi.op(x, multi.op(y, z)))
61 | assert(multi.op(multi.zero, x) == multi.op(x, multi.zero))
62 | }
63 | for {
64 | x <- List(false, true)
65 | y <- List(false, true)
66 | z <- List(false, true)
67 | } {
68 | assert(or.op(or.op(x, y), z) == or.op(x, or.op(y, z)))
69 | assert(or.op(or.zero, x) == or.op(x, or.zero))
70 | }
71 | for {
72 | x <- List(false, true)
73 | y <- List(false, true)
74 | z <- List(false, true)
75 | } {
76 | assert(and.op(and.op(x, y), z) == and.op(x, and.op(y, z)))
77 | assert(and.op(and.zero, x) == and.op(x, and.zero))
78 | }
79 | }
80 |
81 | it("[EXERCISE 10.2] Option型の値を結合するMonoidインスタンス") {
82 | def optionMonoid[A]: Monoid[Option[A]] = {
83 | new Monoid[Option[A]] {
84 | override def op(a1: Option[A], a2: Option[A]) = a1 orElse a2
85 | override def zero = None
86 | }
87 | }
88 | val options: List[Option[Int]] = List(None, Some(1), Some(2))
89 | for {
90 | x <- options
91 | y <- options
92 | z <- options
93 | } {
94 | assert(optionMonoid.op(optionMonoid.op(x, y), z) == optionMonoid.op(x, optionMonoid.op(y, z)))
95 | assert(optionMonoid.op(optionMonoid.zero, x) == optionMonoid.op(x, optionMonoid.zero))
96 | }
97 |
98 | // https://github.com/fpinscala/fpinscala/blob/master/answerkey/monoids/02.answer.scala
99 | // 操作の反転
100 | def dual[A](m: Monoid[A]): Monoid[A] = new Monoid[A] {
101 | override def op(a1: A, a2: A) = m.op(a2, a1)
102 | override def zero = m.zero
103 | }
104 | def firstOptionMonoid[A]: Monoid[Option[A]] = optionMonoid[A]
105 | def lastOptionMonoid[A]: Monoid[Option[A]] = dual(firstOptionMonoid)
106 | for {
107 | x <- options
108 | y <- options
109 | z <- options
110 | } {
111 | assert(firstOptionMonoid.op(firstOptionMonoid.op(x, y), z) == firstOptionMonoid.op(x, firstOptionMonoid.op(y, z)))
112 | assert(firstOptionMonoid.op(firstOptionMonoid.zero, x) == firstOptionMonoid.op(x, firstOptionMonoid.zero))
113 | assert(lastOptionMonoid.op(lastOptionMonoid.op(x, y), z) == lastOptionMonoid.op(x, lastOptionMonoid.op(y, z)))
114 | assert(lastOptionMonoid.op(lastOptionMonoid.zero, x) == lastOptionMonoid.op(x, lastOptionMonoid.zero))
115 | }
116 | assert(firstOptionMonoid.op(firstOptionMonoid.op(Some(1), None), Some(2)) == Some(1))
117 | assert(lastOptionMonoid.op(lastOptionMonoid.op(Some(1), None), Some(2)) == Some(2))
118 | }
119 |
120 | it("[EXERCISE 10.3] endo関数のモノイド") {
121 | def endoMonoid[A]: Monoid[A => A] = new Monoid[(A) => A] {
122 | override def op(a1: (A) => A, a2: (A) => A) = a1 andThen a2
123 | override def zero = (a: A) => a
124 | }
125 | val f1 = (s: String) => s.trim
126 | val f2 = (s: String) => s * 2
127 | val f3 = (s: String) => s.toUpperCase
128 | assert(endoMonoid.op(endoMonoid.op(f1, f2), f3)(" hoge ") == "HOGEHOGE")
129 | assert(endoMonoid.op(f1, endoMonoid.op(f2, f3))(" hoge ") == "HOGEHOGE")
130 | assert(endoMonoid.op(endoMonoid.zero, f1)(" foo ") == "foo")
131 | assert(endoMonoid.op(f1, endoMonoid.zero)(" foo ") == "foo")
132 | }
133 |
134 | it("[EXERCISE 10.4] foldMapの実装") {
135 | val list = List("1", "2", "3")
136 | val monoid = new Monoid[Int] {
137 | override def op(a1: Int, a2: Int) = a1 + a2
138 | override def zero = 0
139 | }
140 | assert(Monoid.foldMap(list, monoid)(_.toInt) == 6)
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter10/Monoid.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter10
2 |
3 | /** Created by blacky on 17/03/08.
4 | */
5 | trait Monoid[A] {
6 | // op(op(x,y),z) == op(x, op(y,z)) を満たす
7 | def op(a1: A, a2: A): A
8 |
9 | // op(zero, x) == x と op(x, zero) == x を満たす
10 | def zero: A
11 | }
12 |
13 | object Monoid {
14 | def concatenate[A](as: List[A], m: Monoid[A]): A = as.foldLeft(m.zero)(m.op)
15 |
16 | def foldMap[A, B](as: List[A], m: Monoid[B])(f: A => B): B = as.foldLeft(m.zero)((b, a) => m.op(b, f(a)))
17 | // def foldMap[A, B](as: List[A], m: Monoid[B])(f: A => B): B = as.foldLeft(m.zero)(m.op(_, f(_)))
18 | // [error] type mismatch;
19 | // [error] found : A => B
20 | // [error] required: B
21 | // [error] def foldMap[A, B](as: List[A], m: Monoid[B])(f: A => B): B = as.foldLeft(m.zero)(m.op(_, f(_)))
22 | // [error] ^
23 | }
24 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter11/Chapter11Spec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter11
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** Created by blacky on 17/04/25.
6 | *
7 | * Scala関数型デザイン&プログラミング―Scalazコントリビューターによる関数型徹底ガイド 第11章 モナド
8 | * https://www.amazon.co.jp/dp/B00WM54V5Q/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
9 | *
10 | * Github https://github.com/fpinscala/fpinscala/tree/master/answerkey/monads
11 | */
12 | class Chapter11Spec extends FunSpec {
13 |
14 | override def suiteName: String = "[FP in Scala] 第11章 モナド"
15 |
16 | it("11.1 ファンクタ : map関数の一般化") {
17 | val listFunctor = new Functor[List] {
18 | override def map[A, B](list: List[A])(f: (A) => B): List[B] = list map f
19 | }
20 | val optionFunctor = new Functor[Option] {
21 | override def map[A, B](op: Option[A])(f: (A) => B): Option[B] = op map f
22 | }
23 |
24 | assert(listFunctor.distribute(List((1, "a"), (2, "b"))) == (List(1, 2), List("a", "b")))
25 | assert(optionFunctor.distribute(Some((1, "a"))) == (Some(1), Some("a")))
26 | assert(optionFunctor.distribute(None) == (None, None))
27 |
28 | assert(listFunctor.codistribute(Left(List(1, 2))) == List(Left(1), Left(2)))
29 | assert(optionFunctor.codistribute(Right(Some("a"))) == Some(Right("a")))
30 | assert(optionFunctor.codistribute(Right(None)) == None)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/functional/programming/in/scala/chapter11/Functor.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.functional.programming.in.scala.chapter11
2 |
3 | /** Created by blacky on 17/04/25.
4 | *
5 | * Functorは全体を写せる(map over)ものの型クラス。 List, Optionなどの型コンストラクタなど。 Functor[F]インスタンスは、Fが実際にファンクタであることの裏付けとなる。
6 | */
7 | trait Functor[F[_]] {
8 | // map を実装する
9 | def map[A, B](fa: F[A])(f: A => B): F[B]
10 |
11 | // mapのみで定義できる演算の一例
12 | def distribute[A, B](fab: F[(A, B)]): (F[A], F[B]) = {
13 | (map(fab)(_._1), map(fab)(_._2))
14 | }
15 |
16 | def codistribute[A, B](e: Either[F[A], F[B]]): F[Either[A, B]] = {
17 | e match {
18 | case Left(fa) => map(fa)(Left(_))
19 | case Right(fb) => map(fb)(Right(_))
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/libraries/cats/CatsSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.libraries.cats
2 |
3 | import cats.data.NonEmptyList
4 | import org.scalatest.{FunSpec, Matchers}
5 |
6 | class CatsSpec extends FunSpec with Matchers {
7 |
8 | override def suiteName: String = "[Cats] Catsの基礎"
9 |
10 | it("cats.syntax で型クラス・既存型に対する拡張などが提供される") {
11 | // cats.syntax 以下のパッケージで、
12 | // 型クラスや、既存クラスに対する拡張が提供されている。
13 |
14 | // 例えば、ListSyntaxでは、Listに対する拡張が提供されている。
15 | import cats.syntax.list._
16 |
17 | // NonEmptyList を返す #toNel
18 | // NonEmptyList はひとつ以上の要素が含まれるリスト
19 | List().toNel shouldBe None
20 | List(1, 2, 3).toNel shouldBe Some(NonEmptyList.of(1, 2, 3))
21 |
22 | // #groupByNel は要素を Map[Key, NonEmptyList[Value]] に変換する
23 | List("hoge", "foo", "bar").groupByNel(_.length) shouldBe Map(
24 | 3 -> NonEmptyList.of("foo", "bar"),
25 | 4 -> NonEmptyList.of("hoge")
26 | )
27 | }
28 |
29 | it("cats.instances で型クラスの実装が提供される") {
30 | // 型クラスShowは、toStringのような、人が読むための文字列を提供する型クラス
31 | // ShowSyntaxで AnyRef => Show の暗黙的変換が定義される
32 | import cats.syntax.show._
33 |
34 | // ただし、この時点では、Intに対するShowのインスタンスが定義されていない
35 | // ↓コンパイルエラー
36 | // 10.show
37 |
38 | // cats.instnces 以下に型クラスのインスタンスが定義されている
39 |
40 | // 利用できた
41 | 10.show shouldBe "10"
42 | }
43 |
44 | it("Eq ... 型安全な等価比較を提供する") {
45 | // EqSyntax により、型安全な比較をするメソッドが提供される
46 | import cats.syntax.eq._
47 |
48 | // FIXME: Conflict to org.scalastics.TripleEqualsSupport
49 | // "hoge" === "hoge" shouldBe true
50 | // 型安全なので、これはコンパイルエラー
51 | // "hoge" === 10 shouldBe false
52 |
53 | "hoge" =!= "hoge" shouldBe false
54 | // 同じくコンパイルエラー
55 | // "hoge" =!= 10 shouldBe true
56 | }
57 |
58 | it("Monoid ... 二項演算と単位元を持つ代数的構造") {
59 | import cats.Monoid
60 | import cats.syntax.monoid._
61 |
62 | implicit val joinMonoid: Monoid[String] = new Monoid[String] {
63 | override def empty = ""
64 | override def combine(x: String, y: String): String = x + y
65 | }
66 |
67 | "a" |+| "b" shouldBe "ab"
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/libraries/jfreechart/JFreeChartSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.libraries.jfreechart
2 |
3 | import better.files.File
4 | import org.jfree.chart.plot.{PiePlot, PlotOrientation}
5 | import org.jfree.chart.{ChartFactory, ChartUtils, JFreeChart}
6 | import org.jfree.data.general.DefaultPieDataset
7 | import org.jfree.data.statistics.HistogramDataset
8 | import org.scalatest.{BeforeAndAfter, FunSpec}
9 |
10 | import scala.util.Random
11 |
12 | /** Created by blacky on 17/02/26.
13 | */
14 | class JFreeChartSpec extends FunSpec with BeforeAndAfter {
15 |
16 | override def suiteName: String = "JFreeChart - グラフを描画するJavaライブラリ"
17 |
18 | val ResultDir: String = "target/jfreechart/"
19 |
20 | before {
21 | File(ResultDir).createDirectories()
22 | }
23 |
24 | it("Part1 ... 円グラフを作成する") {
25 | val dataSet = new DefaultPieDataset()
26 | Map("hoge" -> 10.0, "foo" -> 30.0, "bar" -> 60.0).foreach { case (s, d) => dataSet.setValue(s, d) }
27 |
28 | val chart: JFreeChart = new JFreeChart("sample", new PiePlot(dataSet))
29 | val file = File(ResultDir + "part1.png")
30 |
31 | ChartUtils.saveChartAsJPEG(file.toJava, chart, 300, 300)
32 | }
33 |
34 | it("Part2 ... ヒストグラム") {
35 | val dataSet = new HistogramDataset
36 |
37 | dataSet.addSeries(
38 | "data",
39 | (1 to 1000)
40 | .map(_ => {
41 | val i = 50 - (if (Random.nextBoolean()) -1 else 1) * Random.nextInt(30)
42 | i.toDouble
43 | })
44 | .toArray,
45 | 10
46 | )
47 | val chart = ChartFactory.createHistogram(
48 | "sample",
49 | "x",
50 | "y",
51 | dataSet,
52 | PlotOrientation.VERTICAL,
53 | false,
54 | false,
55 | false
56 | )
57 | val file = File(ResultDir + "part2.png")
58 |
59 | ChartUtils.saveChartAsJPEG(file.toJava, chart, 300, 300)
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/libraries/scalaz/DisjunctionSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.libraries.scalaz
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scalaz._
6 | import Scalaz._
7 |
8 | /** Created by blacky on 17/04/14.
9 | *
10 | * \/ Disjunction, Either 数学の論理和の記号が由来 ∨
11 | *
12 | * -\/ ... Left \/- ... Right
13 | */
14 | class DisjunctionSpec extends FunSpec {
15 |
16 | override def suiteName: String = "[Scalaz] Disjunction - 強化版Either"
17 |
18 | it("Left,Rightの生成") {
19 | // Leftの生成
20 | val a: \/[Int, String] = -\/(1)
21 | val b: \/[Int, String] = \/.left(1) // leftメソッドで
22 | val c: Int \/ String = -\/(1) // 中置記法で
23 | a assert_=== b
24 | b assert_=== c
25 |
26 | // Rightの生成
27 | val d: \/[Int, String] = \/-("a")
28 | val e: \/[Int, String] = \/.right("a") // rightメソッドで
29 | val f: Int \/ String = \/-("a") // 中置記法で
30 | d assert_=== e
31 | e assert_=== f
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/libraries/scalaz/NonEmptyListSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.libraries.scalaz
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scalaz._
6 | import Scalaz._
7 |
8 | /** Created by blacky on 17/04/13.
9 | *
10 | * NonEmptyList ... 要素がひとつ以上含まれることが保証されているList
11 | */
12 | class NonEmptyListSpec extends FunSpec {
13 |
14 | override def suiteName: String = "[Scalaz] NonEmptyList - 空でないことが保証されるリスト"
15 |
16 | val list = NonEmptyList(1, 2, 3)
17 |
18 | it("<:: ... 先頭に要素を追加する") {
19 | 9 <:: list assert_=== NonEmptyList(9, 1, 2, 3)
20 | }
21 |
22 | it("head ... 先頭の要素を取り出す") {
23 | list.head assert_=== 1
24 | }
25 |
26 | it("size ... 要素の数を取得する") {
27 | list.size assert_=== 3
28 | }
29 |
30 | it("reverse ... リストを反転する") {
31 | list.reverse assert_=== NonEmptyList(3, 2, 1)
32 | }
33 |
34 | it("map ... 要素に関数を適用する") {
35 | list.map(_ * 2) assert_=== NonEmptyList(2, 4, 6)
36 | }
37 |
38 | it("flatmap ... 要素に関数を適用し、flattenする") {
39 | list.flatMap(x => NonEmptyList(x, x * 10)) assert_=== NonEmptyList(1, 10, 2, 20, 3, 30)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/libraries/scalikejdbc/ScalikeJDBCUnitTestSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.libraries.scalikejdbc
2 |
3 | import org.scalatest.{fixture, BeforeAndAfterAll, Matchers}
4 | import scalikejdbc._
5 | import scalikejdbc.config.DBs
6 | import scalikejdbc.scalatest.AutoRollback
7 |
8 | class ScalikeJDBCUnitTestSpec extends fixture.FunSpec with Matchers with BeforeAndAfterAll with AutoRollback {
9 |
10 | override def suiteName: String = "ScalikeJDBCでのユニットテスト"
11 |
12 | override def db(): DB = NamedDB('sample1).toDB
13 |
14 | object User {
15 | def create(name: String, organization: Option[String])(implicit session: DBSession): Unit =
16 | sql"insert into users2(name, organization) values($name, $organization)".update().apply()
17 |
18 | def count()(implicit session: DBSession): Long =
19 | sql"select count(1) from users2".map(_.long(1)).single().apply().get
20 | }
21 |
22 | override protected def beforeAll(): Unit = {
23 | DBs.setupAll()
24 |
25 | NamedDB('sample1).localTx { implicit s =>
26 | sql"create table if not exists users2(id bigint primary key auto_increment, name varchar(50) not null, organization varchar(50))"
27 | .update()
28 | .apply()
29 | }
30 | }
31 |
32 | override def fixture(implicit session: DBSession): Unit = {
33 | (1 to 3).foreach { i =>
34 | User.create(s"User$i", Some(s"Org$i"))
35 | }
36 | }
37 |
38 | describe("ユニットテスト") {
39 |
40 | ignore("接続情報の設定") { implicit s =>
41 | // scalikejdbc-config を依存関係に追加する。
42 | // DBs.setupAll() を呼ぶと、 application.conf を読み込んで接続情報を初期化する
43 | import scalikejdbc.config._
44 | DBs.setupAll()
45 | DB.getAllTableNames() contains "users2"
46 | }
47 |
48 | ignore("自動ロールバック") { implicit session =>
49 | // AutoRollback トレイトをmixinして、テストのパラメタにimplicitでDBSessionを受け取る
50 | // テストごとにひとつのトランザクションが生成され、テスト終了後にロールバックされる。
51 | User.create("User4", None)
52 | User.count() shouldBe 4
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/libraries/scopt/ScoptSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.libraries.scopt
2 |
3 | import java.io.File
4 | import java.util.Calendar
5 |
6 | import org.scalatest.FunSpec
7 |
8 | /** Created by blacky on 16/10/14.
9 | */
10 | class ScoptSpec extends FunSpec {
11 |
12 | override def suiteName: String = "Scopt - コマンドライン引数を解析するライブラリ"
13 |
14 | val parser = new scopt.OptionParser[Config]("ScoptSpec") {
15 | head("ScoptSpec")
16 |
17 | opt[String]('a', "aaa")
18 | .action((s, c) => {
19 | c.copy(aaa = s)
20 | })
21 | .text("set string to aaa")
22 |
23 | opt[Int]('b', "bbb")
24 | .action((x, c) => {
25 | c.copy(bbb = Some(x))
26 | })
27 | .text("set int to bbb")
28 |
29 | opt[Unit]('c', "ccc")
30 | .action((b, c) => {
31 | c.copy(ccc = true)
32 | })
33 | .text("set true to ccc")
34 |
35 | opt[Calendar]("calendar").action((x, c) => {
36 | c.copy(calendar = Some(x))
37 | })
38 |
39 | opt[File]("file").action((x, c) => {
40 | c.copy(file = Some(x))
41 | })
42 | }
43 |
44 | it("コマンドライン引数を解析する") {
45 | parser.parse(List("-a", "hoge", "--bbb", "10", "-c"), Config()) match {
46 | case Some(config: Config) =>
47 | println(config)
48 | case None =>
49 | throw new IllegalArgumentException("arguments are bad.")
50 | }
51 | }
52 |
53 | it("引数から日付を取得") {
54 | parser.parse(List("--calendar", "2016-1-1"), Config()) match {
55 | case Some(config: Config) =>
56 | // FIXME: Calendarの比較
57 | println(config.calendar)
58 | case None =>
59 | throw new IllegalArgumentException("arguments are bad.")
60 | }
61 | }
62 |
63 | it("引数からファイルを取得") {
64 | parser.parse(List("--file", "build.sbt"), Config()) match {
65 | case Some(config: Config) =>
66 | assert(
67 | config.file
68 | .getOrElse(throw new IllegalArgumentException())
69 | .getAbsolutePath == new File("build.sbt").getAbsolutePath
70 | )
71 | case None =>
72 | throw new IllegalArgumentException("arguments are bad.")
73 | }
74 | }
75 | }
76 |
77 | case class Config(
78 | aaa: String = "",
79 | bbb: Option[Int] = None,
80 | ccc: Boolean = false,
81 | calendar: Option[Calendar] = None,
82 | file: Option[File] = None
83 | )
84 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/nlp100/Chapter02Spec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.nlp100
2 |
3 | import org.scalatest.{FunSpec, Matchers}
4 |
5 | import scala.io.Source
6 |
7 | /** 言語処理100本ノック 第2章: UNIXコマンド
8 | *
9 | * Weeble Scalaもくもく勉強会にて回答されたコードです。 https://weeyble-scala.connpass.com/
10 | */
11 | class Chapter02Spec extends FunSpec with Matchers {
12 |
13 | it("テキストファイルの読み込み") {
14 | val strings: Iterator[String] = Source.fromFile("data/hightemp.txt").getLines()
15 | println(strings.mkString("\n"))
16 | }
17 |
18 | it("10. 行数のカウント") {
19 |
20 | /** 行数をカウントせよ.確認にはwcコマンドを用いよ.
21 | */
22 | // A01
23 | val lines01 = Source.fromFile("data/hightemp.txt").getLines()
24 | val count01 = lines01.size
25 | count01 shouldBe 24
26 |
27 | // A02
28 | val lines02 = scala.io.Source.fromFile("data/hightemp.txt").getLines()
29 | val count02 = lines02.length
30 | count02 shouldBe 24
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/parsercombinator/ParserCombinatorSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.parsercombinator
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scala.util.parsing.combinator.{JavaTokenParsers, RegexParsers}
6 |
7 | /** 参考資料 * Scalaスケーラブルプログラミング 第33章 パーサー・コンビネーター * 面倒くさいパーサの実装もDSLで書くだけ!そう、Scalaならね - Qiita
8 | * http://qiita.com/suin/items/35bc4afe618cb77f80f6
9 | *
10 | * Created by blacky on 17/04/20.
11 | */
12 | class ParserCombinatorSpec extends FunSpec {
13 |
14 | override def suiteName: String = "パーザコンビネータ"
15 |
16 | it("電話番号のパース") {
17 | // 正規表現パーサを継承する
18 | object PhoneNumberParser extends RegexParsers {
19 | // 正規表現(~ 逐次合成)
20 | def code = """\d{3}""".r ~ "-" ~ """\d{4}""".r ~ "-" ~ """\d{4}""".r
21 | // applyを定義しておくとクライアントコードが軽くなる
22 | def apply(source: String) = parseAll(code, source)
23 | }
24 | assert(PhoneNumberParser("012-7890-3456").get.toString() == "((((012~-)~7890)~-)~3456)")
25 | }
26 |
27 | it("パースのエラーハンドリング") {
28 | object PhoneNumberParser extends RegexParsers {
29 | def code = """\d{3}""".r ~ "-" ~ """\d{4}""".r ~ "-" ~ """\d{4}""".r
30 | // パース結果はParseResultで返る
31 | def apply(source: String) = parseAll(code, source) match {
32 | case Success(parsed, _) => Right(parsed)
33 | case NoSuccess(errorMessage, _) => Left(errorMessage)
34 | }
35 | }
36 | PhoneNumberParser("012-7890-3456") match {
37 | case Right(parsed) =>
38 | assert(parsed.toString() == "((((012~-)~7890)~-)~3456)")
39 | case _ => fail()
40 | }
41 | PhoneNumberParser("01278903456") match {
42 | case Left(errorMsg) =>
43 | assert(errorMsg == "'-' expected but '7' found")
44 | case _ => fail()
45 | }
46 | }
47 |
48 | it("詳細なエラー内容を取得する") {
49 | object PhoneNumberParser extends RegexParsers {
50 | def code = """\d{3}""".r ~ "-" ~ """\d{4}""".r ~ "-" ~ """\d{4}""".r
51 | def apply(source: String) = parseAll(code, source) match {
52 | case Success(parsed, _) => Right(parsed)
53 | // next パースされていない地点を表す
54 | case NoSuccess(errorMessage, next) =>
55 | Left(s"$errorMessage on line ${next.pos.line} on column ${next.pos.column}")
56 | }
57 | }
58 | PhoneNumberParser("01278903456") match {
59 | case Left(errorMsg) =>
60 | assert(errorMsg == "'-' expected but '7' found on line 1 on column 4")
61 | case _ => fail()
62 | }
63 | }
64 |
65 | it("パース内容を変換する") {
66 | // 電話番号を表す case class
67 | case class PhoneNumber(area: String, city: String, subscriber: String)
68 |
69 | object PhoneNumberParser extends RegexParsers {
70 | // P ^^ f はパース結果Pを関数fで変換する
71 | def code = """\d{3}""".r ~ "-" ~ """\d{4}""".r ~ "-" ~ """\d{4}""".r ^^ {
72 | case (area ~ "-" ~ city ~ "-" ~ subscriber) => PhoneNumber(area, city, subscriber)
73 | }
74 | def apply(source: String) = parseAll(code, source) match {
75 | case Success(parsed, _) => Right(parsed)
76 | case NoSuccess(errorMessage, next) =>
77 | Left(s"$errorMessage on line ${next.pos.line} on column ${next.pos.column}")
78 | }
79 | }
80 | PhoneNumberParser("012-7890-3456") match {
81 | case Right(pn) =>
82 | assert(pn == PhoneNumber("012", "7890", "3456"))
83 | case _ => fail()
84 | }
85 | }
86 |
87 | it("四則演算のパース") {
88 | // https://gist.github.com/sschaef/5529436
89 | object ExprParser extends JavaTokenParsers {
90 | sealed trait Tree
91 | case class Add(a: Tree, b: Tree) extends Tree
92 | case class Sub(a: Tree, b: Tree) extends Tree
93 | case class Mul(a: Tree, b: Tree) extends Tree
94 | case class Div(a: Tree, b: Tree) extends Tree
95 | case class Num(x: Double) extends Tree
96 |
97 | def eval(t: Tree): Double = t match {
98 | case Add(a, b) => eval(a) + eval(b)
99 | case Sub(a, b) => eval(a) - eval(b)
100 | case Mul(a, b) => eval(a) * eval(b)
101 | case Div(a, b) => eval(a) / eval(b)
102 | case Num(x) => x
103 | }
104 |
105 | lazy val expr: Parser[Tree] = term ~ rep("[+-]".r ~ term) ^^ { case t ~ ts =>
106 | ts.foldLeft(t) {
107 | case (t1, "+" ~ t2) => Add(t1, t2)
108 | case (t1, "-" ~ t2) => Sub(t1, t2)
109 | }
110 | }
111 |
112 | lazy val term = factor ~ rep("[*/]".r ~ factor) ^^ { case t ~ ts =>
113 | ts.foldLeft(t) {
114 | case (t1, "*" ~ t2) => Mul(t1, t2)
115 | case (t1, "/" ~ t2) => Div(t1, t2)
116 | }
117 | }
118 |
119 | lazy val factor = "(" ~> expr <~ ")" | num
120 |
121 | lazy val num = floatingPointNumber ^^ { t =>
122 | Num(t.toDouble)
123 | }
124 |
125 | def apply(source: String) = eval(parseAll(expr, source).get)
126 | }
127 |
128 | assert(ExprParser("1+2+3") == 6.0)
129 | assert(ExprParser("(1+2)*3") == 9.0)
130 | assert(ExprParser("1/2+3") == 3.5)
131 | assert(ExprParser("1*2-3") == -1.0)
132 | assert(ExprParser("5*(6-3)") == 15.0)
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/play/ws/PlayWSSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.play.ws
2 |
3 | import akka.actor.ActorSystem
4 | import akka.stream.ActorMaterializer
5 | import akka.stream.scaladsl.{Flow, Keep, Sink}
6 | import akka.util.ByteString
7 | import org.scalatest.{FunSpec, Matchers}
8 | import play.api.libs.ws.StandaloneWSRequest
9 | import play.api.libs.ws.ahc.StandaloneAhcWSClient
10 |
11 | import scala.concurrent.duration.Duration
12 | import scala.concurrent.{Await, Future}
13 |
14 | class PlayWSSpec extends FunSpec with Matchers {
15 | import scala.concurrent.ExecutionContext.Implicits.global
16 |
17 | override def suiteName: String = "Play WS ... Play製のHTTPクライアント"
18 |
19 | // Akkaを使っているので必要
20 | implicit val actorSystem: ActorSystem = ActorSystem()
21 | implicit val actorMaterializer: ActorMaterializer = ActorMaterializer()
22 |
23 | it("基本的なHTTPアクセス") {
24 | val client = StandaloneAhcWSClient()
25 | val future: Future[StandaloneWSRequest#Response] = client.url("https://google.co.jp").get()
26 |
27 | for (response <- future) {
28 | response.body should contain("Google")
29 | }
30 | }
31 |
32 | it("akka-streamsのSourceとして受け取る") {
33 | val client = StandaloneAhcWSClient()
34 | val flow = Flow[ByteString].filter(bs => bs.utf8String contains "G")
35 | val sink = Sink.fold[Int, ByteString](0)((acc, bs) => acc + bs.length)
36 | val future = client.url("https://google.co.jp").stream().flatMap { res =>
37 | res.bodyAsSource.via(flow).toMat(sink)(Keep.right).run()
38 | }
39 | val result = Await.result(future, Duration.Inf)
40 | println(result)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/s99/S99Spec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.s99
2 |
3 | import org.scalatest.{FunSpec, Matchers}
4 |
5 | import scala.annotation.tailrec
6 |
7 | /** Weeble ゆるふわScala勉強会にて回答されたコードです。 https://weeyble-scala.connpass.com/
8 | */
9 | class S99Spec extends FunSpec with Matchers {
10 |
11 | override def suiteName: String = "Ninety-Nine Scala Problems"
12 |
13 | it("P01 (*) Find the last element of a list.") {
14 | // List#last は最後の要素を返す
15 | def last[T](list: List[T]): T = list.last
16 | last(List(1, 1, 2, 3, 5, 8)) shouldBe 8
17 | }
18 |
19 | it("P02 (*) Find the last but one element of a list.") {
20 | val list = List(1, 1, 2, 3, 5, 8)
21 | val expect = 5
22 |
23 | // A01
24 | def penultimate[T](list: List[T]): T = list(list.size - 2)
25 | penultimate(list) shouldBe expect
26 |
27 | // A02
28 | // List#init は最後の要素を除いたListを返す
29 | def penultimate2[T](list: List[T]): T = list.init.last
30 | penultimate2(list) shouldBe expect
31 |
32 | // A03 - 例外安全なパターン
33 | // List#lastOption は要素が含まれればSome(最後の値)を、
34 | // 空リストならNoneを返す
35 | def penultimate3[T](list: List[T]): Option[T] = list.init.lastOption
36 | penultimate3(list) shouldBe Some(expect)
37 | penultimate3(List(1)) shouldBe None
38 | }
39 |
40 | it("P03 (*) Find the Kth element of a list.") {
41 | val list = List(1, 1, 2, 3, 5, 8)
42 | val index = 2
43 | val expect = 2
44 |
45 | // A01
46 | // インデックスに基づき要素を返す
47 | def nth[T](i: Int, list: List[T]): T = list(i)
48 | nth(2, list) shouldBe expect
49 |
50 | // A02
51 | // 上のコードと同義。applyは省略できる。
52 | def nth2[T](n: Int, list: List[T]): T = list.apply(n)
53 | nth2(2, list) shouldBe expect
54 |
55 | // A03 - 例外安全なパターン
56 | @tailrec def nth3[T](n: Int, l: List[T]): Option[T] = {
57 | if (n == 0)
58 | l.headOption
59 | else if (l.isEmpty)
60 | None
61 | else
62 | nth3(n - 1, l.tail)
63 | }
64 | nth3(2, list) shouldBe Some(expect)
65 | nth3(9, list) shouldBe None
66 |
67 | // A04 - 例外安全なパターン2
68 | // List#lift は指定されたインデックスに要素が含まれればSome(要素)を、
69 | // 含まれていなければNoneを返す
70 | def nth4[T](i: Int, list: List[T]): Option[T] = list.lift(i)
71 | nth4(2, list) shouldBe Some(expect)
72 | nth4(9, list) shouldBe None
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/scala/JVMSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.scala
2 |
3 | import org.scalatest.{FunSpec, Matchers}
4 |
5 | class JVMSpec extends FunSpec with Matchers {
6 |
7 | override def suiteName: String = "JVM関連のあれこれ"
8 |
9 | it("クラスパスの一覧を取得する") {
10 | // Thanks! => https://gist.github.com/jessitron/8376139
11 | def urlses(cl: ClassLoader): Array[java.net.URL] = cl match {
12 | case null => Array()
13 | case u: java.net.URLClassLoader => u.getURLs ++ urlses(cl.getParent)
14 | case _ => urlses(cl.getParent)
15 | }
16 |
17 | val urls = urlses(ClassLoader.getSystemClassLoader)
18 | println(urls.mkString("\n"))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/scala/TypeClassSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.scala
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scala.math.Ordering
6 |
7 | /** Created by blacky on 17/05/23.
8 | *
9 | * 参考
10 | *
11 | * + 型クラスをOrderingを用いて説明してみる + http://kmizu.hatenablog.com/entry/2017/05/22/224622 +
12 | * Scalaの型クラス(typeclass)の分かりやすい説明(Cats公式翻訳) + http://qiita.com/mizunowanko/items/7d8658c765567677b280 + Scalaで型クラス入門 +
13 | * http://chopl.in/post/2012/11/06/introduction-to-typeclass-with-scala/ + Twitter検索 +
14 | * https://twitter.com/search?l=&q=型クラス&src=typd&lang=ja
15 | */
16 | class TypeClassSpec extends FunSpec {
17 |
18 | override def suiteName: String = "型クラスの使い方"
19 |
20 | it("型クラスとは") {
21 |
22 | /** + 型クラスとは + ある型に振る舞いを提供する仕組み。 + 「アドホック多相」を実現する。 + 引数の型に応じて複数の実装を提供できる。 + 型の宣言時に振る舞いを与えず、アドホック(場当たり的に)実装を提供できる。 +
23 | * (特にScalaでは)異なるスコープにおいて、型クラスの実装を有効・無効にできる。 + 複数の型に対して共通の振る舞いをもたせる、という意味の用途はinterfaceとほぼ変わらない。 +
24 | * が、interfaceにはない利点が多い + interfaceは元のクラス定義を書き換える必要がある。 (ex. Stringに新しいinterfaceを実装することはできない。) +
25 | * ある型に対する操作を後付けで加えることができる。 + 型クラスのインスタンス(OPPのインスタンスとは異なる)を作る ≒ Strategyパターンの具体的なStrategyを定義する
26 | */
27 | }
28 |
29 | it("型クラスの例") {
30 | assert(List(1, 2, 3).max == 3)
31 | assert(List("a", "b", "c").max == "c")
32 |
33 | /** implicit parameter によって、Orderingの引数に与えられる
34 | *
35 | * def max[B >: A](implicit cmp: Ordering[B]): A
36 | *
37 | * [scala.math.Ordering.scala]
38 | *
39 | * trait IntOrdering extends Ordering[Int] { def compare(x: Int, y: Int) = java.lang.Integer.compare(x, y) }
40 | * implicit object Int extends IntOrdering
41 | *
42 | * trait StringOrdering extends Ordering[String] { def compare(x: String, y: String) = x.compareTo(y) } implicit
43 | * object String extends StringOrdering
44 | */
45 | }
46 |
47 | it("Orderedを使った実装例") {
48 | case class Person(id: Int, age: Int)
49 | val persons = List(Person(2, 20), Person(1, 30), Person(3, 10))
50 |
51 | implicit object PersonOrderingById extends Ordering[Person] {
52 | override def compare(x: Person, y: Person): Int = {
53 | if (x.id < y.id) -1 else if (x.id > y.id) 1 else 0
54 | }
55 | }
56 | assert(persons.sorted == List(Person(1, 30), Person(2, 20), Person(3, 10)))
57 |
58 | // implicit object PersonOrderingByAge extends Ordering[Person] {
59 | // override def compare(x: Person, y: Person): Int = {
60 | // if (x.age < y.age) -1 else if (x.age > y.age) 1 else 0
61 | // }
62 | // }
63 | // assert(persons.sorted == List(Person(3, 10), Person(2, 20), Person(1, 30)))
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/scala/TypeParameterSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.scala
2 |
3 | import org.scalatest.FunSpec
4 |
5 | /** Created by blacky on 16/11/19.
6 | */
7 | class TypeParameterSpec extends FunSpec {
8 |
9 | it("Scalaにおける型の検査") {
10 | val v1: Any = "hoge"
11 | val v2: Any = 123
12 |
13 | // Any#instanceOf で型の検査ができる
14 | assert(v1.isInstanceOf[String] == true)
15 | assert(v1.isInstanceOf[Int] == false)
16 | assert(v2.isInstanceOf[String] == false)
17 | assert(v2.isInstanceOf[Int] == true)
18 |
19 | // が、パターンマッチを使うほうが一般的
20 | v1 match {
21 | case v: String =>
22 | case v: Int => fail()
23 | }
24 | v2 match {
25 | case v: String => fail()
26 | case v: Int =>
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/scala_kansai/y2018/N01PatternMatching.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.scala_kansai.y2018
2 |
3 | import java.io.IOException
4 |
5 | import org.scalatest.{FunSpec, Matchers}
6 |
7 | import scala.util.{Failure, Success, Try}
8 |
9 | class N01PatternMatching extends FunSpec with Matchers {
10 |
11 | override def suiteName: String = "Readable Code in Scala ~ パターンマッチ編"
12 |
13 | it("match式おさらい") {
14 | val anyObject: Any = "nanika"
15 | anyObject match {
16 | case 1 => "one" // 値のマッチング
17 | case d: Double => d.toString // 型のマッチング
18 | case Some(x) => x.toString // 構造のマッチング
19 | case s: String if 5 <= s.length => s // パターンガード
20 | case any => "Any" // 任意の値
21 | }
22 | }
23 |
24 | case class User(name: Option[String], isActive: Boolean)
25 |
26 | val userList = List(
27 | User(Some("user1"), isActive = true),
28 | User(None, isActive = true),
29 | User(Some("user3"), isActive = false),
30 | User(Some("user444444444444"), isActive = true),
31 | User(Some("user555555555555"), isActive = false),
32 | User(None, isActive = false),
33 | User(Some("user7"), isActive = true)
34 | )
35 |
36 | val expect = List("user1", "user4", "user7")
37 |
38 | it("愚直に実装する") {
39 |
40 | /** + `List[User]` に対して + `isActive` が `true` のものだけを抜き出し + 名前が10文字以上の場合は最初の5文字だけを抜き出し + `List[String]` を返す
41 | */
42 | def extractUserNameWithTop10Chars(users: List[User]): List[String] = {
43 | users
44 | .withFilter(u => u.isActive)
45 | .withFilter(u => u.name.isDefined)
46 | .map { user =>
47 | user match {
48 | case User(Some(name), _) =>
49 | if (10 <= name.length) {
50 | name.take(5)
51 | } else {
52 | name
53 | }
54 | }
55 | }
56 | }
57 |
58 | extractUserNameWithTop10Chars(userList) shouldBe expect
59 | }
60 |
61 | it("パターンマッチを使ってみる") {
62 | def extractUserNameWithTop10Chars02(users: List[User]): List[String] = {
63 | users
64 | .withFilter(u => u.isActive)
65 | .withFilter(u => u.name.isDefined)
66 | .map { user =>
67 | user match {
68 | case User(Some(name), _) if 10 <= name.length => name.take(5)
69 | case User(Some(name), _) => name
70 | }
71 | }
72 | }
73 | extractUserNameWithTop10Chars02(userList) shouldBe expect
74 |
75 | def extractUserNameWithTop10Chars03(users: List[User]): List[String] = {
76 | users.flatMap { user =>
77 | user match {
78 | case User(Some(name), true) if 10 <= name.length => List(name.take(5))
79 | case User(Some(name), true) => List(name)
80 | case _ => Nil
81 | }
82 | }
83 | }
84 | extractUserNameWithTop10Chars03(userList) shouldBe expect
85 | }
86 |
87 | it("collectを使う") {
88 | def extractUserNameWithTop10Chars04(users: List[User]): List[String] =
89 | users.collect {
90 | case User(Some(name), true) if 10 <= name.length => name.take(5)
91 | case User(Some(name), true) => name
92 | }
93 |
94 | extractUserNameWithTop10Chars04(userList) shouldBe expect
95 | }
96 |
97 | it("PartialFunctionとは") {
98 | // trait PartialFunction[-A, +B] extends (A => B)
99 | // * 特定の引数に対してのみ結果を返す関数。
100 | // * 引数により値を返さない場合がある。
101 |
102 | val pf: PartialFunction[Int, String] = {
103 | case 0 => "zero"
104 | case i if i % 2 == 0 => "even"
105 | }
106 |
107 | // isDefinedAt ... 値をを返すか調べる
108 | pf.isDefinedAt(0) shouldBe true
109 | pf.isDefinedAt(1) shouldBe false
110 | pf.isDefinedAt(2) shouldBe true
111 |
112 | // lift ... 結果をOptionに包む
113 | pf.lift(0) shouldBe Some("zero")
114 | pf.lift(1) shouldBe None
115 | pf.lift(2) shouldBe Some("even")
116 |
117 | // タプルのリストに使う
118 | val strings = List(("a", 1), ("b", 2), ("c", 3)).map { tuple =>
119 | tuple._1 * tuple._2
120 | }
121 | val strings2 = List(("a", 1), ("b", 2), ("c", 3)).map { case (str, times) =>
122 | str * times
123 | }
124 | strings shouldBe List("a", "bb", "ccc")
125 | strings shouldBe strings2
126 |
127 | def extractUserNameWithTop10Chars05(users: List[User]): List[String] = {
128 | users.flatMap {
129 | case User(Some(name), true) if 10 <= name.length => Some(name.take(5))
130 | case User(Some(name), true) => Some(name)
131 | case _ => None
132 | }
133 | }
134 | extractUserNameWithTop10Chars05(userList) shouldBe expect
135 | }
136 |
137 | describe("標準ライブラリにおけるPartialFunctionの利用例") {
138 | it("TraversableOnce#collectFirst") {
139 | def isActiveUser(username: String): Option[Boolean] =
140 | userList.find(_.name.contains(username)).map(_.isActive)
141 |
142 | isActiveUser("user1") shouldBe Some(true)
143 | isActiveUser("user3") shouldBe Some(false)
144 | isActiveUser("unknown") shouldBe None
145 |
146 | def isActiveUser2(username: String): Option[Boolean] =
147 | userList.collectFirst {
148 | case User(Some(name), isActive) if name == username => isActive
149 | }
150 |
151 | isActiveUser2("user1") shouldBe Some(true)
152 | isActiveUser2("user3") shouldBe Some(false)
153 | isActiveUser2("unknown") shouldBe None
154 | }
155 |
156 | it("Try#recoverWith") {
157 | def storeUser(user: User): Try[Unit] = Try {
158 | if (user.isActive) println("stored") else throw new IOException
159 | }
160 | def storeError(t: Throwable): Try[Unit] = Try(throw new IllegalStateException)
161 |
162 | def tryStoringUser(user: User): Try[Unit] = {
163 | storeUser(user) match {
164 | case Success(_) => Success(())
165 | case Failure(e: IOException) => storeError(e)
166 | case Failure(e) => Failure(e)
167 | }
168 | }
169 | tryStoringUser(User(None, isActive = true)) shouldBe Success(())
170 | tryStoringUser(User(None, isActive = false)) shouldBe a[Failure[IllegalStateException]]
171 |
172 | def tryStoringUser2(user: User): Try[Unit] =
173 | storeUser(user).recoverWith { case e: IOException =>
174 | storeError(e)
175 | }
176 | tryStoringUser2(User(None, isActive = true)) shouldBe Success(())
177 | tryStoringUser2(User(None, isActive = false)) shouldBe a[Failure[IllegalStateException]]
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/scala_kansai/y2018/N02ForSyntax.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.scala_kansai.y2018
2 |
3 | import org.scalatest.{FunSpec, Matchers}
4 |
5 | import scala.util.Try
6 |
7 | class N02ForSyntax extends FunSpec with Matchers {
8 |
9 | override def suiteName: String = "Readable Code in Scala ~ for式編"
10 |
11 | case class User(name: Option[String], isActive: Boolean)
12 |
13 | val userList = List(
14 | User(Some("user1"), isActive = true),
15 | User(None, isActive = true),
16 | User(Some("user3"), isActive = false),
17 | User(Some("user444444444444"), isActive = true),
18 | User(Some("user555555555555"), isActive = false),
19 | User(None, isActive = false),
20 | User(Some("user7"), isActive = true)
21 | )
22 |
23 | describe("for式のおさらい") {
24 | it("for式は withFilter,flatMap,map,foreach のシュガーシンタックス") {
25 | def findUser(name: String): Option[User] = userList.find(_.name.contains(name))
26 |
27 | // これと
28 | def namePair(userName1: String, userName2: String): Option[(String, String)] =
29 | findUser(userName1).flatMap { user1 =>
30 | user1.name.flatMap { user1Name =>
31 | findUser(userName2).flatMap { user2 =>
32 | user2.name.withFilter(userName2 => 10 <= userName2.length).map { user2Name =>
33 | (user1Name, user2Name.take(5))
34 | }
35 | }
36 | }
37 | }
38 |
39 | namePair("user1", "user2") shouldBe None
40 | namePair("user1", "user444444444444") shouldBe Some(("user1", "user4"))
41 |
42 | // これは同義
43 | def namePair2(userName1: String, userName2: String): Option[(String, String)] =
44 | for {
45 | user1 <- findUser(userName1)
46 | user1Name <- user1.name
47 | user2 <- findUser(userName2)
48 | user2Name <- user2.name if 10 <= userName2.length
49 | } yield (user1Name, user2Name.take(5))
50 |
51 | namePair2("user1", "user2") shouldBe None
52 | namePair2("user1", "user444444444444") shouldBe Some(("user1", "user4"))
53 |
54 | // yield がないと foreach 展開となる
55 | }
56 |
57 | it("mapの展開") {
58 | for {
59 | a <- List(1, 2, 3, 4, 5)
60 | } yield a * 2
61 |
62 | List(1, 2, 3, 4, 5).map(a => a * 2)
63 | }
64 |
65 | it("withFilterの展開") {
66 | for {
67 | a <- List(1, 2, 3, 4, 5) if a < 3
68 | } yield a * 2
69 |
70 | List(1, 2, 3, 4, 5)
71 | .withFilter(a => a < 3)
72 | .map(a => a * 2)
73 | }
74 |
75 | it("flatMapの展開") {
76 | for {
77 | a <- List(1, 2, 3) if a < 3
78 | b <- List(4, 5, 6)
79 | } yield a * b
80 |
81 | List(1, 2, 3).withFilter(a => a < 3).flatMap { a =>
82 | List(4, 5, 6).map { b =>
83 | a * b
84 | }
85 | }
86 | }
87 |
88 | it("foreachの展開") {
89 | for {
90 | a <- List(1, 2, 3) if a < 3
91 | b <- List(4, 5, 6)
92 | } println(a * b)
93 |
94 | List(1, 2, 3).withFilter(a => a < 3).foreach { a =>
95 | List(4, 5, 6).foreach { b =>
96 | println(a * b)
97 | }
98 | }
99 | }
100 |
101 | it("for式を紐解いてみよう") {
102 | // これを展開(desugar)するとどうなる?
103 | for {
104 | i1 <- Try(10 / 2) if 0 < i1
105 | i2 <- Try(10 / 0) if 1 < i2
106 | } yield i1 + i2
107 |
108 | // ↓
109 | for {
110 | i1 <- Try(10 / 2).withFilter(i1 => 0 < i1)
111 | i2 <- Try(10 / 0).withFilter(i2 => 1 < i2)
112 | } yield i1 + i2
113 |
114 | // ↓
115 | Try(10 / 2).withFilter(i1 => 0 < i1).flatMap { i1 =>
116 | for {
117 | i2 <- Try(10 / 0).withFilter(i2 => 1 < i2)
118 | } yield i1 + i2
119 | }
120 |
121 | // ↓
122 | Try(10 / 2).withFilter(i1 => 0 < i1).flatMap { i1 =>
123 | Try(10 / 0).withFilter(i2 => 1 < i2).map { i2 =>
124 | i1 + i2
125 | }
126 | }
127 |
128 | // Scalaを使う上でfor式は可読性においても強力です。
129 | // ぜひ使いこなして行きましょう~
130 | }
131 | }
132 |
133 | describe("本当にあった怖いfor式") {
134 |
135 | it("恐ろしく長いfor式") {
136 | def とてもすごい処理: Try[Seq[String]] = {
137 | for {
138 | activeUsersGroupIds <- Try {
139 | memberRepository.resolveAll().collect {
140 | case Member(_, _, isActive, Some(groupId)) if isActive => groupId
141 | }
142 | }
143 | activeGroupNames <- Try {
144 | groupRepository.resolveIn(activeUsersGroupIds.toSet).collect {
145 | case Group(_, name, isDeleted) if !isDeleted => name
146 | }
147 | }
148 | // 100行近く続く …
149 | sugoiResult: Seq[String] = {
150 | {
151 | {
152 | Seq( /* なにか */ )
153 | }
154 | }
155 | }
156 | } yield sugoiResult
157 | }
158 | }
159 |
160 | case class Member(id: Long, name: String, isActive: Boolean, groupId: Option[Long])
161 | case class Group(id: Long, name: String, isDeleted: Boolean)
162 |
163 | object memberRepository {
164 | def resolveAll(): Seq[Member] = ???
165 | }
166 | object groupRepository {
167 | def resolveIn(ids: Set[Long]): Seq[Group] = ???
168 | }
169 |
170 | it("Bad Practice: ジェネレータに処理を詰め込む") {
171 | def resolveActiveGroupNames: Try[Seq[String]] = {
172 | for {
173 | activeUsersGroupIds <- Try {
174 | memberRepository.resolveAll().collect {
175 | case Member(_, _, isActive, Some(groupId)) if isActive => groupId
176 | }
177 | }
178 | activeGroupNames <- Try {
179 | groupRepository.resolveIn(activeUsersGroupIds.toSet).collect {
180 | case Group(_, name, isDeleted) if !isDeleted => name
181 | }
182 | }
183 | } yield activeGroupNames
184 | }
185 | }
186 |
187 | it("内部関数やprivateメソッドに切り出す") {
188 | def resolveActiveGroupNames: Try[Seq[String]] = {
189 | for {
190 | members <- resolveAllMembers()
191 | groupIds = extractGroupIdFromActiveMember(members)
192 | groups <- resolveGroupsIn(groupIds)
193 | activeGroupNames = extractNameFromExistsGroup(groups)
194 | } yield activeGroupNames
195 | }
196 |
197 | def resolveAllMembers() =
198 | Try(memberRepository.resolveAll())
199 |
200 | def extractGroupIdFromActiveMember(members: Seq[Member]) = members.collect {
201 | case Member(_, _, isActive, Some(groupId)) if isActive => groupId
202 | }
203 |
204 | def resolveGroupsIn(groupIds: Seq[Long]) = Try(groupRepository.resolveIn(groupIds.toSet))
205 |
206 | def extractNameFromExistsGroup(groups: Seq[Group]) = groups.collect {
207 | case Group(_, name, isDeleted) if !isDeleted => name
208 | }
209 | }
210 |
211 | it("複数のfor式に書き換える") {
212 | def resolveActiveGroupNames: Try[Seq[String]] = {
213 | for {
214 | groupIds <- resolveActiveGroupIds()
215 | groupNames <- resolveExistsGroupNameIn(groupIds)
216 | } yield groupNames
217 | }
218 |
219 | def resolveActiveGroupIds(): Try[Seq[Long]] =
220 | for {
221 | members <- resolveAllMembers()
222 | groupIds = extractGroupIdFromActiveMember(members)
223 | } yield groupIds
224 |
225 | def resolveExistsGroupNameIn(groupIds: Seq[Long]) =
226 | for {
227 | groups <- resolveGroupsIn(groupIds)
228 | activeGroupNames = extractNameFromExistsGroup(groups)
229 | } yield activeGroupNames
230 |
231 | def resolveAllMembers() =
232 | Try(memberRepository.resolveAll())
233 |
234 | def extractGroupIdFromActiveMember(members: Seq[Member]) = members.collect {
235 | case Member(_, _, isActive, Some(groupId)) if isActive => groupId
236 | }
237 |
238 | def resolveGroupsIn(groupIds: Seq[Long]) = Try(groupRepository.resolveIn(groupIds.toSet))
239 |
240 | def extractNameFromExistsGroup(groups: Seq[Group]) = groups.collect {
241 | case Group(_, name, isDeleted) if !isDeleted => name
242 | }
243 | }
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/testing/ScalaCheckSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.testing
2 |
3 | import org.scalacheck.{Arbitrary, Gen}
4 | import org.scalatest.{FunSpec, Matchers}
5 | import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
6 |
7 | class ScalaCheckSpec extends FunSpec with Matchers with ScalaCheckDrivenPropertyChecks {
8 |
9 | it("forAll ... ランダムに生成された値でテストを行う") {
10 | forAll { (s1: String, s2: String) =>
11 | s1.length + s2.length shouldBe (s1 + s2).length
12 | }
13 | }
14 |
15 | it("whenever ... 値のフィルタ") {
16 | forAll { (i: Int) =>
17 | whenever(1 < i) {
18 | i / 2 should be > 0
19 | }
20 | }
21 | }
22 |
23 | case class User(name: String, age: Int) {
24 | def isAdult: Boolean = 20 <= age
25 | }
26 |
27 | it("Gen ... 値を生成するジェネレータ") {
28 | // 1,2,3 の中からひとつランダムに生成されるGen
29 | val intGen: Gen[Int] = Gen.oneOf(1, 2, 3)
30 |
31 | forAll(intGen) { i =>
32 | (1 <= i && i <= 3) shouldBe true
33 | }
34 |
35 | // for式でGenを組み合わせられる
36 | val userGen: Gen[User] = for {
37 | name <- Gen.oneOf("a", "b", "c")
38 | age <- Gen.oneOf(10, 20, 30)
39 | } yield User(name, age)
40 |
41 | forAll(userGen) { (u: User) =>
42 | (u.name + u.age.toString).length shouldBe 3
43 | }
44 | }
45 |
46 | it("Gen.oneOf ... 複数の要素からひとつを選択する") {
47 | val stringGen = Gen.oneOf("hoge", "foo", "bar")
48 | forAll(stringGen)(s => s.toLowerCase shouldEqual s)
49 | }
50 |
51 | it("Gen.someOf ... 複数の要素から0個以上選択する") {
52 | val stringListGen = Gen.someOf(Seq("a", "b", "c"))
53 | forAll(stringListGen)(ss => ss.mkString shouldBe ss.fold("")(_ + _))
54 | }
55 |
56 | it("Gen.choose ... 範囲の中からひとつ選択する") {
57 | val intGen = Gen.choose(-100, 100)
58 | forAll(intGen)(i => i * 2 shouldEqual i + i)
59 | }
60 |
61 | it("Gen.alphaNumStr ... ランダムな文字列を生成する") {
62 | val stringGen = Gen.alphaNumStr
63 | forAll(stringGen)(_.contains("@") shouldEqual false)
64 |
65 | // そのほかのバリエーション
66 | forAll(Gen.alphaStr)(_.contains("0") shouldEqual false)
67 | forAll(Gen.alphaLowerStr)(_.contains("A") shouldEqual false)
68 | forAll(Gen.alphaUpperStr)(_.contains("a") shouldEqual false)
69 | forAll(Gen.numStr)(s => (s.contains("a") || s.contains("A")) shouldEqual false)
70 | }
71 |
72 | it("Gen#suchThat ... ジェネレータに対するフィルタ") {
73 | val userGen = for {
74 | name <- Gen.alphaNumStr
75 | age <- Gen.choose(1, 100).suchThat(20 <= _)
76 | } yield User(name, age)
77 |
78 | forAll(userGen)(_.isAdult shouldBe true)
79 |
80 | // for式のガードでも同様
81 | val userGen2 = for {
82 | name <- Gen.alphaNumStr
83 | age <- Gen.choose(1, 100) if 20 <= age
84 | } yield User(name, age)
85 |
86 | forAll(userGen2)(_.isAdult shouldBe true)
87 | }
88 |
89 | it("Arbitrary ... forAllのimplicit parameterとして使う") {
90 | val userGen = for {
91 | name <- Gen.alphaNumStr
92 | age <- Gen.choose(1, 100).suchThat(20 <= _)
93 | } yield User(name, age)
94 |
95 | implicit val arbitraryUser: Arbitrary[User] = Arbitrary(userGen)
96 |
97 | forAll { (u: User) =>
98 | u.isAdult shouldBe true
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/with_java/WithJavaSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.with_java
2 |
3 | import org.scalatest.{FunSpec, Matchers}
4 |
5 | class WithJavaSpec extends FunSpec with Matchers {
6 |
7 | override def suiteName: String = "ScalaとJavaの結合"
8 |
9 | it("ScalaからJavaを使う 01") {
10 | val java = new JavaClass(1)
11 | java.f() shouldBe "1"
12 | }
13 |
14 | it("ScalaからJavaを使う 02") {
15 | val useScala = new UseScala(new ScalaClass(2))
16 | useScala.f2(ScalaCaseClass(3, "hoge")) shouldBe """ScalaCaseClass(3,hoge)"""
17 | }
18 | }
19 |
20 | class ScalaClass(val i: Int)
21 |
22 | case class ScalaCaseClass(id: Long, name: String)
23 |
24 | object ScalaObject {
25 | def f(i: Int): String = i.toString
26 | }
27 |
--------------------------------------------------------------------------------
/legacy/src/test/scala/org/nomadblacky/scala/samples/xml/XmlSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.samples.xml
2 |
3 | import org.scalatest.FunSpec
4 |
5 | import scala.xml.{Elem, XML}
6 |
7 | class XmlSpec extends FunSpec {
8 |
9 | override def suiteName: String = "XMLを扱う"
10 |
11 | it("xmlリテラル") {
12 | val xml = title
13 | assert(xml.getClass == classOf[Elem])
14 | assert(xml.toString() == "title
")
15 | }
16 |
17 | it("値を埋め込む") {
18 | val a = 100
19 | val xml = {a}
20 | assert(xml.toString() == "100
")
21 | }
22 |
23 | it("ファイルから読み込む") {
24 | val xml = XML.load(getClass.getResource("xml_spec_01.html"))
25 | assert(xml.getClass == classOf[Elem])
26 | println(xml)
27 | }
28 |
29 | it("要素を取得する1") {
30 | val xml = XML.load(getClass.getResource("xml_spec_01.html"))
31 | val body = xml \ "body"
32 | println(body)
33 | }
34 |
35 | it("要素を取得する2") {
36 | val xml = XML.load(getClass.getResource("xml_spec_01.html"))
37 | val pTags = xml \ "body" \ "div" \ "p"
38 | assert(pTags.size == 5)
39 | println(pTags)
40 | }
41 |
42 | it("要素を取得する3") {
43 | val xml = XML.load(getClass.getResource("xml_spec_01.html"))
44 | val aTags = xml \\ "a"
45 | assert(aTags.size == 2)
46 | println(aTags)
47 | }
48 |
49 | it("属性から要素を取得する") {
50 | val xml = XML.load(getClass.getResource("xml_spec_01.html"))
51 | // これだとうまくいかない
52 | // val p = xml \\ "p" \ "@class"
53 | val p = xml \\ "p" \\ "@class"
54 | assert(p.size == 1)
55 | println(p)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.9.9
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")
2 |
3 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4")
4 |
5 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.0")
6 |
--------------------------------------------------------------------------------
/reporter/src/main/scala/org/nomadblacky/scala/reporter/TableOfContentsReporter.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.reporter
2 |
3 | import java.io.PrintWriter
4 | import java.nio.file.{Path, Paths}
5 |
6 | import org.scalatest.Reporter
7 | import org.scalatest.events._
8 |
9 | import scala.collection.mutable.ListBuffer
10 | import scala.util.matching.Regex
11 |
12 | /** Created by blacky on 16/11/06.
13 | */
14 | class TableOfContentsReporter() extends Reporter {
15 |
16 | val markdownFilePath: Path = Paths.get("README.md")
17 | val succeededTests: ListBuffer[TestSucceeded] = ListBuffer[TestSucceeded]()
18 |
19 | val header = "# Table of Contents\n"
20 |
21 | lazy val relativeFilePathRegex: Regex = {
22 | val currentDir = Paths.get(".").toAbsolutePath.getParent
23 | s"$currentDir/(.+)".r
24 | }
25 |
26 | override def apply(event: Event): Unit = event match {
27 | case e: TestSucceeded =>
28 | succeededTests += e
29 | println(s"${Console.GREEN}${e.testName}${Console.RESET}")
30 | case e: TestFailed =>
31 | println(s"${Console.RED}${e.testName}${Console.RESET}")
32 | e.throwable.foreach(_.printStackTrace())
33 | case _: RunCompleted => writeTableOfContents()
34 | case _ =>
35 | }
36 |
37 | private[reporter] def getLineNumber(test: TestSucceeded, default: Int = 1): Int =
38 | test.location
39 | .collect { case l: LineInFile => l.lineNumber }
40 | .getOrElse(default)
41 |
42 | private def writeTableOfContents(): Unit = {
43 | val sortedSuites = succeededTests
44 | .groupBy(_.suiteName)
45 | .toSeq
46 | .sortBy(_._1)
47 |
48 | val markdownLines: Seq[String] = sortedSuites
49 | .flatMap { case (suiteName, tests) =>
50 | val testLines = tests.toList
51 | .sortBy(getLineNumber(_, Int.MaxValue))
52 | .map(getTestDetailLine)
53 | Seq("", s"## $suiteName", "") ++ testLines
54 | }
55 |
56 | for (pw <- new PrintWriter(markdownFilePath.toFile)) {
57 | header +: markdownLines foreach pw.println
58 | }
59 | }
60 |
61 | private def getTestDetailLine(test: TestSucceeded): String = {
62 | val githubRelativeUrl = for {
63 | location <- test.location.collect { case l: LineInFile => l }
64 | filePathname <- location.filePathname
65 | absolutePathName = Paths.get(filePathname).toAbsolutePath.toString
66 | matchGroups <- relativeFilePathRegex.unapplySeq(absolutePathName)
67 | relativeFilePath <- matchGroups.headOption
68 | } yield relativeFilePath
69 |
70 | githubRelativeUrl match {
71 | case Some(url) => s"+ [${test.testName}]($url#L${getLineNumber(test)})"
72 | case None => s"+ ${test.testName}"
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/reporter/src/main/scala/org/nomadblacky/scala/reporter/package.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala
2 |
3 | package object reporter {
4 |
5 | // Refer to ... http://ym.hatenadiary.jp/entry/2015/04/02/163557
6 | implicit class Using[T <: AutoCloseable](resource: T) {
7 | def foreach[R](op: T => R): R = {
8 | try op(resource)
9 | catch { case e: Exception => throw e }
10 | finally resource.close()
11 | }
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/reporter/src/test/scala/org/nomadblacky/scala/reporter/UsingSpec.scala:
--------------------------------------------------------------------------------
1 | package org.nomadblacky.scala.reporter
2 |
3 | import org.scalatest.{FunSpec, Matchers}
4 |
5 | class UsingSpec extends FunSpec with Matchers {
6 |
7 | class Resource extends AutoCloseable {
8 | var isClosed = false
9 | override def close(): Unit = {
10 | isClosed = true
11 | }
12 | }
13 |
14 | describe("#foreach") {
15 | it("should be closing resources when finished operation") {
16 | val resource1 = new Resource
17 | val resource2 = new Resource
18 |
19 | for {
20 | r1 <- resource1
21 | r2 <- resource2
22 | } {
23 | (r1.isClosed, r2.isClosed) shouldBe (false, false)
24 | }
25 |
26 | (resource1.isClosed, resource2.isClosed) shouldBe (true, true)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/sbt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NomadBlacky/scala_examples/00456dfcfb4ad3bee72d578b4c93c9ae8fa8f6bf/sbt
--------------------------------------------------------------------------------
/scala3/README.md:
--------------------------------------------------------------------------------
1 | ## Scala3
2 |
3 | Scala の新しいバージョンである v3 系の新しい機能や変更点をまとめます。
4 |
5 | ### コード例
6 |
7 | Scala Version: 3.0.x
8 |
9 | + [新しい制御構文](src/test/scala/dev/nomadblacky/scala_examples/scala3/Scala3Test.scala)
10 |
11 | ### 参考資料
12 |
13 | + [Scala3 Migration Guide](https://docs.scala-lang.org/scala3/guides/migration/compatibility-intro.html)
14 |
--------------------------------------------------------------------------------
/scala3/src/test/scala/dev/nomadblacky/scala_examples/scala3/Scala3Test.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.scala3
2 |
3 | class Scala3Test extends munit.FunSuite {
4 | test("新しい制御構文") {
5 | // https://docs.scala-lang.org/scala3/reference/other-new-features/control-syntax.html
6 |
7 | // if - then で括弧を省略
8 | val x = 1
9 | if x < 0 then "negative"
10 | else if x == 0 then "zero"
11 | else "positive"
12 |
13 | if x < 0 then -x else x
14 |
15 | // while - do で括弧を省略
16 | var i = 0
17 | while i < 3 do i += 1
18 |
19 | // for - yield で括弧を省略
20 | for x <- (1 to 3) if x > 1 yield x * x
21 |
22 | // for - do で括弧を省略
23 | for
24 | x <- (1 to 3)
25 | y <- (1 to 3)
26 | do println(x + y)
27 |
28 | // try-catch - 例外がひとつの場合ワンライナーで書ける
29 | // format: off
30 | try sys.error("error!") catch case ex: Exception => println(ex)
31 | // format: on
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/shapeless/README.md:
--------------------------------------------------------------------------------
1 | ## shapeless
2 |
3 | [shapeless][github] は Scala でジェネリックプログラミングを実現するためのライブラリです。
4 |
5 | ### コード例
6 |
7 | Scala Version: 2.13.x
8 |
9 | + [ShapelessSpec](src/test/scala/dev/nomadblacky/scala_examples/shapeless/ShapelessSpec.scala)
10 |
11 | ### 参考資料
12 |
13 | + [GitHub][github]
14 |
15 | [github]: https://github.com/milessabin/shapeless
16 |
--------------------------------------------------------------------------------
/shapeless/src/test/scala/dev/nomadblacky/scala_examples/shapeless/ShapelessSpec.scala:
--------------------------------------------------------------------------------
1 | package dev.nomadblacky.scala_examples.shapeless
2 |
3 | import org.scalatest.funspec.AnyFunSpec
4 | import org.scalatest.matchers.should.Matchers
5 | import shapeless._
6 |
7 | /** Shapeless
8 | *
9 | * Scalaでジェネリックプログラミングをサポートするライブラリ
10 | */
11 | class ShapelessSpec extends AnyFunSpec with Matchers {
12 |
13 | override def suiteName: String = "Shapeless"
14 |
15 | it("Poly ... 複数の型を処理できる関数") {
16 | object size extends Poly1 {
17 | implicit def caseInt = at[Int](_ => 1)
18 | implicit def caseString = at[String](_.length)
19 | implicit def caseTuple[T, U](implicit st: Case.Aux[T, Int], su: Case.Aux[U, Int]) =
20 | at[(T, U)](t => size(t._1) + size(t._2))
21 | }
22 |
23 | size(12) shouldBe 1
24 | size("hoge") shouldBe 4
25 | size((12, "hoge")) shouldBe 5
26 | size(((12, "hoge"), 99)) shouldBe 6
27 | // 対応する型が定義されていないのでコンパイルエラー
28 | // size(1.0)
29 | }
30 |
31 | it("HList ... 複数の型を持てるList") {
32 | val xs = 1 :: "hoge" :: HNil
33 |
34 | // 要素の追加
35 | xs :+ 3.0 shouldBe 1 :: "hoge" :: 3.0 :: HNil
36 | 4.0 +: xs shouldBe 4.0 :: 1 :: "hoge" :: HNil
37 |
38 | // 要素の取得
39 | xs(0) shouldBe 1
40 | xs.head shouldBe 1
41 | xs.last shouldBe "hoge"
42 | }
43 |
44 | it("HListの操作") {
45 | val xs = 1 :: "hoge" :: HNil
46 |
47 | object minusOne extends Poly1 {
48 | implicit val caseInt = at[Int](_ - 1)
49 | implicit val caseString = at[String](_.init)
50 | }
51 | xs.map(minusOne) shouldBe (0 :: "hog" :: HNil)
52 |
53 | object totalSize extends Poly2 {
54 | implicit val caseInt = at[Int, Int](_ + _)
55 | implicit val caseString = at[Int, String](_ + _.length)
56 | }
57 | xs.foldLeft(0)(totalSize) shouldBe 5
58 | }
59 |
60 | it("Coproduct ... Eitherを任意の数の選択肢にしたもの") {
61 | // Int or String or Boolean
62 | type ISB = Int :+: String :+: Boolean :+: CNil
63 |
64 | // 値を定義
65 | val isb = Coproduct[ISB]("hoge")
66 |
67 | // 値を取得
68 | isb.select[Int] shouldBe None
69 | isb.select[String] shouldBe Some("hoge")
70 | isb.select[Boolean] shouldBe None
71 | // コンパイルエラー
72 | // isb.select[Double]
73 |
74 | // 変換
75 | object size extends Poly1 {
76 | implicit val intCase = at[Int](i => (i, i))
77 | implicit val stringCase = at[String](s => (s, s.length))
78 | implicit val booleanCase = at[Boolean](b => (b, if (b) 1 else 0))
79 | }
80 | val result = isb.map(size)
81 | type SIZE = (Int, Int) :+: (String, Int) :+: (Boolean, Int) :+: CNil
82 |
83 | result shouldBe a[SIZE]
84 | result.select[(Int, Int)] shouldBe None
85 | result.select[(String, Int)] shouldBe Some(("hoge", 4))
86 | result.select[(Boolean, Int)] shouldBe None
87 | // コンパイルエラー
88 | // result.select[(Double, Double)]
89 | }
90 |
91 | it("Generic ... case classなどをHListやCoproductに変換する") {
92 | // HList
93 | case class User(id: Int, name: String, age: Int)
94 |
95 | val userGen = Generic[User]
96 |
97 | val user = User(1, "hoge", 20)
98 | userGen.to(user) shouldBe (1 :: "hoge" :: 20 :: HNil)
99 | userGen.from(2 :: "foo" :: 30 :: HNil) shouldBe User(2, "foo", 30)
100 |
101 | // Coproduct
102 | sealed trait Lang
103 | case class Java() extends Lang
104 | case class Scala() extends Lang
105 |
106 | val lang = Generic[Lang].to(Scala())
107 |
108 | lang.select[Java] shouldBe None
109 | lang.select[Scala] shouldBe Some(Scala())
110 | }
111 |
112 | it("Record ... HListにキーがついたもの") {
113 | import syntax.singleton._
114 | import record._
115 |
116 | val user =
117 | ("id" ->> 1) ::
118 | ("name" ->> "hoge") ::
119 | ("age" ->> 20) ::
120 | HNil
121 |
122 | user("id") shouldBe 1
123 | user("name") shouldBe "hoge"
124 | user("age") shouldBe 20
125 | // 定義されていないのでコンパイルエラー
126 | // user("foo")
127 | }
128 |
129 | it("Sindleton-typed literals ... リテラルをひとつの型として扱う") {
130 | import syntax.singleton._
131 |
132 | // 1しか受け付けない関数
133 | val one = 1.witness
134 | def f(x: one.T) = x
135 |
136 | f(1) shouldBe 1
137 | // コンパイルエラー
138 | // f(2)
139 | }
140 |
141 | it("case classをMapに変換する") {
142 | import Mappable._
143 |
144 | case class User(id: Int, name: String, age: Int)
145 | val user = User(1, "hoge", 20)
146 |
147 | user.toMap shouldBe Map("id" -> 1, "name" -> "hoge", "age" -> 20)
148 | }
149 |
150 | }
151 |
152 | // https://gist.github.com/calippo/892ce793c9696b330e55772099056b7a
153 | object Mappable {
154 | implicit class ToMapOps[A](val a: A) extends AnyVal {
155 | import shapeless._
156 | import ops.record._
157 |
158 | def toMap[L <: HList](implicit gen: LabelledGeneric.Aux[A, L], tmr: ToMap[L]): Map[String, Any] = {
159 | val m: Map[tmr.Key, tmr.Value] = tmr(gen.to(a))
160 | m.map {
161 | case (k: Symbol, v) => k.name -> v
162 | case x => throw new IllegalStateException(x.toString())
163 | }
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/tmp/sample.txt:
--------------------------------------------------------------------------------
1 | Scala de Scala
2 |
--------------------------------------------------------------------------------
/update-readme.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if git status -s | grep README.md; then
4 | git checkout master
5 | git config user.email "travis@travis.com"
6 | git config user.name "travis"
7 | git add -u
8 | git commit -m "Update table of contents"
9 | git push -fq "https://${GH_TOKEN}@github.com/NomadBlacky/scala_samples.git" master:master
10 | echo "OK: git push"
11 | fi
12 |
--------------------------------------------------------------------------------