Hatena::ブログ(Diary)

scalaとか・・・ このページをアンテナに追加 RSSフィード

2013-05-20

map も flatMap も yield も使わずに、ScalaのforをMonadのための構文として利用する方法

| 18:56 | map も flatMap も yield も使わずに、ScalaのforをMonadのための構文として利用する方法 - scalaとか・・・ を含むブックマーク 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に実装しても同じです

*1:ここから色々工夫しないと、pointの型書かないといけないとか、そもそもこうやってforeachを定義してもなにもメリットない?とか

*2:そして、foreachは通常ではUnit返すのに、そうではなく foreach == bind == "通常のflatMapのシグネチャ" にしてあるのがポイント

2013-05-17

HaskellのdoとScalaのfor式とEitherとMonadPlus

| 03:07 | HaskellのdoとScalaのfor式とEitherとMonadPlus - scalaとか・・・ を含むブックマーク HaskellのdoとScalaのfor式とEitherとMonadPlus - scalaとか・・・ のブックマークコメント

標題の通り、色々書きたいことあって長くなってわかりにくくなりそうですが、頑張って書いてみます。なお、(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通りあります

  1. for式内でパターマッチをしたとき
  2. 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の場合

  • guardは「doの中で使える特殊な構文ではなく*5単なる関数である」
  • guardを使うには「MonadPlusのインスタンスであることを要求する」

一方Scala

  • 「for式内のifは特別な構文である」
  • 「for式内のif」を使うためには、filterが実装されていることが必須

となっています。Haskellのguardについては、以下のページとか見てください

guard の動作原理を考える


なぜ「for式内のif」が特別な構文として実装されたのか?は、odersky先生に聞くか、調べてみないとわかりません。勝手な予想としては、型推論などの関係から、「構文として実装」してしまったほうが、使いやすくなって都合がよかったのだろうと思います。

たとえば、以下のように(明らかに使いづらいですが)、ifをguard関数として実装するのは不可能ではないです

型クラスでHaskellのguardを定義してみる(未完)



続いて、「for式内でのパターンマッチ」が、Haskellの場合どうなるのか見てみます。

Haskellでは、

  • どんなものでもdoの中でパターンマッチできるが、パターンマッチに失敗した場合にMonadのfail関数*6が呼ばれる

という仕様になっています。


一方Scalaの場合は

  • filterを実装していないと、forでパターンマッチができない(やろうとしても、そもそもコンパイルエラーになる)

という仕様です。

Monadのfail関数を実装しない場合はデフォルトでは例外を投げるので、この点においてはある意味Scalaのほうが型安全といえるかもしれまん。

ただし、適切にfailを定義できればHaskellにおいても安全でしょうけれども。



そして、その「適切にfailを定義」が重要なポイントです。

(あまり多く調べたわけではないので自信がないのですが)

Monadの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にはなりません!

*9

だがそれにも関わらず、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の中以外でも使える

*6http://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:それに微妙に関連する話として、HaskellMonadの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 を使う

| 22:34 | Scala の REPL から、無理やり Java8 を使う - scalaとか・・・ を含むブックマーク Scala の REPL から、無理やり Java8 を使う - scalaとか・・・ のブックマークコメント

まだ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の対応

| 19:19 | java.util.stream.StreamのメソッドとScalaの対応 - scalaとか・・・ を含むブックマーク java.util.stream.StreamのメソッドとScalaの対応 - scalaとか・・・ のブックマークコメント

またJava8の話。

Scalaの場合、ListでもStream*1でもVectorでも同じメソッドあるので、あえてどのclassなのかは明示しません。基本はJavadoc読んで雑に調べただけで、ちゃんと全部実行して試したわけじゃないので、明らかに間違ってたら教えて下さい。

http://download.java.net/lambda/b88/docs/api/java/util/stream/Stream.html

http://hg.openjdk.java.net/lambda/lambda/jdk/file/38379cea9127/src/share/classes/java/util/stream/Stream.java


Scalaメソッド名も機能だいたい同じもの
  • filter
  • map
  • flatMap
  • distinct
  • iterator
    • もちろん、JavaのほうはJavaのIterator返すし、ScalaのほうはScalaの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 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
    • 結局ScalaでのreduceLeftとかfoldLeft的なことがやりたい?でいいのか?
    • 引数が3つのほうは、おそらく効率のために、buffer的なものや、BiConsumer(ScalaだとFunction2[A,B,Unit])などを引数にとって、mutableな操作で結果値を生成する感じ
  • isParallel
    • 型で判別すれば、Scalaでもできる?
  • spliterator
    • Spliterator がなんなのかよくわかってないのであとで
  • <A> A[] toArray(IntFunction<A[]> generator)
    • type erasureの関係で存在かな?(Scalaだったらそういうの勝手にやってくれる)明示的に返すArrayそのものを作成して渡さないといけないらしい。シグネチャや使い方は全然違うが、目的としてはScalaのtoArrayと同じだと思う

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

*1scala.collection.immutable.Streamのこと

*2:無限になるので、IteratorとStreamのみに存在

2013-05-02

Java8 の java.util.function package と Scala の対応表

| 03:51 | Java8 の java.util.function package と Scala の対応表 - scalaとか・・・ を含むブックマーク Java8 の java.util.function package と Scala の対応表 - 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:もう仕様が完全に固まったのか、まだ変更ありえるのか?というような事情知らない