Scheme:コーディングスタイル
koguroさんのとこで出た話題
コーディングスタイル
みなさんどんな風に Scheme のソースを書かれているのでしょうか。 他の人のソースを参考にしているのですが、どこかに明文化されたものがあるとうれしいです。(C みたいに K&R とか whitesmith とかいろいろ流派があるのでしょうか)
とりあえず、バリエーションが出る要素を上げていってみましょ。Shiro
関連:Lisp:コメント
ifのbegin
もしC言語でif (...) { を if (...) (begin と書けといわれたらうんざりするでしょう。 C言語のswitch文にbreak;を書いているような気分になります。(gemma): 2006/06/14 19:50:42 PDT
- Shiro (2006/06/14 20:55:23 PDT): else節が要らない場合はwhenを使うのが好みです。(if+beginに比べて)インデントが深くならないし、意図もはっきりする。else節が要る場合はcondを使いますねぇ。個人的にbeginを使うのはほとんど、マクロをトップレベルで複数の式に展開する場合か、デバッグ等で一時的にひとつの式を複数の式に置き換えたい場合のような。
(そもそも、マクロの例を除いては、beginが必要になるということは何らかの副作用を使っているわけですから、beginがうるさいと感じるようならプログラムの組み立てをより関数的に考え直してみるのも良いかもしれません。) - leque(2006/06/14 21:02:42 PDT): 個人的には、begin の必要になるような副作用をつかった処理が際立つので、逆によいと思っています。 Scheme で書く場合には C の場合とちがって副作用のあるアルゴリズムで書かないようにしているので、それほど気になりません。
- gemma(2006/06/15 11:30:29 PDT): 開発中にデバッグプリントを挿入しようとしたら、beginと打たなきゃいけないことが何度かあったので聞いてみました。named-letでloopの終了条件を書くのにif文かcond文かで一瞬迷ってifで済ませることが多かったんですが、これからはcondも使ってみようと思います。
- leque(2006/06/15 12:18:03 PDT): そういった目的なら #?= はどうでしょう (GaucheRefj:debug-print)。こちらなら不要になったときに取り除くのも簡単です。
- gemma(2006/06/17 08:32:48 PDT):mapからmapへと流れていく途中にも簡単にカッコいらずで差し込めるし、便利です。
- ちなみにOlegさんは簡単な条件分岐もcondを使ってましたね。 後々の拡張の際に結局やりやすいからか、それともLispでは元はcondが基本だったから、 その慣習から反射的に使っているのか。 以来私もほとんどcondを使ってたりします。cut-sea:2006/06/15 17:58:40 PDT
- gemma(2006/06/17 08:32:48 PDT):(if (null? l) (reverse! l)...くらいは慣用的にifがいいかと思いましたが、全てcondってのもアリなんですね。
括弧の位置
閉じ括弧の位置ですが、私は「(baz) こそがこの関数の戻り値」と考えるときは前者、「func とは単に処理を繋げたもの」と思っているときは後者を使います。 (koguro)
(define (func) (foo) (bar) (baz))
(define (func) (foo) (bar) (baz) )
Shiro: 戻り値の有無で意識したことはないです。 むしろ(baz)の後に頻繁に追加や削除がありそうな時は後者のようにしています。
- koguro: なるほど。最初は前者のように閉じ括弧を連続で繋げていたのですが、これだと (baz) の後に追加をするのが面倒なので、(baz) が戻り値(=ここで終わり)以外のときは後者を使ったほうがいいのかなと思っていました。
let式
bindingが短いとこう書きたくなる:
(let ((a b) (c d) (e f)) ... ...)
逆にbindingが長い時は、bindingの終りを明示するために閉じ括弧だけ 一つの行に置いたりします。
(let ((a loooooooooooooooooooooooooooooooong-expression) (b (loooong-expression (and loooong-argument-list loooooong-argument-list lo--------ng----argum--ent---list))) (c (further long exp- ression)) ) ;;←コレ ... ...)
if式
EmacsのScheme-mode標準だとこう:
(if test then else)
Lisperはこう書く人もいる。 else節以降が implicit prognになる処理系もあるので、else の下にどんどん 続けるには適している。
(if test then else)
ifのネストが深くなるときは、こんな風に書きたくなることも。
(if test then else)
(if test then else)のthenが短くelseが長い時に、 thenをtestの後ろに書くのを見たことがある。コンパクトにはなるが、 個人的には条件をぱっと見た時にわかりにくいのでいまいち好きじゃない。Shiro
(if (test-expr) r (let ((x y) (z q)) (foo) (bar) (loop (cons x r))))
わたしは常にこれかも。ささだ
(if (null? x) y (else-nantoka...))
関数定義
関数定義を
(define (foo arg) (hoge huga ...))
と書くか
(define foo (lambda (arg) (hoge huga ... )))
と書くか。
私は現在完全に前者。 そっちの方が自分で分りやすいからだけど、scheme 本を読み比べると 結構ポリシーがあるのか後者に執着するケースも見られる。 で、個人的には後者を書く方がなぜかカッコいいと思ってたりする。(錯覚?)cut-sea:2004/03/03 22:19:31 PST
- ytaki: 僕も前者が多かったのですが,最近後者に統一しようかと思ってます. というのは,『関数を定義する』といった場合は必ずしも上記のような場合ばかりで なく,関数を返す高階関数で関数を定義することもあるためです.というか僕の場合, 前者のようにばかり書いていたら高階プログラミングの発想が薄れていたいう(^^;). 慣れればどちらでもいいんでしょうけどね.(2005/03/19 00:27:07 PST)
- emeitch: 自分は『シンボルに束縛する』という、『define』への統一的感覚が生じる後者が好きです。ですが、単純な『関数を返す関数』場合、『lambda』の本体記述部にすぐにまた、『lambda』が現れます。これでは、「パッと見たときに『関数を返す関数』の意図が把握しづらいのでは?」という思いと(これは慣れの問題かもしれません)、「『lambda』構文を何回も入力するのが面倒だぁ。」という怠惰から(これが主な理由かも...)、『関数を返す関数』の時だけは前者を選択しているというのが現状です。でも、「これじゃ、ダブルスタンダードでは?」と考えると、「前者に統一するほうがいいのでは?」という気もしてきます。2005/03/21 19:38:26 PST
- ytaki: たぶん,syntax sugar をどう捉えるかによるんじゃないかなと 思います.ある程度まとまった実用的なプログラムにする時は,その定義の 意図に応じて(慣用表現やライブラリ利用を含む)読みやすい/書きやすい 記述にしておく方が改良などの保守はしやすいはずですし,共同開発でダブル スタンダードは禁物でしょう.ただ,そればかりでは,新しい問題に対処して いこうとする時にどうしても既存のパターンにひきずられてしまい,難解に 考えすぎてしまうというか.…はい,僕のことです(泣).(2005/03/22 11:28:12 PST)
関数のサイズ
Scheme/Lisp で、一関数当たりの行数(もっと妥当な言葉があるかも)はどれくらいが望ましいでしょうか? わたしはどの言語でも50行程度を上限だと思っていますが、Scheme はまだ入門者で分かりません。 何か意識のしどころが違ったりしますか?
- Shiro: Schemeの場合、関数内関数を多用するスタイルが多く見られるので、 トップレベルの関数定義だけ見ると非常に長いものができたりしますね。 ただ、各関数の本体だけを見るなら、ほとんどのものは20行以内に 収まるんではないでしょうか。分岐の枝のほうにあるループ構造などは 関数として括り出します。長くなりがちなのはcondやcaseの分岐が多い場合 ですが、これもあまり長くなるようだったら分岐処理の方を関数内関数にします。
- 参考になります。Emacs Lisp で異常に長い関数を良く目にしていたのですが、あれがユーザーインターフェースを扱うためなのか、単に良くないスタイルなのか、Lisp 系の言語ではあまり気にしないものなのか、行という意識が悪いのか、などと疑問に思っていました。Scheme では、一、二行程度の非常に短い・ユーティリティ的な関数も多いようですね。
- Shiro: Common Lispでは長い関数を書く人もよく見ます。 prognでだらだら流すスタイルは副作用前提になります。Schemerは どっちかというと関数的な書法を好むようで、そのへんの差かもしれません。
- うーん、そうなのですか…Shiroさんの3/18の日記と合わせると、(Common)Lisp は関数型だから保守しやすいプログラムになる、とボスに訴えて会社で使おうなどと画策しているのですが、戦略として破綻しそうです(Scheme でないのは単に商用のものがあるかどうか、です)。「progn でだらだら」は読みやすくない、ですよね?それとも Schemer としては好みじゃないけどまあ読める、といった程度でそれほど悪いスタイルではないと思ったほうがよいでしょうか?
- Shiro: 書き手によると思います。Schemerとしても名高い某先生の 書いたCommonLispプログラムは短い関数の集合でとっても読みやすかったです。 想像ですが、手続き型どっぷりの人がそのままの流儀を持ち込もうとした時に、 CommonLispのほうがその柔軟性の高さゆえに流儀に適合しやすい、 っていうことがあるんじゃないでしょうか。Schemeでも副作用ばりばりの コードは書けなくはないですが、種々の言語構造が関数的な書法の方を 書きやすくしているように感じます。
編集環境
括弧を書くタイミング
MIT OPEN COURCE WARE から LECTURE NOTE を引っ張って呼んでますが、 emacs 上で S式を書く時のタイピングをそのまま記載している文面を見ました。
(define (f x) C-j (lambda (x) C-j ...
みたいな感じなのですが、皆さんも頭から順に書かれるのでしょうか? どうも私は () を書いてから戻って中を書くくせがあります。 でもそれで書きやすいか?と言われると結構書きにくいので、 みんなどうやって書いているのかなぁと思ってました。 emacs 上で、上記の様な書き方になれるのが結構書きやすいものなのでしょうか。。。 個人差があろうかとも思いますけど、普段他の人が S 式を書いているところをライブで みたことがないので色々聞いてみたかったりして。cut-sea:2003/09/10 09:58:42 PDT
- Shiro: 私は頭からですね。emacsではM-( で ()が入ってカーソルが
開き括弧の後に移動しますが、私は使う習慣はないなあ。
- やはりそうですか。ちなみに()を先に書くというのは自分で()の対応をを管理しないといけないので、つらいのだと気付きました。
ウチにウチに書いていく間はともかく、あとから同じレベルもしくは上のレベルに部分式をいれる時に結局閉じガッコを数えたり、
全部消してガンガン))))書きながらハイライトみて確認したり。(二度手間ダヨ〜)
間抜けなんだけど完全にクセになってしまっているので早急に直さないといかんなぁ。
そしたら今度は C のコード吐いている時に不自由するのかしらん。cut-sea:2003/09/11 23:53:04 PDT
- だいぶん慣れて来ました。とりあえず()を連続して書く癖も抜けて来て良い感じ。cut-sea:2003/10/16 06:30:09 PDT
- やはりそうですか。ちなみに()を先に書くというのは自分で()の対応をを管理しないといけないので、つらいのだと気付きました。
ウチにウチに書いていく間はともかく、あとから同じレベルもしくは上のレベルに部分式をいれる時に結局閉じガッコを数えたり、
全部消してガンガン))))書きながらハイライトみて確認したり。(二度手間ダヨ〜)
間抜けなんだけど完全にクセになってしまっているので早急に直さないといかんなぁ。
そしたら今度は C のコード吐いている時に不自由するのかしらん。cut-sea:2003/09/11 23:53:04 PDT
emacsのキーバインディング
emacs 上では S 式単位での移動や入れ換えなんかも簡単にできるようになってますけど、 デフォルトでは M-C-t とか M-C-F など、メタキーとコントロールキーを押す操作が 入りますよね。 ESC, Ctrl+t というタイピングも押しにくいのですが、 Ctrl+Alt+t ってのも 結構押しにくい。。。と感じるのです。 さて、皆さんはどうやって押してます? もしくはキーバインドを変更してますか?cut-sea:2003/09/10 09:58:42 PDT
- Shiro: 私は、左手の小指と親指でCtrlとAltを押さえてますね。 M-C-tやM-C-fならtやfは左手の人差指。M-C-aだとaは左手の薬指で 押してます。
- 最近は ?C-[ でエスケープを入力するようにしてます.
これなら遠くないし.?C-[ ?C-t とか.
ただこれってターミナルエミュレータ上とか,
元端末上のプログラムとかでないと実装してない事もありますね.
Word や Excel 相手に入力して無視されたり.
Emacs は大丈夫.
- こちらも結構慣れて来ました。?C-[ で Meta 代わりに使うのは非常に良い感じです。cut-sea:2003/10/16 06:37:48 PDT
- WordやExcelな世界のために、右手小指付け根で右Altを押す技もおぼえておくといいですよ。
- 無変換や前候補、カタカナキーをCtrl, Meta, Backspaceにしてます。これに慣れると106キーボードのほうがむしろ使いやすくなる。
- 「Ctrlキーは手首の付け根で押せ」とemacs使いの先輩に教えてもらった事があります。奇特な人だと思って流していたのですが、3年位経ってから「小指の付け根って言ってたのか!」と思い直し、それ以降、小指の付け根で押してます(勘違いスマソ→先輩)。Ctrlキーを押した状態で5本指(Homeポジション)が使えますよ。この押し方のおかげで、3台持ってたHappyHackingKeyboard捨てちゃいました。南無。-- hira@vim使い 2004/03/02 19:13:18 PST
- iriyak (2004/03/02 20:17:25 PST): 脱線します。hira さんの先輩のお話ですが、同僚にもその押し方をすすめられたことがあります。最初なんてアクロバット的な押し方なのか! と思いました。わたしは NeXT のスペースキーバーの下側のバーのように親指で押せるバーに CTRL が割り当てらればいいな〜と思ったことがあります。木管楽器をやっていた人は左手の親指が器用に動く人が多いから親指にもっと活躍の場を与えてもいいのかなと思いませんか? (余談でした)
- あ〜私は何年か前に何かで読んだんだよなぁ〜、
ちなみにあれは小指の付け根で押すらしい
みたいな文章だったと思うけど失念。
でも小指の付け根っていうかほとんど手の平に近い感じ。。。 最初はちゃんとおせてるのか?って心配だったけど案外素直に押せますよね。 (そりゃそういう設計なんだから・・・まぁ)
とかエラソーにいいながら右側の Ctrl はちょびっと離れてて苦手だったり。
まぁでもこのおかげでメタ(私の場合は esc または alt)はヤだけど Ctrl はだいぶ押しやすくなったと思います。 で、最初に戻るんだけどどうしても Ctrl を小指の付け根で押さえると alt との同時押しは不便だわ、 esc は離れとるわで。。。cut-sea:2004/03/03 21:44:09 PST
- Emacs の Lisp 用 S-式操作(デフォルト)話題に出てきたものを中心に
Key 定義 コメント C-M-@, C-M-SPC mark-sexp これでS式をマークできるので、切り取ったりコピーしたりする C-M-h mark-defun 関数をマーク C-M-f, C-M-b forward-sexp, backward-sexp S式単位で移動 C-M-t transpose-sexp M-t のS式版 C-x n d narrow-to-defun 現在の関数以外を不可視に。defun の奥深くからでも可能 M-( insert-parentheses 括弧を挿入し、カーソルをその間に移動(□) M-TAB lisp-complete-symbol 補完 C-M-a beginning-of-defun 関数定義の先頭へ Esc C-delete backward-kill-sexp 前の S 式を削除 C-M-k kill-sexp S 式 をkill C-c C-e eval-expression S式の評価 C-x C-e eval-last-sexp 直前のS式の評価 C-c C-r eval-region リージョン内のS式の評価
わたしは未だに式単位でなくてC-f, C-n などでちまちまカーソルを動かす悪い癖があるのですが、 なかなか克服できません…
- 某 CommonLisp セミナーでキーバインドを覚えてきましたので追加しました。M-x apropos sexp とすると幾つか出てきます。なお、セミナーでは無理にキーバインドを全部覚える必要は無いとの話でした。
括弧挿入のキーバインド
括弧の対を挿入するEmacs のコマンド M-( または M-x insert-parentheses は, 括弧がキーボードの上段にあることもあり,押しづらいという方もいると思います. 自分が押しやすいキーに変えるといいのかもしれませんが,ここでは,その一例を挙げてみます.
(global-set-key [?\C-\S-d] 'insert-parentheses) ; "d" for down (global-set-key [?\C-\S-f] 'insert-parentheses-1) ; "f" for forward (defun insert-parentheses-1 () "カーソルの直後の式を括弧で囲む." (interactive) (insert-parentheses 1))
全角の「\」を 1 バイトのバックスラッシュに読み替えてください.
- Control-Shift-d によって,括弧を挿入し,そのなかに移動します.
- Control-Shift-f によって,カーソルのつぎの式を囲むように括弧を挿入し,そのなかに移動します.
両括弧の対応を見るために show-paren-mode がオンでないと, 上で指摘されているとおり,どのレベルにカーソルがあるのか分からなくなりかねません.
全体的なコメントはこちら
Shiro (2003/01/06 21:09:44 PST): コンパクトな方が見やすいので、ロジックの流れを追える範囲で なるべく上下方向に詰めるように書いてます。 ただ、ネストが深くなった時に最後の式の後ろに閉じ括弧を並べるのは あまり好きではなく、そういう時は括弧の対応が取りやすいように 適宜行を変えています (大きなletやcondを単位として)。 Cで得た習慣かな。Lisperの中には最後の閉じ括弧の連続を 全然気にしない人もいるようで(スーパー括弧なんてのもありましたね)。
- 戯 2003/01/07 05:19:46 PST 閉じ括弧連続を気にしないのってPythonみたいだと思うのは俺だけでしょか?C系言語(だけ)に慣れちゃった自分には付いていけないノリかも。それでなくても括弧対応を追うのが辛いと思うのに。
- ところで上の皆さんの主張は、括弧の位置は機械的(文脈自由(笑))に決めるだけじゃなく意味的(文脈依存)に決める場面もあるぞ、ということですよね。"意味"を未だに知らない(この正月にも結局Lisp/Scheme覚えなかったし)俺としてはコレは結構驚異的です。やっぱり慣れてくると特定の単語だけが浮かび上がったり輝いたり(笑)して見えてくるのかな…
- fuyuki 2003/01/09 02:06:18 PST Lisperが変態の一種だというのは同感です(だれもそんなこと言ってない)。 ちなみに私はLisperでもSchemerでもありませんが、閉じ括弧の連続は気になりません。 むしろ閉じ括弧がたくさん続くと豊かな気持ちになります。 雪がたくさん降ると豊かな気持ちになるのに似てなくもない。
- nobsun 2003/01/09 17:19:06 PST 閉括弧の連続は、これで終りというよろこびがあるので、私もあまり気になりませんが、SXMLなんかのときにはカラム位置を対応する開括弧に合せたくなるような気がします。
- ローカルな束縛を使って読みやすくしよう、とかどれぐらい意識するものですか? let を書きすぎるとインデントが深くなりすぎるような気もしますが
- let* か letrec を使いませう。
- 多値の束縛もしたいときにはちょっと憂鬱。
- let*-values (srfi-11)。括弧が多いのが欠点。
- 多値の束縛もしたいときにはちょっと憂鬱。
- let* か letrec を使いませう。
- 僕は師匠に教わった、括弧は同じ行でないものは間にスペースを入れる方法を使っています。こんな感じで、
(define (hoge x) (hugahuga ...) )