Showing all posts tagged scala:

ScalaMatsuri 2016 A1 Refactoring in Scala

ScalaMatsuri 2016は大盛況のうちに幕を閉じました。私もスタッフとして参加しておりました。

さて、ScalaMatsuri2016のA会場で行われた、基調講演たるがくぞさんのセッションの内容を簡単にまとめたので共有します。




【値オブジェクトに使える値の型4選】

  1. Type Alias(簡単、意味が明確にできる。だが型安全でない)
  2. Tagged Type(型安全だ。しかし数値型だとboxing/unboxingが発生してしまう)
  3. Value Class(boxing/unboxing発生しない。しかし定義するのが面倒)
  4. Phantom Type(http://gakuzzzz.github.io/slides/refactoring_in_scala/#27

【Phantom Typeはちょっとコツがいる。導入のための手順は以下のとおり】

  • DBの取り扱い可能な値型との相互変換が問題となる
  • したがってPrismまたはIsoを導入する(相互変換のためのパターン)
    • DB側でPrismの扱いを定義する
    • それぞれの値オブジェクトでPrismを継承する
      • ボイラープレートだ
      • experimentalだがmacroを使えればボイラープレートがなくなる



といったトークがあり、(独断と偏見で)まとめると以下の表のようになります


Type Alias
Tagged Type
Value Class
Phantom Type
Phantom Type & Prism & macro
導入コスト
◎:他のロジックに影響ない
×:定義が面倒
△:macroはまだExperimental
型安全
×
コンテナ型へのキャスト
×
×
追加のメソッド定義
×
×
boxing/unboxing
×:AnyValだとアカン
レイヤー境界での変換が不要(DBなどとの互換性)
×
×
×


こんなにわかりやすい発表資料もなかなかないと思います。

急いで作ったのでミスあると思いますが、その際はどうぞお知らせください。-> https://twitter.com/NoriakiHoriuchi

java.lang.IllegalArgumentException

以下のエラーと長いStacktraceが出力されてどうしたものかと思ったが、ここで示されているList内にパッケージ内のどこでエラーが出ているのかが書いてあったおかげで救われました。


[error] (compile:compileIncremental) java.lang.IllegalArgumentException: Could not find proxy for model: models.MyModel in List(value model, method apply, <$anon: Function1>, method myMethod, class MyClass, package myPackage, package services, package <root>) (currentOwner= value x$11 )

List(value model, method apply, <$anon: Function1>, method myMethod, class MyClass, package myPackage, package services, package <root>) とあるので、末尾から逆に辿って _root_.services.myPackage.MyClass に定義されているmyMethod内の引数一つの無名関数のどこかにおかしいところがあります。

原因は myMethod 内で使用していた Optionfold[B](ifEmpty: => B)(f: A => B) について、第一引数 ifEmpty を指定し忘れていたからでした。

改行して中にコメントを追加していたのでIDEも検知できなかったようです(?)。

foreach するなり、 for 内包表記で処理するという対処法があります。また、Unitを返すべき場面では () を追加するという方法もあります。

Scalaのクラスのコンストラクタに、空の括弧(Empty Parentheses)をつけて定義すべきかどうか?結論:どちらでもいい

「引数を取らないメソッドに対して、空の括弧(empty paren)をつけて定義するか?それともつけずに定義するか?」は、副作用があるかどうかで決めるのがScalaの流儀であります。これと同様に、クラスのコンストラクタも引数を取らないときには括弧をつけるパターンとつけないパターン、いずれの方法でも定義することができます。そうなると果たしてどうやって括弧をつけるかどうか判断すべきなのか?というのが今回のテーマです。

じっさい公式のスタイルガイドにはそのような項目がないので、どうするかは各人に任されているようなのですが、StackOverflowにはドンピシャな質問があります。

Scala style for empty class parameter lists

ベストアンサーの方の回答を要約すると

  1. Javaでクラス(括弧あり)とインターフェースを見分けるために使っているように、Scalaでもクラス(空の括弧あり)とトレイトで使い分けてもいい

  2. メソッドと同様に、副作用があるならつける、ないならつけないということもできる

    • でもコンストラクタでは副作用が発生しないのが理想だよな…
  3. 状態を持つオブジェクト、ミュータブルなオブジェクト(空の括弧つける)とイミュータブルなオブジェクト(括弧つけない)で使い分けるということもできる

    • でもケースクラスはイミュータブルであるべきだが、空の括弧をつけずに定義するのは非推奨だし…
  4. 個人的にはない方が見やすいから付けてないよ

ということです。

個人的にも、わざわざ付ける必要はないと思っています。

Scalaのcase classの第二パラメータリストに外からアクセスできなくて困った件

ケースクラスの2つ目の引数が外から見えない、なぜだろう?ということが発端となり、いろいろと調べたりGitterで質問してみました。その結果、コンストラクタ引数の定義のしかたによって見え方(アクセス修飾子の状態)が異なるということがわかりました。


scala> case class A(x: Int)(y: Int){

     | def printY() = println(y)

     | }

defined class A

scala> val a = A(10)(20)

a: A = A(10)

scala> a.x

res22: Int = 10

scala> a.y

<console>:19: error: value y is not a member of A

              a.y

                ^

scala> a.printY()

20

クラスののデフォルトコンストラクタ引数はprivateです。したがって、下の例ではprivateなフィールドxにアクセスしようとしてエラーが出ています。


scala> class A(x: Int)

defined class A

scala> new A(10)

res0: A = A@af23093

scala> res0.x

<console>:13: error: value x is not a member of A

       res0.x

            ^

scala> 

私は普段case classをよく使う一方、クラスのフィールドに外からアクセスすることはあまりないので、すっかり頭のなかから抜け落ちていました。

このフィールドをpublicなものとするには、 val または var を明示的に付けてあげる必要があります。


scala> class A(val x: Int)

defined class A

scala> new A(10)

res0: A = A@6deaf732

scala> res0.x

res1: Int = 10

あるいは、case classとすると、valやvarを付加しなくても自動生成されたゲッターメソッドによってフィールドにアクセスできます。


scala> case class A(x: Int)

defined class A

scala> A(10)

res0: A = A(10)

scala> res0.x

res1: Int = 10

しかしながら、case classは万能ではありません。case classに複数のパラメータリストが存在する場合、2つ目以降のパラメータリストにはゲッターメソッドを生成してくれません。


scala>  case class B(x: Int)(y: Int)(z: Int)

defined class B

scala> B(10)(20)(30)

res0: B = B(10)

scala> res0.x

res1: Int = 10

scala> res0.y

<console>:14: error: value y is not a member of B

       res0.y

            ^

scala> res0.z

<console>:14: error: value z is not a member of B

       res0.z

            ^

それどころか、case classはequals()やhashCode()についても2つ目以降のパラメータリストを無視してくれるので、以下のように「第一パラメータだけで比較する」「ハッシュコードが同一になる」といった現象が起こります。
https://gitter.im/scalajp/public


scala> case class C(x: Int)(y: Int)

defined class C

scala> C(10)(20)

res0: C = C(10)

scala> C(10)(30)

res1: C = C(10)

scala> res0 == res1

res2: Boolean = true

scala> res0.hashCode

res3: Int = -2008924253

scala> res1.hashCode

res4: Int = -2008924253

結論:気をつけます。

参考


http://www.ne.jp/asahi/hishidama/home/tech/scala/class.html#h_construntor


http://www.ne.jp/asahi/hishidama/home/tech/scala/class.html#h_case_class

Scalaをちょろっと書く方法

ちゃんとしたScalaプロジェクトを書くなら、相当に腕の立つ方でない限りIDEを使って書いたほうがいいですが、簡単なプログラムを動かしたいという時には小回りがきかずやや面倒です。

ここでは、テキストのサンプルコードをちょっと動かしてみたいときに使える方法を挙げます。

REPLで動かす

  • scalaにパスが通っている場合: scala と入力して実行

  • activatorにパスが通っている場合: activator と打ってから console と入力、または activator console と入力して実行

  • sbtにパスが通っている場合: sbt と打ってから console と入力、または sbt console と入力して実行

このあと好きなように入力します。

REPLでファイルを指定して実行する

script.scala という名前で以下のような内容のファイルを作成する。


object HelloWorld extends App {

  println("Hello, world!")

}

HelloWorld.main(args)

そしてREPLを起動して、 script.scala と入力して実行する。

シェルスクリプトとして動かす

scalaがインストールされているのを前提。

script.sh という名前で以下のような内容のファイルを作成する。


#!/bin/sh

exec scala "$0" "$@"

!#

object HelloWorld extends App {

  println("Hello, world!")

}

HelloWorld.main(args)

そして chmod +x script.sh と実行権限を付加した後、 ./script.sh と入力して実行。

paiza.ioで動かす

paiza.ioならばWebブラウザ上でScalaのコードを動かすことができる。

以上です。

環境

Macのターミナル

Someにタプルを渡したら「Adapting argument list by creating a 4-tuple: this may not be what you want」という警告を受けた

Optionにタプルを包もうと考え、以下の例のように書きました。


Some(userId, None, Some(itemId), item)

すると -Xlint オプションのお陰で、以下のように警告されました。


Adapting argument list by creating a 4-tuple: this may not be what you want.

[warn]         signature: Some.apply[A](x: A): Some[A]

[warn]   given arguments: userId, None, Some(itemId), item

[warn]  after adaptation: Some((userId, None, Some(itemId), item))

[warn]           Some(userId, None, Some(itemId), item)

「はて、うまく渡っているようだが、どういうことだろう」と考え、その前で @unchecked アノテーションを付けるなどしてみたがうまくいかず。

この警告の意味は要するに、

「Someの引数はひとつなのに、与えられた引数は4つもある。仕方がないからタプルに変換して渡してやったが、これはアンタの考えている挙動と異なるかもしれないから、よく確認しろ!」

ということです。

以下のように、タプルを明示的に渡してあげると、警告はなくなりました。ちゃんとタプルとして渡せているのかどうか、注意しなければなりませんね。


Some((orderDetail.sellerId, None, Some(orderDetailComment.orderDetailId), orderDetail))

安易に警告を抑制(@suppressWarnings)できなくてよかったです。

参考

引数の数間違えたと思ったらタプルになってた

CreationException: Unable to create injector

原因:jdbcとplay-slickが依存性に含まれていると、それぞれがplay.api.db.DBApiを使用するので、競合してエラーとなる。

対策:jdbcの除去

参考:play-framework > [play 2.4.0-RC5 Scala] evolutions and injector error with play-slick

! @6mk485065 - Internal server error, for (GET) [/] ->

play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:

1) A binding to play.api.db.DBApi was already configured at play.api.db.slick.evolutions.EvolutionsModule.bindings(EvolutionsModule.scala:14):
Binding(interface play.api.db.DBApi to ConstructionTarget(class play.api.db.slick.evolutions.internal.DBApiAdapter) in interface javax.inject.Singleton) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1).
  at play.api.db.DBModule.bindings(DBModule.scala:25):
