├── .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 |
14 |

google

15 |

yahoo

16 |
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 |
14 |

google

15 |

yahoo

16 |
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 | --------------------------------------------------------------------------------