Gauche:$
これはあくまで好みの問題ですし、こういった構文糖衣は濫用されがちなので 気をつけてください。けれどもスパイスのように、控えめな隠し味として使うと、 しばしばとても有用です。
It is purely a matter of taste, and also this kind of syntax sugars can be easily abused. Use with care, but it may work well if used sparingly, like spices.
from GaucheRefj:$ GaucheRefe:$
Shiro (2008/02/01 00:59:54 PST):
Haskell Envy
Haskellの$がうらやましかったのですよ。
fn1 (fn2 (fn3 (fn4 x)))
と書くかわりに
fn1 $ fn2 $ fn3 $ fn4 x
と書ける。書くときのイメージなんだけど、カッコでくくるのはやっぱり それをまとめてスタックに置いてる感じがするのね。
(foo (bar x y) z)
これを左から書いてく時、(bar で始まるとこで意識の中でスタックを積んで、 y) で終わるところでスタックをポップする (「引数終わり」)。 けれど、
(foo x (bar y z))
の場合だともう(bar y z)の後ろに引数が無いわけで。 単純な処理を数珠つなぎにしてくイメージの時に、
(fn1 (fn2 (fn3 (fn4 x)))
みたいに意識のスタックを積んで行くのがどうも煩わしい。Haskellの
fn1 $ fn2 $ fn3 $ fn4 x
はスタックを積まずに、処理を線形に書き下してる感じがする。
意識の末尾再帰最適化だね。
で、このエッセンスをGaucheにも入れてみたかったんだけど、 他と一貫性を保ちながら中置の$を入れるのは結構難しくて、 棚上げしてた。
でもさっき気づいたんだけど、中置にする必要ないじゃん。
($ fn1 $ fn2 $ fn3 $ fn4 x)
が
(fn1 (fn2 (fn3 (fn4 x))))
に展開されればいい。
$マクロ試案
展開例:
($ f a b c) => (f a b c) ($ f $ g a b c) => (f (g a b c)) ($ f $ g $ h a b c) => (f (g (h a b c))) ($ f a $ g b $ h c) => (f a (g b (h c)))
これはすごく簡単。補助マクロを使って:
(define-syntax $ (syntax-rules () [(_ . rest) (%$-rec () . rest)])) (define-syntax %$-rec (syntax-rules ($) [(_ (e ...) $ . rest) (e ... ($ . rest))] [(_ (e ...) x . rest) (%$-rec (e ... x) . rest)] [(_ es) es]))
ちなみに$を重ねるとこうなる。
($ $ ff) => ((ff)) ($ $ $ fff) => (((fff)))
ただ、($) の解釈がちょっと曖昧。上の定義だと () になって、これは現在のGaucheでは たまたまself-evaluatingだから () へと評価されるけど、「何も無いものをapplyする」って 何か変な感じだ。エラーにすべきかもしれない。
上の例だけだと読みやすいようには見えないけど、関数名が長くてインデントががんがん 右に寄っちゃうような場合はインデントしやすくなるんじゃないかな。
pa$と統合
($ ...) で始まったフォームが $ で終わった場合、その後にさらに引数が来ることを 期待してるってことで、pa$したのと同じ扱いにしたらどうだろうか。
($ f a b $) => (pa$ f a b)
これだと、「部分適用版は末尾に'$'をつける」っていうGaucheのnaming convention とも一貫性がある。どのくらい使えるかはわからんけど。定義をちょっと変えるだけでいける。
(define-syntax $ (syntax-rules () [(_ f . rest) (%$-rec () f . rest)] [(_) (syntax-error "Invalid $-form: ($)")])) (define-syntax %$-rec (syntax-rules ($) [(_ (e ...) $) (pa$ e ...)] [(_ (e ...) $ . rest) (e ... ($ . rest))] [(_ (e ...) x . rest) (%$-rec (e ... x) . rest)] [(_ es) es]))
Shiro(2008/02/03 15:16:32 PST): いやこれじゃだめだ。
($ f $ g $) => (lambda args (f (apply g args)))
になるべき。これができると、例えば「シンボルのリストを取り、全部のシンボルを 大文字のシンボルにする」というのは
(map ($ string->symbol $ string-upcase $ symbol->string $) lis)
と書ける。
jmuk? 2008/02/10 05:15:56 PST: こんな風に書いて対処してみましたがどうでしょう(エレガントさに欠けますが)
(define-syntax $ (syntax-rules () [(_ f . rest) (%$-parse () () () f . rest)] [(_) (syntax-error "Invalid $-form: ($)")])) (define-syntax $* (syntax-rules ($) [(_ f . rest) (%$-parse (apply) () () f . rest)] [(_) (syntax-error "Invalid $*-form: ($*)")])) (define-syntax %$-parse (syntax-rules ($ $*) [(_ (app ...) fs (e ...) $) (%$-cons-args args (apply e ... args) . fs)] [(_ (app ...) fs (e ...) $ . rest) (%$-parse () ((app ... e ...) . fs) () . rest)] [(_ (app ...) fs (e ...) $* . rest) (%$-parse (apply) ((app ... e ...) . fs) () . rest)] [(_ apps fs (e ...) x . rest) (%$-parse apps fs (e ... x) . rest)] [(_ (app ...) fs es) (%$-cons (app ... . es) . fs)] )) (define-syntax %$-cons (syntax-rules () [(_ r) r] [(_ r (f ...) . fs) (%$-cons (f ... r) . fs)])) (define-syntax %$-cons-args (syntax-rules () [(_ args r) (lambda args r)] [(_ args r (f ...) . fs) (%$-cons-args args (f ... r) . fs)]))
applyもいけるかな
さらにエスカレート。
($* f a b c) => (apply f a b c) ($* f a $ g b c) => (apply f a (g b c)) ($* f $* g $ h a b) => (apply f (apply g (h a b))) ($* f $* g $* h a b) => (apply f (apply g (apply h a b)))
これはやりすぎか? でもこれだと実際$*使った方が短くなるしな。
Arcの `:'
Arcの `:' は1引数に特化してるけどここのアイディアと同じであることに気づいた。
Arcでは foo:bar は (compose foo bar) と読まれる。と、この定義だけ読むと ふぅんって感じなんだけど、
(foo (bar (baz x y))) => (foo:bar:baz x y)
ってことなんだな。確かにこれは短くなるしネストも減る。
ここの$記法だと
($ foo $ bar $ baz x y)
になるんだけど、欠点は必ず元のネストした式よりも長くなってしまうことだ。 前置の$が無ければちょうど同じ長さになるんだけどね。
$のアドバンテージは、各関数名が長くてネストしてる時にインデントが 右に流れすぎるのを防げること。
(abcde-fghijk (lmnop-qrstuv w x) (yzabcdef-ghijkl-mnop q r (stuvwxyz-abcdefg hijklm (nopqr stuvwx))))
みたいなのが
($ abcde-fghijk (lmnop-qrstuv w x) $ yzabcdef-ghijkl-mnop q r $ stuvwxyz-abcdefg hijklm $ nopqr stuvwx)
になる。
Clojureの->,->>
yamasushi(2013/05/01 04:02:34 UTC)
- Gauche:Clojure:->,->>
- Gauche:Clojure:-?>,-?>>
(foo (bar (baz x y))) => (-> (baz x y) bar foo)
GaucheSource:lib/gauche/common-macros.scm
;; Haskell-ish application. ;; The starting '$' introduces the macro. ;; Subsequent '$' delimits "one more arguments" ;; Subsequent '$*' delimits "zero or more arguments". ;; ;; ($ f a b c) => (f a b c) ;; ($ f a b c $) => (lambda (arg) (f a b c arg)) ;; ($ f $ g a b c) => (f (g a b c)) ;; ($ f $ g a b c $) => (lambda (arg) (f (g a b c arg))) ;; ($ f $ g $ h a b c) => (f (g (h a b c))) ;; ($ f a $ g b $ h c) => (f a (g b (h c))) ;; ($ f a $ g b $ h $) => (lambda (arg) (f a (g b (h arg)))) ;; ;; ($ f a b c $*) => (lambda args (apply f a b c args)) ;; == (pa$ f a b c) ;; ($ f a b $* g c d) => (apply f a b (g c d)) ;; ($ f a b $* g c d $) => (lambda (arg) (apply f a b (g c d arg))) ;; ($ f a b $* g c d $*) => (lambda args (apply f a b (apply g c d args))) ;; ($ f a b $ g c d $*) => (lambda args (f a b (apply g c d args)))
cut-sea (2008/02/01 02:05:26):
shiro (2008/02/01 02:53:08):
cut-sea (2008/02/01 04:22:26):
shiro (2008/02/01 04:53:45):
cut-sea (2008/02/01 05:49:47):
shiro (2008/02/01 05:50:45):
cut-sea (2008/02/01 05:51:08):
cut-sea (2008/02/01 05:54:06):
Theoria (2008/02/04 00:02:38):
shiro (2008/02/04 01:16:16):
shiro (2008/02/04 01:17:09):