専門の文法の支持の言語の中でmonadを使うのはみっともないです
先週JavaEyeクイズで見ました
論理演算の結果を求める、その中のDe Morganの法則の応用はnight_のようですstalker兄さんが言ったように、難しくはありません.しかし、このように法則を適用して推定するのは直感的ではないかもしれないので、その時私は角度を変えて、C#コードを書いて検証問題の中の表現式がDe Morgan法則を適用する前後の真偽値がいつも同じかどうかを窮挙したいと思っていました.
LINQでは、コードが簡潔です.
このコードは比較的直感的に見えるはずです.でも...あまり直観的じゃないみたい?
多くの人にとって、ループを明示的に使うとより直感的になるかもしれません.
でもこのコードは醜いですね=v=
カンニングをしました.以上の2つのバージョンのコードを比較すると、実際の実行プロセスは異なります.LINQのバージョンはすべての可能性を計算してからContains()かどうかを見ます.Containsはeager operatorで、Select、Whereのようにlazyではありません.ループ版は、等しくない状況に遭遇するとそのままループを終了します.しかし、いずれにしても2つのバージョンの計算結果は同じで、比較に使っても過言ではありません.
実はC#のforeachサイクルは多くの細部を隠しており、明示的に下付きまたはIEnumeratorで容器を巡る必要はありません.しかし、私はやはりこのようなシーンで明示的なループを使うのが好きではありません.主にループがネストされているのがとても見苦しいので、うまくいかないとスクリーンの右側を超えます.
前にLINQのバージョンは簡潔に見えるが、必ずしも直観的ではないのは、複数のfrom句がつながっている対応する関数がSelectManyであり、IEnumerable/IQueryableにとってこのLINQ演算子の背後にある概念はlist monadのbind関数であるからである.そのメカニズムはやはり資料を読んでこそ理解できる.monadという言葉を見ると気絶する人も多いのではないでしょうか.=w=
本当はlist monadをC#でLINQで使ってこんなに簡潔にできると思っていたのですが、ルビーでもそう使えたらいいなと思いました.しかし、タイトルのように、言語がmonad文法を提供していない場合、この遊びは書くのも優雅ではありません.実はもっと重要な問題はtypeclassがない条件の下でmonadは抽象的な階層を高めるために統一的なインタフェースを提供できないかもしれませんか?しかし、私は文法、つまり「どう見えるか」の問題に関心を持ちたいだけです.
例えば、Rubyでmaybe monadを実現します.
Maybeクラスを定義したら、次のように使用できます.
見た目は悪くないですが、メソッド呼び出しはネストされておらず、つながっています.
しかし、これはこの一連の計算が単一のソースからデータを得たからにすぎない:その「1」.1つの「1」と1つの「2」をMaybeで包装し、それらの和を得るには、次のようになります.
そこでbindの呼び出しはネストして、気がふさぎますT
Haskellに書いた時にdoを使わなくてもそんなに面倒ではなかったのを覚えているのに、こんな感じでした.
そして思い出したのは実はこの式がネストされたOTL
やはりカッコがなくて見やすいように見えるのですが…優先度が違うので、Haskellで省略できるカッコはRubyでは省略できません.おとなしく書くしかないでしょう.
まぁ、いずれにしても、Maybeクラスの定義があれば、Maybe::Noneを伝える能力を得ました.前の1+2を1+Noneに変えるように、得られるのはNoneです.
それとも一行に書きますか?
Haskellで書くと、do記法を使わずに
ドで覚えると
ええと、maybe monadを見たことがありますが、list monadは?
Rubyの中のEnumerableモジュールは実は1つの抽象的な表と見なすことができて、中のselect、inject、map/collectなどの方法はすべて関数式のプログラミングの対応物があります:filter、fold_left、mapなど.ではlist monadのbind関数をEnumerableに書いておきましょう.
次に、例えば、[1,2]の各要素と[3,4,5]の各要素との積のリストを要求する.
うーん、思ったほどきれいじゃないT T
リストmonadを使わなくても、eachで直接書くのは悪くないようです.
専門的な文法サポートのある言語では、この論理は優雅に書かれています.
Haskellではdo記法を使わなくても見苦しくありません:
冒頭のC#の例に戻り、上のRubyのリストmonadで書くと、
微妙な感じ・・・
このmonadをルビーでもっときれいにする方法はありませんか?それとも私が要求しすぎたの?
LINQのSelectManyのようにBindを1つのパラメータを多く受け入れるバージョンにしても、呼び出しはネストされるでしょう(lambda式はネストする必要はありませんが).カッコを少なくしてインデントを少なくしたいだけですが...
週末に『The Ruby Programming Language』を読んでインスピレーションを探してみましょう.
ところで、リンクをメモします.
MenTaLguY: Monads in Ruby
まだ読んでいませんが・・・
論理演算の結果を求める、その中のDe Morganの法則の応用はnight_のようですstalker兄さんが言ったように、難しくはありません.しかし、このように法則を適用して推定するのは直感的ではないかもしれないので、その時私は角度を変えて、C#コードを書いて検証問題の中の表現式がDe Morgan法則を適用する前後の真偽値がいつも同じかどうかを窮挙したいと思っていました.
LINQでは、コードが簡潔です.
using System;
using System.Linq;
static class Program {
static void Main(string[] args) {
var booleanValues = new [] { true, false };
var equal = !(from a1 in booleanValues
from a2 in booleanValues
from a3 in booleanValues
from b1 in booleanValues
from b2 in booleanValues
from b3 in booleanValues
select (!((a1 && b1) || (a2 && b2) || (a3 && b3))) ==
(!(a1 && b1) && !(a2 && b2) && !(a3 && b3)))
.Contains(false);
Console.WriteLine(equal); // true
}
}
このコードは比較的直感的に見えるはずです.でも...あまり直観的じゃないみたい?
多くの人にとって、ループを明示的に使うとより直感的になるかもしれません.
using System;
static class Program {
static void Main(string[] args) {
var booleanValues = new [] { true, false };
var equal = true;
foreach (var a1 in booleanValues) {
foreach (var a2 in booleanValues) {
foreach (var a3 in booleanValues) {
foreach (var b1 in booleanValues) {
foreach (var b2 in booleanValues) {
foreach (var b3 in booleanValues) {
if ((!((a1 && b1) || (a2 && b2) || (a3 && b3))) !=
(!(a1 && b1) && !(a2 && b2) && !(a3 && b3))) {
equal = false;
break;
}
}
if (!equal) break;
}
if (!equal) break;
}
if (!equal) break;
}
if (!equal) break;
}
if (!equal) break;
}
Console.WriteLine(equal); // true
}
}
でもこのコードは醜いですね=v=
カンニングをしました.以上の2つのバージョンのコードを比較すると、実際の実行プロセスは異なります.LINQのバージョンはすべての可能性を計算してからContains()かどうかを見ます.Containsはeager operatorで、Select、Whereのようにlazyではありません.ループ版は、等しくない状況に遭遇するとそのままループを終了します.しかし、いずれにしても2つのバージョンの計算結果は同じで、比較に使っても過言ではありません.
実はC#のforeachサイクルは多くの細部を隠しており、明示的に下付きまたはIEnumeratorで容器を巡る必要はありません.しかし、私はやはりこのようなシーンで明示的なループを使うのが好きではありません.主にループがネストされているのがとても見苦しいので、うまくいかないとスクリーンの右側を超えます.
前にLINQのバージョンは簡潔に見えるが、必ずしも直観的ではないのは、複数のfrom句がつながっている対応する関数がSelectManyであり、IEnumerable
本当はlist monadをC#でLINQで使ってこんなに簡潔にできると思っていたのですが、ルビーでもそう使えたらいいなと思いました.しかし、タイトルのように、言語がmonad文法を提供していない場合、この遊びは書くのも優雅ではありません.実はもっと重要な問題はtypeclassがない条件の下でmonadは抽象的な階層を高めるために統一的なインタフェースを提供できないかもしれませんか?しかし、私は文法、つまり「どう見えるか」の問題に関心を持ちたいだけです.
例えば、Rubyでmaybe monadを実現します.
class Maybe
def initialize(val, has_val = true)
@value, @has_value = val, has_val
end
def value
raise 'Maybe::None has no value' unless @has_value
@value
end
class << self
def unit(val)
new(val).freeze
end
end
def bind
@has_value ? (yield @value) : Maybe::None
end
def none?
self == Maybe::None
end
def clone
raise TypeError, "can't clone instance of Maybe"
end
def dup
raise TypeError, "can't dup instance of Maybe"
end
None = Maybe.new(nil, false).freeze
private_class_method :new, :allocate
end
Maybeクラスを定義したら、次のように使用できます.
res1 = Maybe.unit(1).
bind { |x| Maybe.unit(x + x) }.
bind { |x| Maybe.unit(x * x) }
#=> #<Maybe:0x34bc3e8 @has_value=true, @value=4>
見た目は悪くないですが、メソッド呼び出しはネストされておらず、つながっています.
しかし、これはこの一連の計算が単一のソースからデータを得たからにすぎない:その「1」.1つの「1」と1つの「2」をMaybeで包装し、それらの和を得るには、次のようになります.
one, two = [1, 2].map { |i| Maybe.unit i }
res2 = one.bind { |x|
two.bind { |y|
Maybe.unit(x + y)
}
}
#=> #<Maybe:0x35ece5c @has_value=true, @value=3>
そこでbindの呼び出しはネストして、気がふさぎますT
Haskellに書いた時にdoを使わなくてもそんなに面倒ではなかったのを覚えているのに、こんな感じでした.
Just 1 >>= \x -> Just 2 >>= \y -> return (x + y)
そして思い出したのは実はこの式がネストされたOTL
(Just 1) >>= (\x -> ((Just 2) >>= (\y -> (return (x + y)))))
やはりカッコがなくて見やすいように見えるのですが…優先度が違うので、Haskellで省略できるカッコはRubyでは省略できません.おとなしく書くしかないでしょう.
まぁ、いずれにしても、Maybeクラスの定義があれば、Maybe::Noneを伝える能力を得ました.前の1+2を1+Noneに変えるように、得られるのはNoneです.
res3 = one.bind { |x|
Maybe::None.bind { |y|
Maybe.unit(x + y)
}
}
#=> #<Maybe:0x316384 @has_value=false, @value=nil>
res3.none?
#=> true
それとも一行に書きますか?
res3 = one.bind { |x| Maybe::None.bind { |y| Maybe.unit(x + y) } }
Haskellで書くと、do記法を使わずに
Just 1 >>= \x -> Nothing >>= \y -> return (x + y)
ドで覚えると
do x <- Just 1
y <- Nothing
return (x + y)
ええと、maybe monadを見たことがありますが、list monadは?
Rubyの中のEnumerableモジュールは実は1つの抽象的な表と見なすことができて、中のselect、inject、map/collectなどの方法はすべて関数式のプログラミングの対応物があります:filter、fold_left、mapなど.ではlist monadのbind関数をEnumerableに書いておきましょう.
module Enumerable
def bind
self.inject([]) { |acc, e| acc + (yield e) }
end
end
次に、例えば、[1,2]の各要素と[3,4,5]の各要素との積のリストを要求する.
[1, 2].bind { |x|
[3, 4, 5].bind { |y|
[x * y]
}
}
#=> [3, 4, 5, 6, 8, 10]
うーん、思ったほどきれいじゃないT T
リストmonadを使わなくても、eachで直接書くのは悪くないようです.
res = []
[1, 2].each { |x|
[3, 4, 5].each { |y|
res << (x * y)
}
}
#=> res == [3, 4, 5, 6, 8, 10]
専門的な文法サポートのある言語では、この論理は優雅に書かれています.
do x <- [1, 2]
y <- [3, 4, 5]
return (x * y)
-- [3,4,5,6,8,10]
from x in new [] { 1, 2 }
from y in new [] { 3, 4, 5 }
select x * y
//=> { 3, 4, 5, 6, 8, 10 }
Haskellではdo記法を使わなくても見苦しくありません:
[1, 2] >>= \x -> [3, 4, 5] >>= \y -> return (x * y)
冒頭のC#の例に戻り、上のRubyのリストmonadで書くと、
boolVals.bind { |a1|
boolVals.bind { |a2|
boolVals.bind { |a3|
boolVals.bind { |b1|
boolVals.bind { |b2|
boolVals.bind { |b3|
[(!((a1 && b1) || (a2 && b2) || (a3 && b3))) ==
(!(a1 && b1) && !(a2 && b2) && !(a3 && b3))]
}
}
}
}
}
}.all? { |b| b }
#=> true
微妙な感じ・・・
このmonadをルビーでもっときれいにする方法はありませんか?それとも私が要求しすぎたの?
LINQのSelectManyのようにBindを1つのパラメータを多く受け入れるバージョンにしても、呼び出しはネストされるでしょう(lambda式はネストする必要はありませんが).カッコを少なくしてインデントを少なくしたいだけですが...
週末に『The Ruby Programming Language』を読んでインスピレーションを探してみましょう.
ところで、リンクをメモします.
MenTaLguY: Monads in Ruby
まだ読んでいませんが・・・