Binding(interface play.api.db.DBApi to ProviderConstructionTarget(class play.api.db.DBApiProvider)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)

1 error]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1.apply(DevServerStart.scala:165) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1.apply(DevServerStart.scala:121) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at scala.Option.map(Option.scala:146) ~[scala-library-2.11.6.jar:na]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1.apply(DevServerStart.scala:121) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1.apply(DevServerStart.scala:119) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at scala.util.Success.flatMap(Try.scala:230) ~[scala-library-2.11.6.jar:na]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1.apply(DevServerStart.scala:119) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1.apply(DevServerStart.scala:111) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24) ~[scala-library-2.11.6.jar:na]
    at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24) ~[scala-library-2.11.6.jar:na]
    at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1423) ~[na:1.8.0_05]
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) ~[na:1.8.0_05]
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902) ~[na:1.8.0_05]
    at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689) ~[na:1.8.0_05]
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644) ~[na:1.8.0_05]
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) ~[na:1.8.0_05]
Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors:

1) A binding to play.api.db.DBApi was already configured at play.api.db.slick.evolutions.EvolutionsModule.bindings(EvolutionsModule.scala:14):
Binding(interface play.api.db.DBApi to ConstructionTarget(class play.api.db.slick.evolutions.internal.DBApiAdapter) in interface javax.inject.Singleton) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1).
  at play.api.db.DBModule.bindings(DBModule.scala:25):
