Gauche:$

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)


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)))

Past comment(s)

cut-sea (2008/02/01 02:05:26):

こういうのも含めてLispのリーダマクロみたいのがあれば、 好きにできていいなぁと思ったりしたんです。

shiro (2008/02/01 02:53:08):

リーダーマクロはいずれつけるつもりですが、難しいのは、ナイーブにやっちゃうと効果がグローバルに出ちゃうんですね。(現在でもsrfi-10のreader-ctorに同じ問題がありますが) できればデフォルトではモジュールによる束縛の可視/不可視と同じスコープで、reader syntaxを切り替えられるとうれしいんですが。

cut-sea (2008/02/01 04:22:26):

そういや、arcのコードでもmzschemeのreadtableは parameterちっくに一時切り替えみたいに見えましたねぇ。 私もその方がいいなと思います。 期待して待ちます。

shiro (2008/02/01 04:53:45):

ああいうふうに明示的にloadするんなら簡単なんですよね。「このモジュールをuseしたらこういうmy文法がソース中で使えるようになるよ」ってのを綺麗にやるのはもうちょっとめんどくさそう。まだあんまり考えてないんだけど。

cut-sea (2008/02/01 05:49:47):

ははぁ、例えば

(use syntax.arc)

(map [+ _ 10] '(1 2 3 4 5))

な風にできるわけですね。

ちょっと考えただけで、競合する文法を可能にする ライブラリをロードしたらどうなんの?とか いろいろ問題ありそうですな。

shiro (2008/02/01 05:50:45):

そうそう。それをやりたい。

cut-sea (2008/02/01 05:51:08):

競合するような文法を持つ複数のライブラリをロードしたら

っていう意味です。分かりにくい日本語だった。。。

cut-sea (2008/02/01 05:54:06):

しかし、これはなかなか夢がある。

(use syntax.haskell)とか(use syntax.ruby)とか・・・ こういう文法はどうよ? (use syntax.mylang) みたいなのをガンガン楽しめます。

Theoria (2008/02/04 00:02:38):

「とりあえず書いてみる」というのはどうでしょう :-)

shiro (2008/02/04 01:16:16):

まだ考えてないのは私にまとまった時間が取れないからなので、ハックは歓迎します。書いてみないとわからないこととして、性能に与える影響があります。useとreadに大きな影響を与えないことが確認されないと取りこめません。あと、もうひとつpendingになってる理由として、Unicodeの文字クラスでエントリを指定できるようになってないと大きな文字セットで使い物にならないという話があって、じゃあ性能の良いUnicodeの文字クラスを実装する方が先だなと。ちなみにtreemapが組み込みになったのは、やはりUnicodeフルセットで「全ての漢字にはこの読み取りルーチン」みたいな指定を効率的にやるのにtreemapがいるなあと思ったってこともあります。

shiro (2008/02/04 01:17:09):

あ、もしかしてreadtableの話じゃなくて'$'のこと? それならもうCVS HEADにちょっとしたものが入ってます。'$'で終わるやつはちゃんと実装できてませんが。

Post a comment

Name:


Tags: $, Haskell

More ...