異なるスコープにおいて関数定義を有効化したり無効化したりできる この最後の点によって Scala のアドホック多相性は Haskell のそれよりもより強力なものだと言える。
これが一概に良いとは言えないのが辛いところ。より強い機能がより良いとは限らない。それは同時に複雑性ももたらす。
Nick さんはやらなかったけど、この形の暗黙のパラメータは context bound で書かれることが多い
僕はどちらかというと明示的に引数へ列挙するほうが好きかな。 context boundだと引数と言うか、前提条件が引数に列挙されないので使いづらいと感じるときがある。
この関数に直接異なるモノイドを渡すこともできます。例えば、Int の積算のモノイドのインスタンスを提供してみましょう。
これができるのはScalaのアドホック多相性の利点の一つだよね。 Haskellだと1つの型の特定の型クラスのインスタンスは1つしか定義できなかったので、 newtypeキーワードなどで元の型を別のものとして定義する必要があった。
これらの型クラスの全ては必要な関数だけを含んだ部品に分けられています。ある関数が必要十分なものだけを要請するため究極のダック・タイピングだと言うこともできるでしょう。
これ今なら意味がわかる。また、たしかにダックタイピング的ではあるんだけど本質的にはその実装される関数が横断的に実装されることでモナドなど特定の利用のしかたができることが大きなメリットであるので、単なるダックタイピングではない。
独習 Scalaz — メソッド注入 (enrich my library)
ん、これはHaskellではなかったような。いや実際には型クラス制約のついた新しい型クラスの定義と中置記法に過ぎないか。でもなんだかScalaのほうがややこしいように見えるなあ。長さ・記述の簡潔性のためかな。
scala> (1 > 10)? 1 | 2 res3: Int = 2
scala> if (1 > 10) 1 else 2 res4: Int = 2
なんじゃあこりゃあ!!! これはすごい。三項演算子を言語機能ではなくメソッド注入で実装しているのか。面白い。
書かれているscalazのバージョンが7.1、2019/02/23現在の安定版最新リリースが7.2.27。マイナーレベルの差だからそれほど記述の違いはないと思う。
この辺で脱線してこれを読む。なるほどよくわかる。
Scalaz で Eq 型クラスと同じものは Equal と呼ばれている:
Haskellの各種ネーミングの微妙な略し方は僕も悪手だと思うので、この点はScalaのほうが良い。
scala> "hello".println
超クール。あとはScala(Java)からtoStringを引っぺがすことができたらいいんだがなあ。
これは対応する Scalaz での型クラスを見つけることができなかった。
へー意外。
なるほど、これはいいな。既存の型のenumメソッドを利用するのもいいが、自分が列挙型を定義した時に型クラスを実装してこのインターフェイスを提供すると便利そう。
これobject xxx extends構文使えないの辛いな。
たまにimplicit引数の変数名で見たevidence$1とかのやつ、どういった背景でそう書いているのか知らなかかったんだけどcontext boundで書くと自動的にscala compilerがそう名付けるのね。
またしても不変な型パラメータのせいで Nil を特殊扱いしなくてはいけない。
ムムム。
Scalaz は Tuple などにも Functor のインスタンスを定義している。
へー。各要素に対するmapなのかな?共通する親クラスまで遡って適用するんだろうか。
この演算は Tuple の最後の値のみに適用されていることに注意。詳細は scalaz group での議論を参照。
ダニー!? いや、でもこれでもファンクタ則を満たすのか。なるほど・・・。
順番が完全に違っている。ここでの map は F[A] に注入されたメソッドのため、投射される側のデータ構造が最初に来て、次に関数が来る。List で考えると分かりやすい:
うーん、これは少しややこしい。たぶんHaskellが右から左に読ませたいのに対してScalaは左から右に読ませたいためだろうと思う。これはHaskellの流儀のほうが良い気がするなあ。
pureの代わりにpoint
ここら辺はつい最近読んだすごいHaskellまんまなので割愛。
type Function1Int[A] = ({type l[A]=Function1[Int, A]})#l[A]
なんじゃこりゃあ。よくわからん。いわゆるtagged type?
scala> :k Equal scalaz.Equal's kind is F[A]
お、意図通り動いている。
( -> ) -> *の型はScalaではX[F[A]] なーるほどー。型の表記がScalaだと->なのに対してScalaでは[]だからこうなるのか。
List そのものは F[+A] なので、F が函手に関連すると覚えるのは簡単だ。しかし、型クラス定義の Functor は F[A] を囲む必要があるので、カインドは X[F[A]] となっている。さらにこれを混乱させるのが、Scala から型コンストラクタを第一級変数として扱えることが目新しかったため、コンパイラは 1階カインド型でも「高カインド型」と呼んでいることだ:
あーくそ、これは厄介。 X[F[A]]がHaskellでいう高カインド型なのか。道理で高カインド型の認識がHaskellとScalaで違うと思った。
あああ、いいっすね。
あー、newtype、というよりもtypeにお互いの代入性・可換性をなくしたものがtagged typeなのか(あってる?)。 これほしかったやつじゃーん。
これは newtype そっくりだけど、Int @@ KiloGram など定義できるからより強力だと言える。
うーん、強力なことは支持するけどそれが利点になっているかというと以下略
Semigroup
半群だ。
def zero: A
これうっかりmutable型のvalにしないよう注意
Scalaz 7 でこれらはそれぞれ Boolean @@ Tags.Disjunction、Boolean @@ Tags.Conjunction と呼ばれている。
んん〜?組み込みで||と&&用のモノイド定義が用意されているということ?
scalaz/Tags.scala at 0b75d6c1858d137da5a551d3316f9257786a5726 · scalaz/scalaz
コード読んでもイマイチピンとこねーな。
これらの法則は Functor の実装者が従うべき法則で、コンパイラはチェックしてくれない。Scalaz 7 にはコードでこれを記述した FunctorLaw trait が入っている:
scala> functor.laws[List].check + functor.identity: OK, passed 100 tests. + functor.associative: OK, passed 100 tests.
へーほー。ランダムにインスタンスを生成してファンクタ則を破るパターンがないかテストしているんだろうなあ。
なるほど、アプリ化ティブファンクタ則、モノイド則、半群則もそれぞれテストできると。 言語的にテストできないことを実際に動かしてテストすることは、完全にそのLawに沿っていることは証明できないもののまあ安心程度にはなるかな。
ところで本当に証明できないんだろうか?
独習 Scalaz — Monoid としての Option
Haskell は newtype を使って First 型コンストラクタを実装している。Scalaz 7 は強力な Tagged type を使っている:
なるほど。ここはScalazのアプローチのほうがスマートに感じる、、、が、本質的には同じか?
trait Monad[F[_]] extends Applicative[F] with Bind[F] { self =>
モナドの(>>=) :: m a -> (a -> m b) -> m b
の部分がBindとして抽出されているらしい。
Haskell と違って Monad[F[]] は Applicative[F[]] を継承するため、return と pure と名前が異なるという問題が生じていない。両者とも point だ。
なるほど、これは後発の強み。
ここら辺で気づいたけど、Haskellの型クラスは横断的に導入されるのでScalaのImplicitを使った型クラスの欠点である横断的でなくスコープがわかりづらい(スコープという概念を考慮せざるを得ない)という欠点がない一方、異なる型クラスで同じ関数名を導入できないため(Haskellはモジュールには別名を与えることができるが関数には別名を与えることができない?っぽいため。あってる?)、pureとreturnを別々の名前にせざるを得ないというような欠点があるのだな。 Haskellの流儀が必ず良いというわけでもない、ということか。
OO の方が見栄えが良いと思う:
たしかに。ここはOOのほうが理解しやすい気がする。
ところで、この綱渡りのコード例はモナドの強力さを表す至高のコード例だと思う。
突然型推論が崩れてしまった。問題の原因はおそらく演算子の優先順位にある。 Programming in Scala 曰く:
うーんー、この演算子の優先順位の問題は結構深刻と言うか、Scalaの言語的な大きな縛りとなっている。 たしかに十分agreeableな優先順位を言語仕様とするのも一つの手だが、このように微妙にそれを外れたいとき辛くなる。Haskellはたしか演算子の優先順位をコードで変えられたっけ?
失敗したパターンマッチングは None を返している。これは for 構文の興味深い一面で、今まで考えたことがなかったが、言われるとなるほどと思う。
実はここ、僕は長年すごく腑に落ちていなかった。こういう背景があるからNoneが返って空Listが返っていたんだなあ。
モナディックな視点に立つと、List というコンテキストは複数の解がありうる数学的な値を表す。
(あとでもちろん説明が入っていると思うけど)、このListは複数の解の可能性を表すというの、あくまでListのモナドインスタンスの解釈の一つであって、じっさいすごいHaskell本ではZip方式のListのモナド実装の例も出てくる。 このListのモナド実装も昔からずっと疑問だったんだけど(それListの性質と直接関係なくない?と思っていた)、それもすごいHaskellのおかげで疑問が氷解した。
Zip方式もいいけど、たぶん最初の要素しか見ないようなOptionライクなモナド実装もモナド則を満たせるんじゃないかな。Identityを満たさないかな?(未検証)
ペアに型エイリアスと付けるかわりにまた case class にしよう:
ケースクラスとtagged type( + tuple)は本質的に同じものな気がするけどcase classを使ったほうがよいのかなあ。sbt coding standardにもあるようにcase classは余計なメソッドを大量に導入するのでなるべく使いたくない・・・。
Haskell の do 記法と Scala の for 構文には微妙な違いがある。以下が do 表記の例: これは Scala では moveKnight second の値を抽出して yield で再包装せずには書くことができない。
これなー。scalaのfor文の欠点。最後の行が冗長になってしまうんだよな。これはよくない。
独習 Scalaz — Writer? 中の人なんていません!
(set, tellなどwriterのメソッドについて) 上のメソッドは全ての型に注入されるため、以下のように Writer を作ることができる:
おー、これは便利、というか自然に使えるなあ。
val addStuff: Int => Int = for { a <- (_: Int) * 2 b <- (_: Int) + 10 } yield a + b
んんん?この構文さっぱりわからない。なんでこんな書き方ができるんだ? scaalzのコード見てもReaderモナドの実体らしきものは見当たらないし(実際HaskellでもReaderモナドの実体は関数だからScalazでもなくても不思議ではない)、どうなってるんだろう。
Scala 標準ライブラリの Either 型はそれ単体ではモナドではないため、Scalaz を使っても使わなくても flatMap メソッドを実装しない:
読んでて思ったけど、標準のEither型に対してもモナド型クラスインスタンスを提供すればモナド関数は使えるのでは?
scalaz/Monad.scala at series/7.3.x · scalaz/scalaz
やっぱり行けそうな気がする。
ここで NonEmptyList (略して Nel) が登場する:
これ毎回名前を忘れて検索するのでコンスタントに使って脳細胞に定着させたい。
Scalaz では filterM はいくつかの箇所で実装されている。
ん?なんでだ?1度書けば十分なのにそれぞれで書く意味がちょっとわからない。
6日目のリーダーの例題は以下のように書き換えることができる:
なるほど、これなら理解できる。
Scalaz には Kleisli と呼ばれる A => M[B] という型の関数に対する特殊なラッパーがある:
クライスリってなんだかわかってなかったんだけど>>=でモナドに食わせる関数の型のことか。 Scala的にはモナドのflatMapで食わせる型と言うべきか。
Scala の case class の等価性はヒープ内の位置じゃなくて内容で決まる。そのため、木構造内の複数のノードを識別するだけでももし偶然同じ型と内容があれば Scala は同じもの扱いしてしまう。
何を言っているのかよくわからない。 関数型(immutable)で値が偶然同じだったらそれは同じ値でいいのでは?
getChild がちょっと変わっているのが 1-base の添字を使っていることだ。
確かに。これはあまり良くないんでは?
木の中身を検証するのに Tree の draw があるみたいだけど、改行を入れても入れなくても変な表示になった。
drawはprotected/privateに変更された?
Id[X]型で提供されているnullチェックの??、C#でもよくにたものを見た気がする。
英語がわからなくて無法者である理由がよくわからなかった。Law = モナド則であり、モナド則がないということか?
scalaz/Index.scala at series/7.1.x · scalaz/scalaz
@deprecated("Index is deprecated, use Foldable#index instead", "7.1")
なんとなくだけど、別のクラス・メソッドと機能が重複したりしていることが理由なのかな。
Pointed は有用な法則を持たないし、皆が教えてくれる利用方法のほとんどが実際にはインスタンスそのものが提供している関係を ad hoc に乱用したものであることが多い。
なるほど?ここでいう法則とは型クラスのインスタンスが満たすべき法則でモナド則やモノイド則もその一例であるものかな。 ここのPointedの主張は依然よくわからないんだけど。Pointedってhaskellのreturnやpureと同じものだよね?
きたー。これを理解したくて独習Scalazを再入門していたのだ。
ここで一旦休憩。
Reader のポイントはコンフィギュレーション情報を一度渡せばあとは明示的に渡して回さなくても皆が使うことができることにある。
ここの意味がなんともわからなくて調べ回ったが、これは例が悪い。
上のコード例で言うとReader[String, String]
の左のStringがConfigurationに当たる。つまりその後のforの中でmyNameを何度も読んでいるのは微妙。
Readerモナドを何度もチェーンしてもOK、最初の引数(変数)を何度も引き回さなくてもOKというのが本質っぽい。
型だけでReaderモナドを理解しようと試みる - IT練習ノート
これが参考になった。
ちゃんと見ていなかった。1引数のメソッドをモナド化して
scala> localExample("Fred")
で設定を流し込んでいるのでこの例はやっぱり適切だった。
def applyA, B: ReaderTOption[A, B] = kleisli(f)
んー?なんでこれKleisli[Option, A, B]
からReaderTOption[A,B]
、つまりReaderT[Option, A, B]
に変換できているんだ。
KleisliとReaderTは可換?
いや、ReaderはKleisliの特殊系であることを考えるとこの場合は自然か。
つまりこの手法はReaderであるからこそできる方法であり他のモナドトランスフォーマーでも通じると思わないほうがよさそう。
これむしろreader(f)
メソッドがないためにkleisli(f)
を使っているのが実情かな。
{type l[X] = ReaderTOption[C, X]})#l
たまに出てくるこのidiom、よくわからない。後の説明に期待。
for { s <- get[Config, Stack] val (x :: xs) = s _ <- put(xs) } yield x
ええ、こんな書き方できたっけ・・・。
うーん、ReaderOptionまでは理解できる気がするがStateReaderOptionはちょっとわからん・・・。
Seth さん曰く:
Lens 則は常識的な感覚
(0. 2度 get しても、同じ答が得られる) 1. get して、それを set しても何も変わらない。 2. set して、それを get すると、set したものが得られる。 3. 2度 set して、get すると、2度目に set したものが得られる。
なるほど、昨日無法者のページで従うべき法則云々がよくわからないという話をしていたけど、法則ってこんな感じでふんわり決まっているものなのね。 それならIndexなどの法則云々がよくわからないというかふわっとした議論だったのも少し理解できるかも。 明確な法則がないまま実装だけあったけど、有用な法則を定義できないからその実装自体も削除しよう、こんな感じかな?
最後に、Gerolf Seitz さんによる Lens を生成するコンパイラプラグイン gseitz/Lensed がある。このプロジェクトはまだ実験段階みたいだけど、手で書くかわりにマクロとかコンパイラが Lens を生成してくれる可能性を示している。
Lensを手動で書くのはやっぱり嫌だよね。特にScalaにはcase classっちゅうものがあるんだからと思わないでもないんだけど、case classの上に積み重ねるのもそれはそれでいやだなあ。 コンストラクタが受け取るpublicフィールドだけlensしてくれるようなもの、あるいは単純にpublic field全部に対してlensを自動で定義してくれたら嬉しいかも。
脱線するけど、Kleisliって単なる特殊なメソッドを包含するだけかと思ったら中のメソッドはMがファンクタだったりモナドだったりまちまちを期待している。やっぱりMがモナドやアプリカティブファンクタであるなら便利な型っぽい。
EIP は Scala の関数型をやってる人の間では人気の論文みたいだ。
昔読んだね、、、。
scalaz/scalaz: Principled Functional Programming in Scala
最新のscalazではもう"import Scalaz._"は推奨されなくなっている? scalaz._
と統合されたのかな。
微妙だ。個人的には注入されたメソッドを使うので、これらの関数は僕は使っていない。
ここだけ見ると確かにw static methodにするにしてもシングルトンオブジェクトから呼び出すほうが整理されていていい気がする。
たとえどれだけ一部のユーザにとって論理的に筋が通ったとしても、ライブラリがシンボルを使った演算子を導入すると議論の火種となる。 sbt、dispatch、specs などのライブラリやツールはそれぞれ独自の DSL を導入し、それらの効用に関して何度も議論が繰り広げられた。
たしかになあ。
例えば、[syntax.ToBindOps] は [F: Bind] である F[A] を BindOps[F, A] に暗黙に変換して、それは >>= 演算子を実装する。
Arrow[=>:[, ]] の型宣言は少し変わってみえるけど、これは Arrow[M[, ]] と言っているのと変わらない。2つのパラメータを取る型コンストラクタで便利なのは =>:[A, B] を A =>: B のように中置記法で書けることだ。
にゃるにゃる。
なるほど、妙なtypeのイデオムの理由はこれか。
メモ化
なるほど面白い。覚えておくと使う機会がありそう。 だいぶわからんくなってきた。 ST, IO, Iterateeモナドはもう全然わからん。ここは後で読み返そう。
だけども、Scala の部分適用型の処理がヘボいため
独習 Scalaz — Stackless Scala with Free Monads
もうさっぱりわからん。Freeモナドは別の場所でもよく聞くのであとで調べ直そう。
こっからあと、圏論は最初は既存の知識があるからわかったけど結局後半のメリットはよくわからなかった。 catsはあとで調べよ。
以上で終了。数年に渡って何度も挫折を繰り返したけどなんとか最後までたどり着けた。