@yukiya68k

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Emacs Lisp の nconc の使い方

解決したいこと

Emacs Lisp でリストの連結にnconcを使い始めたのですが、初期値がnilだと更新されません。

(setq x '(1))
->(1)
(nconc x '(1))
->(1 1)
x
->(1 1)

(setq x nil)
->nil
(nconc x '(1))
->(1)
x
->nil

やっぱり append 同様 (setq x (nconc x '(1))) と書くしかないのでしょうか?
できれば nil でダメな理由もお願いします。

環境 Windows10 Emacs30.1

0 likes

2Answer

nconc の説明ではこう書かれています。

the last CDR of each of the lists is changed to refer to the following list.

各リストの最後の (コンスセルの) CDR を変更する仕組みなのでコンスセルが存在しないなら変更すべきものも存在しません。 NIL はリストでありながらコンスセルが含まれないという特別な立場なので注意を払う必要があります。

ドキュメントにはあまり詳細が書かれていませんが、引数に NIL があった場合は単に読み飛ばしてそれ以外を (最後のコンスセルの CDR フィールドを変更する形で) 連結するようです。 そうやってリストを連結しても質問の例では x が参照している先は NIL から動かないので NIL のままです。

一般論としては nconc で連結した結果は nconc の返却値から取得すべきです。

破壊的更新をする関数にも二種類あり、質問者が期待しているようなオブジェクトの変更に意味がある関数と、オブジェクトの再利用をするような関数です。 nconc の場合はどちらかというと再利用タイプと考えるべきでしょう。 CDR を書き換えるということ自体にはあまり意味はなく、連結の効率的な実装のためにそうしているに過ぎません。

2Like

Comments

  1. @yukiya68k

    Questioner

    詳細な解説に加えて分析までしていただいてありがとうございます。特にnilについての説明が理解の助けになりました。nconc は後発の関数で append より書きやすくなっていて嬉しい、ということで使い始めたのですが自分の場合は空リストに追加してゆく方法が殆どですので append に戻すことにします。
    リストの連結は加算的な処理なのに、最初がnilのときだけ0に何掛けても0みたいな掛け算かい!という若干の恨み節は残っているものの、nconcの破壊的な処理、はポインタ渡しが使えるというのがわかったので良い経験でした。

  2. append は最後の引数以外はコピーするという仕様なので、後ろに追加するというのを何度も繰り返すような用途では高コストです。 そのへんの事情を私のブログで説明したことがあるので参考資料として提示しておきます。

    ここでは Scheme での説明ですが append については Emacs Lisp でも Common Lisp でも ISLISP でも事情は同じなのでおそらく LISP 系言語の伝統みたいなものがある……というか自然に作ったらそうなるんでしょう。

    nconc のようにオブジェクトを書き換えて繋げていく方法でもリストを最後まで辿るコストは生じるので、リストの最後ではなく頭に追加していって最後に逆転させる方法もよく使われるイディオムです。

    状況によって適切な方法は異なるので append が良いとか nconc が良いとかではなくそれぞれをきちんと理解して使い分けましょうという話ですね。

  3. @yukiya68k

    Questioner

    追記に気づかず、失礼しました。appendの記事、読ませていただいて十分難しかったですが、重い処理だということは理解しましたので、nconcも(setq (nconc しながら使っていこうと思います。あれだけの図版を書けるだけでもビックリです。

This answer has been deleted for violation of our Terms of Service.

Your answer might help someone💌