はじめに
Egison ( http://www.egison.org/ ) をいろいろ触っているのですが、仕様が確定していなかったり Undocumented なものがまだまだある、という印象。
その中で、ひょっとして現段階ではバグかも?と思ったけれど実は仕様通りの動作だったことが判明した挙動について2点、紹介します。
動作確認環境
※以下の挙動は、上記すべてのバージョンで同様となっています。
その1:複数引数を受け取る関数(lambda)の挙動
コード
;; multi_args.egi
(define $l2 (lambda [$x $y] {x y}))
(define $l3 (lambda [$x $y $z] {x y z}))
(define $main
(lambda [$args]
(do {
[(print (show (l2 "x" "y")))]
; => {"x" "y"}
[(print (show (l2 ["x" "y"])))]
; => {"x" "y"}
[(print (show (l3 "x" "y" "z")))]
; => {"x" "y" "z"}
[(print (show (l3 ["x" "y" "z"])))]
; => {"x" "y" "z"}
})))
解説。
-
l2
は、2つの引数を受け取る関数として定義。
またl3
は、3つの引数を受け取る関数として定義。 - 9行目。
l2
に2つの引数を渡している(定義の通り)問題なく動作。 - 11行目。
l2
に「2つの要素からなる tuple」を渡している。これでも↑と同様に動作。 - 13行目。
l3
に3つの引数を渡している(定義の通り)問題なく動作。 - 15行目。
l3
に「3つの要素からなる tuple」を渡している。これでも↑と同様に動作。
考察
「n
個の引数を受け取るよう定義された関数(lambda)は、n
個の要素からなる tuple を1つだけ渡しても動作する」
ようになっている模様です。
この挙動について公式サイトのドキュメントには記述が見当たりませんが、その代わり。
apply
式というものが存在し、例えば
(apply l2 [1 2])
と書けば、2引数関数であるl2
に[1 2]
という tuple の要素を引数として適用できるようにはなっています3。
でも実際には
(l2 [1 2])
と記述しても全く同様に動作する模様…てことは。
apply
要らないんじゃね?
と一瞬思うのですが、実はそうでもなくて、apply
はちゃんと意味があるようです。
それは↓の「その2」の挙動から分かります。
その2:引数を1つだけ受け取る関数(lambda)の挙動
コード
;; single_arg.egi
(define $l1 (lambda [$t] t))
(define $main
(lambda [$args]
(do {
[(print (show (l1 "x")))]
; => "x"
[(print (show (l1 ["x" "y"])))]
; => ["x" "y"]
[(print (show (l1 "x" "y")))]
; => ["x" "y"]
[(print (show (l1 "x" "y" "z")))]
; => ["x" "y" "z"]
})))
解説。
-
l1
は、引数を1つだけ受け取る関数として定義。 - 7行目。
l1
に1つの引数(string)を渡している(定義の通り)問題なく動作。 - 9行目。
l1
に1つの引数(tuple)を渡している(定義の通り)問題なく動作。 - 11行目。
l1
に2つの引数を渡しているが、エラーは発生せず、2要素の tuple が引き渡されたものとして動作する。 - 13行目。
l1
に3つの引数を渡しているが、エラーは発生せず、3要素の tuple が引き渡されたものとして動作する。
考察
「1個の引数を受け取るよう定義された関数(lambda)は、複数の引数を渡すと勝手に tuple にまとめて1つの引数として扱われる」
ようになっている模様です。
この挙動についても公式サイトのドキュメントには記述が見当たりません。
ただ「その1」の挙動の反転と考えると、ある意味納得がいきますね。
それにこの挙動が仕様ならば実は結構ありがたいです。
「可変個引数」が実現できる可能性があるので。
実際、primitive関数のいくつかは(これも Undocumented な気がするのですが)可変個引数に対応しています:
;; var_args.egi
(define $main
(lambda [$args]
(do {
[(print (show (+ 1 2)))]
; => 3
[(print (show (+ 1 2 3)))]
; => 6
[(print (show (* 2 3)))]
; => 6
[(print (show (* 2 3 5)))]
; => 30
})))
+
は数値の和、*
は積の関数です。ご覧の通り、3個以上の引数を渡しても動作します。
ただし。これらは言語レベルで可変個引数に対応できるよう特別扱いされているそうです。また
(+ [1 2])
と記述しても 1+2 を計算してくれません4。でも。先ほど出てきたapply
を使えば。
(apply + [1 2])
これならちゃんと、1+2 を計算してくれて 3
が返ってきます。同様、
(apply + [1 2 3]) ; => 6
(apply * [2 3 5]) ; => 30
等。
つまり。apply
はあくまで、(apply fn [x y z])
を (fn x y z)
というように展開するためのもの。ということですね。それが必要な場面はちゃんとある、と。
スミマセンやっぱ apply
必要です。
追記:これらの挙動について
作者の江木さんからコメントいただきました。
Egisonは全ての関数は多値を受け取り多値を返すという考えで設計されています。
[
、]
で囲まれたものはタプルと読んでいるのですが、普通の言語では多値と呼ばれているものと似た振る舞いをします。
例えば、要素が1つのタプルはその要素と同じものとして扱われます。
という仕様により、上述の2つの挙動が起きている、ということのようです。
とりあえずこれらは仕様通りの動作と言うことが判明したので、これからはバンバン使っていこうと思います。
-
まだYosemiteにアップグレードしてない…。 ↩
-
公式サイトのパッケージインストーラを使用せず、ghc + cabal-install で共存インストールしています。 ↩
-
公式サイトのBasics of Syntax and Semantics 参照。なお技術情報として Egison Quick Reference も有用な情報が色々載っています。 ↩
-
実はエラーにもならず、
[1 2]
という tuple が返ります。+
は引数が1つだけの場合はその引数をそのまま返し、2つ以上の時にその1つめの数値に対してどんどん加算していくという挙動になっている模様です。*
(乗算)も、あと-
(減算)も同様。 ↩