restとnextの使い分けについて


今読んでいる参考書(clojure in action)で以下の関数が例示してありました。

[code]

    user=> (defn print-seq [s]
             (when (seq s)
               (prn (first s))
               (recur (rest s))))
    
[/code]

その本によると、この例では`seq`で明示的にシーケンスの中身の有無をチェックしているので、`recur`の引数では`next`ではなく`rest`を使うべきだと書かれていました。

`rest`と`next`については以下のような違いがあります。

[code]

    user=> (rest [])
    ()
    user=> (rest nil)
    ()
    user=> (next [])
    nil
    user=> (next nil)
    nil
    
[/code]

見ての通り、`rest`であれば引数のシーケンスが空だろうと`nil`だろうと空リストを返すという違いがあります。

一方、`seq`は引数で`nil`を受け取っても結局`nil`を返します。

[code]

    user=> (seq [])
    nil
    user=> (seq nil)
    nil
    
[/code]

ここまで理解してみて、少し困ったことになりました。  
ここまでの理解をそのままつなげると、上記の例では、関数の引数は`nil`であろうとシーケンスであろうと、どちらでも構わないように思われます。つまり`rest`を`recur`の引数として積極的に推奨するほどの理由が分かりません。

[code]

    user=> (print-seq nil)
    nil
    user=> (print-seq [])
    nil
    user=> (print-seq [1 2])            
    1
    2
    nil
    
[/code]

ためしに`next`版も作ってみました。

[code]

    user=> (defn print-seq2 [s]
             (when (seq s)
               (prn (first s))
               (recur (next s))))
    #'user/print-seq2
    user=> (print-seq2 [])
    nil
    user=> (print-seq2 nil)
    nil
    user=> (print-seq2 [1 2])
    1
    2
    nil
    
[/code]

実質的に何も動作に変わりがないように思われます。

例が良くないだけではないかという気もしているのですが、わざわざ「`next`よりも`rest`を使いなさい」と識者が書いているくらいなのだからそれには理由があるのだと思います。  
だったらその人に聞けば良いではないかと言われそうで恐縮ですが、もし一般的にこのアドバイスが当てはまるパターンがあるとしたら、それはどのような場面においてなのでしょうか？あるいは慣習的なルールがあるのでしょうか？もしそういうのがあれば教えてください。

clojureはバージョン1.8です。




あくまで想像ですが、`()`がコレクションなのに対し`nil`はコレクションではないので、コレクションを引数とする`seq`に`nil`を与えるのが（気分的に）筆者さんの好みに合わないのかなと。

[code]

    user=> (coll? ())
    true
    user=> (coll? nil)
    false
    
[/code]

ちなみに、公式APIドキュメントの`seq`の項で「(seq nil) returns
nil.」と別記されているように、`seq`の実装を見ると、引数が`nil`だったときにはコレクションとしてではなく特別扱いで`nil`を返しているようです。




以下は直接の回答ではありませんが、参考程度にはなるかと思います。

[code]

    user=> (when '() true)
    true
    user=> (when nil true)
    nil
    
[/code]

`'()`(空リスト)は `true`, `nil` は `false` として扱われますので、`print-seq` 関数が以下の様になっている場合は
`next` を使う事になります。ここで `rest` を使ってしまうと引数が `nil` 以外の場合は無限ループになってしまいます。

[code]

    (defn print-seq [s]
      (when s
        (prn (first s))
        (recur (next s))))
    
[/code]

cul8er さんが書かれている通り、`(seq s)` とする場合は `'()` が `nil` に変換されるので `next` でも `rest`
でも結果は同じになります。

**補遺**

`next` と `rest` はソースコード上では以下の様になっています。

# clojure1.6 のソースコードなので、1.8 とは異なるかもしれません

**clojure1.6/src/clj/clojure/core.clj**

[code]

    (def
     ^{:arglists '([coll])
       :tag clojure.lang.ISeq
       :doc "Returns a seq of the items after the first. Calls seq on its
      argument. If there are no more items, returns nil."
       :added "1.0"
       :static true}  
     next (fn ^:static next [x] (. clojure.lang.RT (next x))))
    
    (def
     ^{:arglists '([coll])
       :tag clojure.lang.ISeq
       :doc "Returns a possibly empty seq of the items after the first. Calls seq on its
      argument."
       :added "1.0"
       :static true}  
     rest (fn ^:static rest [x] (. clojure.lang.RT (more x))))
    
[/code]

`next` と `rest` は `clojure.lang.RT` クラスの `next()` と `more()`
メソッドに対応している事が分かります。

**clojure1.6/src/jvm/clojure/lang/RT.java**

[code]

    static public ISeq next(Object x){
      if(x instanceof ISeq)
        return ((ISeq) x).next();
      ISeq seq = seq(x);
      if(seq == null)
        return null;
      return seq.next();
    }
    
    static public ISeq more(Object x){
      if(x instanceof ISeq)
        return ((ISeq) x).more();
      ISeq seq = seq(x);
      if(seq == null)
        return PersistentList.EMPTY;
      return seq.more();
    }
    
[/code]

`PersistentList.EMPTY` は言葉通りで空リスト(`()`)の事です。また、それぞれの型(`Cons` や `ASeq` 型など)の
`more()` や `next()` メソッドを呼び出していますが、処理内容は同様です。

**clojure1.6/src/jvm/clojure/lang/Cons.java**

[code]

    public ISeq next(){
      return more().seq();
    }
    
    public ISeq more(){
      if(_more == null)
        return PersistentList.EMPTY;
      return _more;
    }
    
[/code]

この `_more` 変数は cons cell の `cdr(rest)` 部に相当します(`Cons.java` の冒頭を参照して下さい)。

つまり、`(next ...) == (seq (rest ...))` となっています。

