2006年06月20日 18:20 [Edit]

Haskellは難しくない--こともある。

haskell.org

まだVol.33も正式発売されていないというのに、すでにVol.34の原稿書きにいそしむ今日この頃。実はその記事の一つがHaskellだ。「Powered by Ph.Dな言語の解説を中卒がやるのってどうよ?」と我ながら思うが、自分で言うのもなんだがむしろいい記事に仕上がってきているのを感じる。

Matzにっき(2006-06-13)
私:「Haskell難しいですから」
「ええっ?」

というわけで、予告ついでに、HaskellよりRubyの方がずっと難しいことを一つ上げさせていただく。


それは、なんといってもProcオブジェクトだ。

「Rubyの美しくない部分を一つあげよ」と聞いて、真っ先に思い立つのがこれだ。

例えば、logbXをカリー化して表現することを考えてみる。

いづれの電脳言語でも、log()ln()(log natural)として定義されているので、ここではlog2と名付けることにする。あまり美しくないが、Log()とするとhaskellで問題が生じるし、一応atan2()とも整合性がとれるので。

まずはPerl 5で。

sub log2{
  my $b = shift;
  sub {
    my $x = shift;
    log($x)/log($b)
  }
}

使うときは、こう。

my $lg2 = log2(2);
print $lg2->(16);

Perl6だと、もっと美しくかける。

sub log2($b){ sub($x){ log($x)/log($b) } }
my $lg2 = log2(2);
say $lg2.(16)

Pythonだと、こんなか。

import math

def log2(b):
  return (lambda x: math.log(x) / math.log(b) )
  # 2.3以降は math.log(x, b) もOKだそうだ
lg2 = log2(2)
print lg2(16)

いちいちlambdaと言わなければならないところが、Perlより不自然だ。

それでは、Rubyでは?

def log2(b)
  return lambda{ |x| log(x) / log(b) }
end

まあ、ちょっとPythonじみているけど、mathをimportしない分よしとしよう。問題はこれを呼び出すときだ。

lg2 = log2(2);
p lg2(16);

が動かないのである。

p log2.call(16)

としなければならないのだ。

それで、Haskellはどうかというと、

log2 b x = log x / log b

こんだけ、である。カリー化はデフォルトなのだ。だから型宣言では、

log2 :: (Floating t) => t -> t -> t

と矢印を使う。それであれば

lg2 = log2 2

とした時に、実に直感的に

lg2 :: t -> t

であることが定まる。そしてHaskellは、こういう直感は型推論でよきにはからってくれる。

これは、確かに簡単で直感的だ。直感度で行くと残りはperl, python, rubyといった順序になると思う。

実は、この自動カリー化と型推論は Perl6 でも取り入れられることが決まっている。

sub log2($b,$x){ log($x)/log($b)  }
my $lg2 = &log2.assuming(2);
say $lg2.(16)

Haskellに比べるとめんどくさい感じがするが、これは Perl6 があくまでもふだんは手続き型言語のふりをして、必要とあればいつでも関数型に「化ける」というポジションにあるからだ。実は Perl6 は、「スキン」は Ruby から最も多く盗んでいるが、「はらわた」は Haskell から最も多く盗んでいるのだ。

Rubyではこの辺どうしていくのだろうか....

Dan the Multilingual


この記事へのトラックバックURL

この記事へのコメント
Pythonのコード、

from math import log
log2 = lambda x:(lambda y: log(y, x))

か、あるいは、

from math import log
def log2(x):
def xlog2(y):
return log(y, x)
return xlog2

のほうが、ちょっとキレイかも………
(全く実の無い話でごめんなさい)
Posted by 機械伯爵 at 2006年06月22日 13:16
yuki_neko_nyanさん、
その通りです。最初はLog()として定義していて、Haskellのことを思い出してlog2()に変えたのに、そこを直し忘れてました。
Dan the Typo Genearotor
Posted by at 2006年06月21日 16:57
Perl6のコードの
----------------------
sub log2($b){ sub($x){ log($x)/log($b) } }
my $lg2 = Log(2);#ここ
say $lg2.(16)
----------------------

----------------------
sub log2($b){ sub($x){ log($x)/log($b) } }
my $lg2 = log2(2);#ここ
say $lg2.(16)
----------------------
が正しいでしょうか?
#pugs 6.2.11で下のコードの動作は確認しました。
Posted by yuki_neko_nyan at 2006年06月21日 16:46
Tetsさん、
>lg2.(16) という記法はやめた方がいいような気がします。ドットを見落とすことによるバグが多発するような。
ちなみに Perl6 では、この場合 . は省略可能です。
$subref($arg)
Rubyの場合、
function(arg)とmethod(arg)は同じにしにくいですね。Perl6の場合はsigilのおかげで曖昧さが排除されているけど、rubyはそうでないので(でもsigilがきらいな人にはそれがうれしい)。
横やりですが、大歓迎です。
Dan.the.Occasional.Rubyist
Posted by at 2006年06月21日 02:56
>まつもとさん
lg2.(16) という記法はやめた方がいいような気がします。ドットを見落とすことによるバグが多発するような。
メソッド呼び出しと全く同様に出来ないならば、へたに似せるのは良くないと思います。現在のlg2[16]や1.9の(lg2)(16)ぐらいが限度ではないでしょうか?(他にもあるかもしれませんが)

横槍を入れて申し訳ありません>弾さん
Posted by Tets at 2006年06月21日 02:22
いや、配列アクセスに使ってる[]メソッドを流用しただけなんですが。
まあ、関数型プログラミングでHaskellに勝とうとは思ってませんから。
Posted by まつもと at 2006年06月21日 01:50
muscovyduckさん、
あ、ほんとだ。
でもなんで?
http://www.ruby-lang.org/ja/man/?cmd=view;name=Proc;em=Proc
> self[arg ...]
> call(arg ... )
>
> 手続きオブジェクトを実行してその結果を返します。
確かに。でも薄気味悪すぎる。むしろ邪悪度が上がったような気が。
Dan.the.Occasional.Rubyist
Posted by at 2006年06月20日 23:34
なるほど、Procオブジェクトにこんな使い方があったとは。
http://www.ruby-lang.org/ja/man/?cmd=view;name=Proc#self.5barg.20.2e.2e.2e.5d

しかし、それでもやはりPythonの書き方のほうが綺麗に見えます。
ベースが関数型で、オブジェクト指向を後付けとしたPythonと、
ベースがオブジェクト指向で、関数型を後付けとしたRubyとの違いでしょうか。

恥ずかしながら私はPythonの(見かけ以外の)綺麗さを実感できたのは初めてです。Danさんに深く感謝いたします。

しかし同じ関数型なら、Haskellの綺麗さが際立っています。モダンな関数型言語の機能を完全に取り込むことは困難ですから、オブジェクト指向に重きを置いたRubyの方が実用的なように思えます。
Posted by no-name at 2006年06月20日 23:19
p lg2.call(16)

は、

p lg2[16]

と書けます。次のとおりです。

--
#!/usr/bin/env ruby

include Math

def log2(b)
return lambda{ |x| log(x) / log(b) }
end

lg2 = log2(2)
p lg2[16]
--

実行結果です。

--
4.0
--
Posted by muscovyduck at 2006年06月20日 22:07
全般的な使用頻度を考えると、PythonやScheme、Haskellのような関数をとるのがデフォルトという選択は考えてません。
1.9なら実験的に (lg2)(16) ってのが書けるけど、lg2.(16)もありかなあ。
Posted by まつもと at 2006年06月20日 19:05