Binding(interface play.api.db.DBApi to ProviderConstructionTarget(class play.api.db.DBApiProvider)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)

1 error
    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:466) ~[guice-4.0.jar:na]
    at com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:155) ~[guice-4.0.jar:na]
    at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:107) ~[guice-4.0.jar:na]
    at com.google.inject.Guice.createInjector(Guice.java:96) ~[guice-4.0.jar:na]
    at com.google.inject.Guice.createInjector(Guice.java:73) ~[guice-4.0.jar:na]
    at com.google.inject.Guice.createInjector(Guice.java:62) ~[guice-4.0.jar:na]
    at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:126) ~[play_2.11-2.4.1.jar:2.4.1]
    at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:93) ~[play_2.11-2.4.1.jar:2.4.1]
    at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21) ~[play_2.11-2.4.1.jar:2.4.1]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1$$anonfun$2.apply(DevServerStart.scala:153) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1$$anonfun$2.apply(DevServerStart.scala:150) ~[play-server_2.11-2.4.1.jar:2.4.1]
    at play.utils.Threads$.withContextClassLoader(Threads.scala:21) ~[play_2.11-2.4.1.jar:2.4.1]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1.apply(DevServerStart.scala:150) ~[play-server_2.11-2.4.1.jar:2.4.1]
    ... 15 common frames omitted

