Appトレイトを使って定義したmain関数を並列コレクション操作の中で呼び出すと並行に走らない。
先日のScalaハマリポイント。Appトレイトを使って定義したmain関数を並列コレクション操作の中で呼び出すと並行に走らない。問題自体の解決策は分かったが、何故そのような挙動になるのかは未解決。
あるプログラムに処理結果を吐き出させ、すぐさまその結果を読み込んで使う、という処理を並行動作させるコードを書いた。説明のために簡略化すると、このような感じ。
そうすると、このような実行結果。
% sbt 'run-main ParallelWritingTest' write 1-1.txt write 3-1.txt write 2-1.txt write 4-1.txt read 4-1.txt read 2-1.txt read 3-1.txt read 1-1.txt write 2-2.txt write 1-2.txt write 4-2.txt read 4-2.txt read 2-2.txt read 1-2.txt write 3-2.txt read 3-2.txt write 3-3.txt write 4-3.txt read 3-3.txt read 4-3.txt write 5-1.txt write 5-2.txt read 5-1.txt write 5-3.txt read 5-2.txt read 5-3.txt [error] (run-main-0) java.io.FileNotFoundException: 1-1.txt (No such file or directory) java.io.FileNotFoundException: 1-1.txt (No such file or directory) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.<init>(FileInputStream.java:138) at scala.io.Source$.fromFile(Source.scala:91) : : : [trace] Stack trace suppressed: run last experimental/compile:runMain for the full output. java.lang.RuntimeException: Nonzero exit code: 1 at scala.sys.package$.error(package.scala:27) [trace] Stack trace suppressed: run last experimental/compile:runMain for the full output. [error] (experimental/compile:runMain) Nonzero exit code: 1 [error] Total time: 2 s, completed 2015/05/07 15:51:18
書き出されないファイルがある模様。 出来ているファイルも以下のとおり。
% ls 3-1.txt 3-2.txt 4-2.txt 4-3.txt 5-2.txt 5-3.txt build.sbt lib project resources src target
何故そうなるか分からずしばらく困っていたが、ふと第六感が働いて、App
トレイトをmix-inするのをやめてメインメソッドを直に書いたところ、想定通りの動作をした。よくわからないが、DelayedInitが何かブロッキングが起こるようなことをやっているんだろうか。
% sbt 'run-main ParallelWritingTest' [info] Running ParallelWritingTest write 4-1.txt write 1-1.txt write 3-1.txt write 2-1.txt read 2-1.txt read 4-1.txt read 1-1.txt read 3-1.txt write 4-2.txt read 4-2.txt write 3-2.txt write 2-2.txt write 1-2.txt write 4-3.txt read 2-2.txt read 1-2.txt read 3-2.txt read 4-3.txt write 2-3.txt write 3-3.txt write 1-3.txt read 2-3.txt read 3-3.txt read 1-3.txt write 5-1.txt read 5-1.txt write 5-2.txt read 5-2.txt write 5-3.txt read 5-3.txt ParVector(Vector((4950,0), (4950,1), (4950,2)), Vector((9900,0), (9900,1), (9900,2)), Vector((14850,0), (14850,1), (14850,2)), Vector((19800,0), (19800,1), (19800,2)), Vector((24750,0), (24750,1), (24750,2))) [success] Total time: 1 s, completed May 7, 2015 1:28:39 PM
Sparkの資料
型消去の結果同じ引数型になってしまうメソッドをオーバーロードする その2
以前の話の続き。
DummyImplicit
という型を使って下のような書き方をする作法もあるらしい。
使わない暗黙のパラメータを取ることで引数リストの数を区別しようというもの。
ちなみに、DummyImplicit
はscala.Predef
の中で
class DummyImplicit object DummyImplicit { implicit def dummyImplicit: DummyImplicit = new DummyImplicit }
とされているので、何もしなくてもこの暗黙のパラメータは解消される。 ダミーの暗黙のパラメータを使って区別する場合はこれを使うのがよい作法に思える。
implicit i1: DummyImplicit
とかいちいち書くのが面倒くさければ、
ClassTagを使って少し短く書ける。
どちらにしても相当気持ち悪い見た目をしていることには変わりない。 前回の方法とどちらを好むかは趣味がわかれるところ。
参考文献: 1. stackoverflow.com
Arrayをprintしたい時は deep
ScalaのArrayは前回の問題のせいで、toStringで要素の値を文字列に出来ないが、WrappedArrayというラップ型に変換すれば中身も文字列化される。このため、適当にtoSeq
などして表示するわけだが、WrappedArrayという文字列が長くてイライラするし、多次元配列等の場合はめんどくさい。そこで、.deep
とすれば、多次元配列を再帰的にWrappedArrayに変換してくれる上に、"WrappedArray"
という型を表す長々としたプレフィックス文字列を"Array"
で上書きしてくれる。
こういうの欲しかったんです。ありがたい。
でもSeq[Seq[Array[Int]]]
みたいなときには結局困るので、もうつまりArrayは使うなということかもしれない…。
sbtで使われているclasspathを取り出す
以下のワンライナーでOK
sbt "export compile:dependency-classpath" | tail -1 | scala -e 'import scala.io.StdIn;StdIn.readLine().split(":") foreach println'
型パラメータAを含むSeq[A]をtoArrayするときには実行時型が必要
昨日のScalaハマリポイント: 型パラメータAを含むSeq[A]をtoArrayするときには実行時型が必要
例えば、次のようなSchool
クラスがあったとする。
このクラスはStudent
もしくはその子クラスのコレクションをメンバに持っている。このクラス設計が嬉しいのは、コレクションの要素型がパラメータになっているおかげで、もし全ての生徒が男子であればstudents
はSeq[Boy]
となり、このように作られたboysSchool
に対してはBoy
だけがもつメンバfightingInstinct
にアクセスできることだ。
ただし、このようなクラス設計をした時にちょっとハマってしまうのが、
School
クラスの内部でSeq[T]
をArray
に変換しようとしたときに、以下のようにコンパイラに怒られることだ。
Error: No ClassTag available for T def studentsAsArray = students.toArray ^ Error: not enough arguments for method toArray: (implicit evidence$1: scala.reflect.ClassTag[T])Array[T]. Unspecified value parameter evidence$1. def studentsAsArray = students.toArray ^
一方で、ShoolTest
でboysSchool.students.toArray
とするぶんには問題ない。
このような現象に初めて出会うと、何のことだかさっぱりだ。
何故このようなことが起こるのかというと、ScalaのArrayは実際のところJavaのArrayであり、
JavaのArrayはプリミティブ型に対して最適化された個別のクラスを用意していて、それ以外の参照型に対するArrayとクラスが違うからである。
つまり、Arrayを作るときにこれらのクラスを作り分けるために型T
がプリミティブ型か参照型かを知る必要があるわけだが、
コンパイル時の型消去のために、School
クラスの内部ではT
が参照型([T <: School]
)であることを知ることができない。
そのため、一体どのArray
を作ればいいかわからない状態に陥ってしまう。
この問題を回避するために、JavaやScalaには実行時に型情報を取得する実行時リフレクションという機構がある。 Scala 2.11では、ClassTag という オブジェクトを使ってコンパイル時に消去されるクラス型を保持しておき、実行時に取得することができる。
上記の問題の場合、以下のように書くことでTのクラス型を保持し、実行時に知ることができるため、無事参照型のArrayを作ることできるということになる。
ClassTagの詳細については、ClassTagトレイトのソースコードも参考になる。
参考:
ScalaのSeqやMapは関数である
Array
に関数オブジェクトを突っ込んでいるときに、「Arrayだって数学的には写像なんだから、Array[A => B]
をInt => A => B
だとみなせないのかな?」と思っていたら、ScalaのSeq
がPertialFunction
トレイトを継承していることを発見。ちゃんと実現されていた。
つまり、こういうことができる。
そもそも、Array
の中に関数オブジェクトを入れたものに対して添字アクセスしていても、
見た目はカリー化された関数と変わらない。
こういうのを見ると、Scalaの添字アクセスが丸括弧なことに一層の美しさを感じる。
関数だとみなせるということは、mapメソッドに渡す事もできる。
Seqは原理上インデックスしか定義域に取れないのでPertialFunction[Int, A]
を継承している。
一方、Mapは定義域の型が自由なのでPertialFunction[A, B]
を継承しており、
下のような感じで扱える。
参考: