2013-05-20
map も flatMap も yield も使わずに、ScalaのforをMonadのための構文として利用する方法
scala | |
![]()
注意: 遊んでみただけなので実用的ではない*1だろうし、初心者は混乱するかもしれないのであまり真面目に見ないほうがいいかもしれません
forでyield使わなくても、foreachが値を返す場合、for全体も値を返すのですね・・・
https://gist.github.com/xuwei-k/5611368
「pointとbindさえ実装すればMonadだよ!」*2というのがわかりやすいように、わざとtrait作りましたが、べつにforeachを直接MaybeやConsListに実装しても同じです
2013-05-17
HaskellのdoとScalaのfor式とEitherとMonadPlus
標題の通り、色々書きたいことあって長くなってわかりにくくなりそうですが、頑張って書いてみます。なお、(2.8や2.9でもほぼ同じだと思いますが)Scalaのversionは2.10.1です。Haskellはghc7.4.2です。
「for文は7つしか使っていません(ドヤッ」という謎の主張を含んだスライドが最近流行っていましたが、まずScalaのfor式はMonadのための構文というのはお馴染みですよね!!!
Monadそのものから説明していたらとても長くなってしまうので、そのあたりの説明は飛ばします。Scalaのfor式は、コンパイル時に内部的に以下のメソッドの組み合わせに変換されます
- map
- foreach
- flatMap
- filter
- withFilter
なので、上記の5つのメソッド名は、予約語ではないですが、for式に変換されるという点において特別です。
for式を「Monadのための構文」として捉えた場合、関係あるのはflatMapとmapですね。
foreachは、「yieldを使わず値を返さない場合」なので、多重ループといった感じです。これ以降foreachについては触れません。
で、残った2つのメソッドである「for式におけるfilterとwithFilterとは、そもそもなんなのか?」というのを深く掘り下げて、勝手に解釈してみるのがこの記事のテーマです。
ところで、withFilterのほうは、*1本質的にfilterと同じなので、これ以降はwithFilterとfilterの違いは考えないことにします。*2
Scalaのfor式でfilterが使われるのは、以下の2通りあります
- for式内でパターマッチをしたとき
- for式内で、ifを使った場合
for式内でパターンマッチとは、たとえば以下のようなコードです
for{ Some(a) <- List(Some(1), None) } yield a
これは、だいたい以下のように展開されます*3
List(Some(1), None).withFilter{ case Some(a) => true case _ => false }.map{ case Some(a) => a }
2の「ifを使った場合」というのは、以下のようなコードが
for{ a <- List(Some(1), None) if a.isDefined }yield a
こんな感じに変換されます
List(Some(1), scala.None).withFilter(_.isDefined).map(a => a)
さて、ここで結論から先に言うと、filterはMonadとは関係ないという話です。たとえば、filterもwithFilterも定義されていない、MyOptionやMyListのデータ型を適当に作って、それをMonad則を満たすように定義することは可能です。
そしてfilterは、Monadではなく、MonadPlusに対応します!*4
この後、まだまだ言いたいことがたくさんあって、どこから説明すればいいのか悩むのですが、一旦Haskellのdoを見てみましょう。
「Scalaのfor式内でのif」に対応するものが、Haskellでは「guardという関数」です。たとえば、先程の例をHaskellに訳すと以下のようになります
import Control.Monad import Data.Maybe do a <- [Just 1, Nothing] guard(isJust a) return a
まず「Haskellのguard」と「Scalaのfor式内のif」で重要な違いがあります。Haskellの場合
一方Scalaは
- 「for式内のifは特別な構文である」
- 「for式内のif」を使うためには、filterが実装されていることが必須
となっています。Haskellのguardについては、以下のページとか見てください
なぜ「for式内のif」が特別な構文として実装されたのか?は、odersky先生に聞くか、調べてみないとわかりません。勝手な予想としては、型推論などの関係から、「構文として実装」してしまったほうが、使いやすくなって都合がよかったのだろうと思います。
たとえば、以下のように(明らかに使いづらいですが)、ifをguard関数として実装するのは不可能ではないです
続いて、「for式内でのパターンマッチ」が、Haskellの場合どうなるのか見てみます。
Haskellでは、
という仕様になっています。
一方Scalaの場合は
- filterを実装していないと、forでパターンマッチができない(やろうとしても、そもそもコンパイルエラーになる)
という仕様です。
Monadのfail関数を実装しない場合はデフォルトでは例外を投げるので、この点においてはある意味Scalaのほうが型安全といえるかもしれまん。
ただし、適切にfailを定義できればHaskellにおいても安全でしょうけれども。
そして、その「適切にfailを定義」が重要なポイントです。
(あまり多く調べたわけではないので自信がないのですが)
「もしもMonadPlusならば、failでmzeroを返すように定義するべきではないか?」
ということです。たとえば実際に、ListのMonadのfailではNilを返しているし、MaybeではNothingを返しています。
このあたり、(Haskellの一般的な慣習として?)failをどのように定義するのか、Haskell詳しい人教えてくれるとありがたいです。もしくは、「そもそもdoでのパターンマッチは失敗する可能性あって安全じゃないからあまり使わないよ!」とか
やっとMonadPlusの話に戻ってこれて、
"MonadPlusと「Scalaのfor式内でのif」と「for式内でのパターンマッチ」がなんとなく関連する"
ことがわかってきました。*7
ここでまた話が飛びますが、たとえばScalazにおいて、filterがMonadPlusの関数として定義されています。*8
https://github.com/scalaz/scalaz/blob/v7.0.0/core/src/main/scala/scalaz/MonadPlus.scala#L16
Scalazのfilterの定義をみてもらうとわかるとおもいますが、filterを定義するには、bindとmzero両方が必要なので、MonadではなくMonadPlusでなければいけまん。
ここからやっと確信というか、本当に言いたいことに迫ってきますが、
「では、MonadPlusにならない場合に、filterをどうするか?」
ということです。
例えば、通常EitherはMonadPlusにはなりません!
だがそれにも関わらず、Scala標準のEither*10には、以下のようなfilterが定義されています
https://github.com/scala/scala/blob/v2.10.1/src/library/scala/util/Either.scala#L391
def filter[Y](p: A => Boolean): Option[Either[A, Y]]
このfilterが使いづらいと思ったことはありませんか?
まず、シグネチャがおかしいですよね?Option[A]をfilterした場合には、Option[A]型が再び返ってきますが、Either*11をfilterすると、Option[ Either[A, Y] ]と、Optionに包まれて返ってきます。*12
随分長い間
「Scala標準のEitherのfilter使いづらいなー。(特にfor式でパターンマッチやifを使う場合) でも、じゃあどのように定義するのが適切なのだろう」
という疑問がありました。それで
「filter関数をfor式内で使うためのものとして捉えた場合に、filterはMonadPlusと関連しているので、MonadPlusのlawを満たすように定義するべき」
という立場にたつと
「EitherはMonadPlusになること自体が不可能なので、適切な使いやすいfilterを定義することもそもそも不可能」
という結論に達しました。*13
ただ、これで終わりではなくて、まだ話が続きます。本当に
「filterはMonadPlusと関連しているので、MonadPlusのlawを満たすように定義するべき」
という点が、必ずしも絶対的な条件ではなく、議論が分かれるところだからです。
たとえば、scala標準のscala.util.Tryや、ScalazのValidationなども、MonadPlusの条件は満たしませんが、filterメソッドが存在しています。*14
そして、ScalazのEitherのfilterもMonadPlusの条件を満たさないのですが、以下のように面白い定義になっていて、for式内で(少なくともScala標準のEitherのfilterよりは)ある意味便利に使えるようになっています。*15
https://github.com/scalaz/scalaz/blob/v7.0.0/core/src/main/scala/scalaz/Either.scala#L122
def filter[AA >: A](p: B => Boolean)(implicit M: Monoid[AA])
Scalazの場合、Scala標準のものとは違い、filterした後の型がOptionに包まれているのではなく、(ほぼ)同じ型を返すようになっています。
MonadPlusを満たさなくても*16、for式で便利に使えるので、これを許容するのかどうかが、意見が分かれるところだと思います。
そして、ScalazのMLで、EitherではなくEitherTですが、filterとwithFilterの定義について議論になっていました
EitherT.filter and filterWith are evil, stupid, and wrong.
個人的には、EitherTのfilterはこのまま存在してもいい気もしますが、果たしてどうなるんでしょうか。
というわけで、だいぶ長くなりましたが、「HaskellのdoとScalaのfor式とEitherとMonadPlus」の関係について説明してみました。
*1:主にパフォーマンスの関係から導入されただけで
*2:withFilterに変換される場合も、たんにfilterと言ったりします
*3:-Xprint:typerとかで出力してみると、たぶん細かいところ違いますが、議論の本質に関係ないので省略して書きました。気にしないでください
*4:対応しますと完全に言い切ると語弊があるのですが。そもそもこのあたりが、今回最終的に言いたいところなので
*5:doの中以外でも使える
*6: http://www.haskell.org/ghc/docs/latest/html/libraries/base/Control-Monad.html#t:Monad
*7:微妙に話の流れが強引な気がするが・・・説明難しい・・・
*8:この関数の名前がfilterなのは、for式で使うことを意図しているはずです
*9:ものすごく無理やりな定義をすれば、不可能ではないかもしれませんが、そんな定義をしたとしても、有用な定義になはならなくて使えないでしょう
*10:のRightProjectionもしくはLeftProjection
*11:のRightProjectionもしくはLeftProjection
*12:それに微妙に関連する話として、HaskellのMonadのfail関数も、同じ型を返さなければいけないように制限されています
*13:たとえばHaskellのEitherもMonadPlusにはならないので「Monadのfail関数の適切な定義」が意見が分かれる?ので、そもそも定義されていません。Eitherに対してdoの中でパターンマッチに失敗すると例外が発生します
*14:他には、json4s(lift-json)のfilterもたしかそうだったはずです。
*15: Scala2.10.1がバグってるのか、こんな現象が起きますが https://twitter.com/xuwei_k/status/335480823438528513
*16:implicit に Monoidを引数にとったからといって、どちらにしろScalazもMonadPlusのlawは満たさない
2013-05-07
Scala の REPL から、無理やり Java8 を使う
まだScala側がほとんど対応してなくて、本当に無理やりです。classfile読み込み時にエラー何度もでるし、AbstractMethodErrorなどもいっぱいでるので、まだ全然実用的じゃないです。でもちょっとだけ動きました。
Scala2.10.1でも、 2.11.0のSNAPSHOT でも同じでした。使ったjava8のversionは、ここからダウンロードした lambda-8-b88-linux-x64-29_apr_2013 です。
$ scala Welcome to Scala version 2.10.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0-ea). Type in expressions to have them evaluated. Type :help for more information. scala> [init] error: error while loading CharSequence, class file '/home/kenji/jdk8/lambda-8-b88-linux-x64-29_apr_2013/jre/lib/rt.jar(java/lang/CharSequence.class)' is broken (class java.lang.RuntimeException/bad constant pool tag 18 at byte 10) // とりあえず最初に"bad constant pool tag"のエラーでる scala> import java.util.function._ import java.util.function._ scala> implicit def ToBinaryOperator[A](self: (A, A) => A) = new BinaryOperator[A]{def apply(a:A,b:A):A = self(a,b) } error: error while loading BiFunction, class file '/home/kenji/jdk8/lambda-8-b88-linux-x64-29_apr_2013/jre/lib/rt.jar(java/util/function/BiFunction.class)' is broken (class java.lang.RuntimeException/bad constant pool tag 18 at byte 15) <console>:10: error: Parameter type in structural refinement may not refer to an abstract type defined outside that refinement implicit def ToBinaryOperator[A](self: (A, A) => A) = new BinaryOperator[A]{def apply(a:A,b:A):A = self(a,b) } ^ <console>:10: error: Parameter type in structural refinement may not refer to an abstract type defined outside that refinement implicit def ToBinaryOperator[A](self: (A, A) => A) = new BinaryOperator[A]{def apply(a:A,b:A):A = self(a,b) } ^ // まだ対応してないので、よくわからないエラー・・・ scala> implicit def ToBinaryOperator[A](self: (A, A) => A) = new BinaryOperator[A]{def apply(a:Any,b:Any):A = self(a.asInstanceOf[A],b.asInstanceOf[A]) } ??? base anonymous class $anon not found in basetypes of anonymous class $anon ??? base trait BinaryOperator not found in basetypes of anonymous class $anon ??? base <none> not found in basetypes of anonymous class $anon warning: there were 1 feature warning(s); re-run with -feature for details ToBinaryOperator: [A](self: (A, A) => A)java.util.function.BinaryOperator[A]{def apply(a: Any,b: Any): A} // 相変わらずエラーでてるけど、無理やりキャスト使って定義したら、定義できた!? scala> java.util.stream.Stream.of(1,2,3,4,5,6,7,8,9,10).reduce(0, (a: Int, b: Int) => a + b) res0: Int = 55 // Scala の Function2型から、Java の lambda に暗黙変換できて、正しく実行できた!?
2013-05-06
java.util.stream.StreamのメソッドとScalaの対応
またJava8の話。
Scalaの場合、ListでもStream*1でもVectorでも同じメソッドあるので、あえてどのclassなのかは明示しません。基本はJavadoc読んで雑に調べただけで、ちゃんと全部実行して試したわけじゃないので、明らかに間違ってたら教えて下さい。
http://download.java.net/lambda/b88/docs/api/java/util/stream/Stream.html
Scalaとメソッド名も機能だいたい同じもの
- filter
- map
- flatMap
- distinct
- iterator
- toArray
- Javaのほうの引数なしの方は、戻り値型がObject[]で残念
- min
- JavaのほうはOptionalを返すので、Scalaより型安全!?。Comparatorを明示的に渡さないといけない
- max
- minと同様
- count
- Javaのほうはintではなくlong返すらしい
- Stream<T> sorted()
- TがComparableじゃない場合、ClassCastExceptionを投げるの・・・
- Stream<T> sorted(Comparator super T> comparator)
- Comparatorを明示的にわたすversion
Scalaに直接対応するものが存在しない(?)もの
- unordered
- peek
- 副作用専用メソッド?
- forEachOrdered
- 順序に関する考え方がScalaと違うというか、java.util.stream.Streamは最初からpipeline的な処理をするため専用に作られてるので、たんにsortしてからforeach呼ぶのとも違うみたい?・・・(つまりよくわかってない)
- noneMatch
- anyMatch(scalaだとexists)の逆?
- <U> U reduce(U identity,BiFunction<U,? super T,U> accumulator,BinaryOperator<U> combiner)
- 多分ない・・・はず。ただ最終目的としては、ほかのreduceと同様に、Scalaのreduce、reduceOptionもしくはfoldと同じみたい。使い方よくわかってないのであとで気が向いたら調べる
- collect
- isParallel
- 型で判別すれば、Scalaでもできる?
- spliterator
- Spliterator がなんなのかよくわかってないのであとで
- <A> A[] toArray(IntFunction<A[]> generator)
IntとLongとDoubleの場合それぞれあるもの
- mapToInt
- mapToLong
- mapToDouble
- flatMapToInt
- flatMapToLong
- flatMapToDouble
メソッド名が異なる(けど機能はほぼ同じ)もの
| Java8 | Scala | 補足コメント |
|---|---|---|
| limit | take | |
| sequential | seq | |
| parallel | par | |
| Stream<T> substream(long startInclusive) | drop | |
| Stream<T> substream(long startInclusive, long endExclusive) | slice | |
| forEach | foreach | |
| T reduce(T identity,BinaryOperator<T> accumulator) | fold | |
| Optional<T> reduce(BinaryOperator<T> accumulator) | reduceOption | |
| anyMatch | exists | |
| allMatch | forall | |
| findFirst | headOption | sortの考え方がちがうみたいなので、概念的に本当に直接対応するのかよくわかってない |
| findAny | headOption | findFirstと型は全く同じ。気が向いたらfindFirstとfindAnyの違いしらべる・・・ |
static メソッド
- builder
- Scalaに対応するものがあるといえばあるが・・・
- empty
- コンパニオンオブジェクトに同じ名前で存在
- of
- iterate
- コンパニオンオブジェクトに同じ名前で存在
- generate
あと、ListやSetやMapに変換とか、maxByやminByなどは、CollectorsというオブジェクトのstaticメソッドとしてCollector型で用意してあるみたいですね
http://download.java.net/lambda/b88/docs/api/java/util/stream/Collectors.html
2013-05-02
Java8 の java.util.function package と Scala の対応表
現状*1 java.util.function packageには43個interfaceがあるようです。
http://download.java.net/jdk8/docs/api/java/util/function/package-summary.html
とりあえず対応表だけ作ったら力尽きたので、後で気が向いたときに、java.util.functionの残念なところと、Scalaのspecialized annotationについて書く・・・
| Java | Scala |
|---|---|
| BiConsumer<T, U> | Function2[A, B, Unit] |
| BiFunction<T, U, R> | Function2[A, B, C] |
| BinaryOperator<T> | Function2[A, A, A] |
| BiPredicate<T, U> | Function2[A, B, Boolean] |
| BooleanSupplier | Function0[Boolean] |
| Consumer<T> | Function1[A, Unit] |
| DoubleBinaryOperator | Function2[Double, Double, Double] |
| DoubleConsumer | Function1[Double, Unit] |
| DoubleFunction<R> | Function1[Double, A] |
| DoublePredicate | Function1[Double, Boolean] |
| DoubleSupplier | Function0[Double] |
| DoubleToIntFunction | Function1[Double, Int] |
| DoubleToLongFunction | Function1[Double, Long] |
| DoubleUnaryOperator | Function1[Double, Double] |
| Function<T, R> | Function1[A, B] |
| IntBinaryOperator | Function2[Int, Int, Int] |
| IntConsumer | Function1[Int, Unit] |
| IntFunction<R> | Function1[Int, A] |
| IntPredicate | Function1[Int, Boolean] |
| IntSupplier | Function0[Int] |
| IntToDoubleFunction | Function1[Int, Double] |
| IntToLongFunction | Function1[Int, Long] |
| IntUnaryOperator | Function1[Int, Int] |
| LongBinaryOperator | Function2[Long, Long, Long] |
| LongConsumer | Function1[Long, Unit] |
| LongFunction<R> | Function1[Long, A] |
| LongPredicate | Function1[Long, Boolean] |
| LongSupplier | Function0[Long] |
| LongToDoubleFunction | Function1[Long, Double] |
| LongToIntFunction | Function1[Long, Int] |
| LongUnaryOperator | Function1[Long, Long] |
| ObjDoubleConsumer<T> | Function2[T, Double, Unit] |
| ObjIntConsumer<T> | Function2[T, Int, Unit] |
| ObjLongConsumer<T> | Function2[T, Long, Unit] |
| Predicate<T> | Function1[A, Boolean] |
| Supplier<T> | Function0[A] |
| ToDoubleBiFunction<T, U> | Function2[A, B, Double] |
| ToDoubleFunction<T> | Function1[A, Double] |
| ToIntBiFunction<T, U> | Function2[A, B, Int] |
| ToIntFunction<T> | Function1[A, Int] |
| ToLongBiFunction<T, U> | Function2[A, B, Long] |
| ToLongFunction<T> | Function1[A, Long] |
| UnaryOperator<T> | Function1[A, A] |
*1:もう仕様が完全に固まったのか、まだ変更ありえるのか?というような事情知らない