Scala: ファイルの内容を一行ずつ処理したい

java.nio.file.Filesで一行ごとに処理(ただしScala)

Java SE 7のjava.nio.file.Filesがとても便利な件

ファイルの内容を一行ずつ読み込んで処理したい場合に、今まではこちらのページ:(Fileの読み込み - Scala覚書)を参考に実装していましたが、nioを使えば簡単だったので、メモしておきます。

val file = Paths.get(path)
Files.readAllLines(file, Charset.defaultCharset())
     .foreach(println)

Charset.defaultCharset()が鬱陶しいですがそれに目を瞑ればシンプルです。 scala.io.Sourcejava.io.BufferedReadercommons.io.FileUtils だといずれもバッファをクローズする必要があるので、それに比べればだいぶ良いのではないでしょうか。

巨大なファイルのとき

これは、scala.io.Sourceを利用するのがよさそう。構造的部分型を利用したローンパターンを使わなければならないので、そのぶんコードが膨らみますが、そのぶん使用箇所はシンプルにまとまります。

using(Source.fromFile("file.txt")) {
     _.getLines().foreach(println)
}

def using[A <% { def close():Unit }](s: A)(f: A=>Any) {
     try f(s) finally s.close()
}

また、using内でSourceのもつメソッドを十分に活用したい場合は、構造的部分型での一般化を諦める必要があります。
参考:Scala using(ローンパターン)-Hishidama's Scala Memo-

変数に対してString Interpolationを適用する

皆さんお馴染みのString Interpolationですが、すでにある変数に対してString Interpolationを適用したいと思ったのでメモします。

String Interpolation自体は、以下のように、文字列にあとから変数を挿入する機能です。

scala> val x = "interpolation"
scala> println(s"string #{x}")
string interpolation

詳しくはこちら。 http://docs.scala-lang.org/ja/overviews/core/string-interpolation.html## 何がしたいの?

すでにあるstring interpolationを含んだ文字列に、あとからString Interpolationを適用したいということです。何を言っているかわからないと思いますが、概念的にはこういうことです。
```scala> val a = "string ${x}"
scala> val x = "interpolation"
scala> println(s(a))
string interpolation


試しに入れ子にしてみます。

scala> println(s"${a}")
string ${x}
```

ダメですよね。すみません。

無理みたいです

http://stackoverflow.com/questions/13260864/string-interpolation-in-scala-2-10-how-to-interpolate-a-string-variable

「コンパイル時に情報が不足してしまうから」とのことで、納得。

http://kmizu.hatenablog.com/entry/20120505/1336216839