Edited at

"Rubyistに贈るHaskell入門"がちょっと微妙だったので補足記事その1

More than 3 years have passed since last update.

ども@nobkzです、この記事

http://qiita.com/techno-tanoC/items/1549d0efc044faf16c36

がすごーく微妙だったので、個人的に追記していきたいと思います。

注意:Rubyあんまり書いたことが無いのでお手やわらかに

ちょっと長くなりそうなので、回を分けます


微妙だと思ったので追記したその1


blockとProcオブジェクトについて

まずはですね、先の記事では、高階関数を、「ブロック渡しの関数」と「Procオブジェクトを返す関数」を作ってましたけど、それはそれで正しいのですが、もうすこし、ブロックとProcオブジェクト、Haskellのラムダ式に関して若干の記述の足りなさを感じていたため、追記します。


blockはオブジェクトでは無いということ

まず、blockはオブジェクトでは無いということです 。これはどういうことかと言えば、もし、blockがオブジェクトであるならば、他のオブジェクトと同様に扱えるべきです。例えば、もしblockがオブジェクトであれば、代入ができるわけで、次の様な構文が可能であるべきです。

block = { puts "I am a block!"} # 不可


Haskellの関数

Haskellは関数はファーストクラスであって、ふつうの値と同様に扱うことができます

let id = (\x -> x)


blockをオブジェクト化 => Procオブジェクト

しかし、blockをオブジェクト化することは可能であり、それがProcオブジェクトであるわけです。Procオブジェクトは、blockを、変数のスコープやスタックフレームをオブジェクト化したものです。

proc_obj = Proc.new { puts "I am a Proc Object!" }

proc_obj.call #=> outputs : I am a Proc Object!


Procオブジェクトにおける高階関数

なので、Procオブジェクトは、ふつうのオブジェクトとして使えるため、引数として使えます。

def call_only p

p.call
end

call_only(Proc.new { puts "I am a Proc Object!" })

もちろん、返り値としても使えます。

def return_proc

Proc.new { puts "I have returned!" }
end

return_proc().call

これにおいて、Procオブジェクトを用いて、Rubyは高階関数に相当する記述が可能であるわけです。つまり、Rubyにおいて「関数を引数にする関数」というのは「Procオブジェクトを引数にしたメソッド」であり、「関数を戻り値にする関数」は「Procオブジェクトを返すメソッド」であるわけです


blockは、制限された「関数を渡す関数」

さて、Rubyにおいて、blockを渡すメソッドつまり、「block付きメソッド」というものがあります。先の記事においても紹介されていました。

先の記事では、高階関数における「関数を渡す関数」として、「block付きメソッド」を使ってました。それは、僕としても、間違ってはいないとは思います。

ちなみに、blockを引数にする関数はこうです。

def only_call_b &block

block.call
end

# yieldを使ってこうも記述できる
def only_call_b
yield
end

only_call_b { puts "I am a block" }

block付きメソッドというのは、


  • blockは一つだけ

  • blockは最後の引数

という制限があります。(だからyieldという記法も可能なわけですが)この点において、Haskellのラムダ式による高階関数としては、差異が見られます。どういうことかと言えば、Haskellはこれと同様な制限が無い ということです。

例えば、次のような関数を作ることが可能であるわけです。

Prelude> let sample x y cond = if cond then x . y else y . x

Prelude> sample (+1) (*2) True 1
3
Prelude> sample (+1) (*2) False 1
4

このようは関数をblock付きメソッドで作ることは不可です。

## 不可だが、強引に書いてみる

def sample(&x, &y, cond)
if cond then ->(n){ x.call(y.call(n)) } else ->(n){ y.call(x.call(n)) } end
end
## 不可だが、強引に書くとしたらこう?
sample {|x| x + 1}, {|x| x * 2 }, true

さて、これを実装するには、「Procオブジェクトを渡すメソッド」として高階関数を作ってあげれば良いのです。

def sample(x, y, cond)

if cond then ->(n){ x.call(y.call(n)) } else ->(n){ y.call(x.call(n)) } end
end

sample(->(n){ n + 1 }, ->(n){ n * 2 }, true).call(1)
sample(->(n){ n + 1 }, ->(n){ n * 2 }, false).call(1)

さて、まとめると、


  • block付きメソッドは、「関数を一つだけ引数にして、かつ、最後の引数とする関数」という高階関数に相当するものであろうということ

  • もし、Haskellと同等の高階関数に相当するものは、「Procオブジェクトを引数にしたメソッド」「Procオブジェクトを返り値にしたオブジェクト」がRubyにおける高階関数であるということです。


更に追記:そもそも、なんでblock付きメソッドがあるのか?

まぁ、これは詳しいソースは分かりませんが、おそらく


  • Procオブジェクトを生成するコストが高いこと

  • より、構文っぽく見える

からかなぁと思ってます。なんとなく。