23681


今読んでいる参考書(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です。


CC BY-SA 3.0
2016-03-30T08:48:20.230
23620
2016-04-03T13:09:33.067
9403
question
clojure
restとnextの使い分けについて


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

[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 ...))` となっています。


CC BY-SA 3.0
2016-03-30T14:20:26.030
23628
2016-04-02T03:09:21.113
2016-04-02T03:09:21.113
23620
answer


以下の挙動が違うみたいでした。

[code]

    (def a
      (map prn (range)))
    (def a' (rest a))
    ;; 0 だけ表示される
    
    (def b
      (map prn (range)))
    (def b' (next a))
    ;; 0 と 1 が表示される
    
[/code]

def は、式をそのまま REPL に入れたりすると  
評価してしまうので、それを防ぐ為に使っています。

でも、その本が言っている事とは関係なさそうですね。

評価(realize)に関わる挙動なので、  
副作用がないとか、  
上の prn の代りに凄く時間がかかる処理でなければ  
関係なさそうです。

ドキュメントに書いてないので、  
その内挙動は変わるかもしれないですが。

# 環境

[code]

    (clojure-version) ;=>"1.7.0"
    (System/getProperty "java.version") ;=> "1.8.0_20"
    
[/code]


CC BY-SA 3.0
2016-04-02T04:07:28.897
23680
2016-04-02T06:15:14.813
2016-04-02T06:15:14.813
15007
15007
23620
answer


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

[code]

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

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


CC BY-SA 3.0
2016-04-02T05:53:11.707
23681
2016-04-02T05:53:11.707
4010
23620
answer


書いてある言葉をあまり細かく考えると  
揚げ足取りみたいになっちゃうかもしれませんが

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

この文章だけで考えると `clojure in action` を書いた人は、  
seq を使うと「明示的」にシーケンスの中身の有無をチェックしていて、  
使わなかったら「明示的」じゃないと思っているのかなと思います。

でも、「明示的」っていうのは、個人の感覚的な問題だと思います。  
[seq のドキュメント](http://clojuredocs.org/clojure.core/seq)には

> If the collection is empty, returns nil.

とありますが、[next のドキュメント](http://clojuredocs.org/clojure.core/next)にも

> If there are no more items, returns nil.

とあって、これを根拠にするなら両方明示的だと思いますし。

あと明示的はどうかは分かりませんが、  
僕が個人的に分かりやすいと思うのはこれです。

[code]

    (defn print-seq [s]
      (when-let [[f & r] s]
        (prn f)
        (recur r)))
    
[/code]

Clojure使っている人全員の支持が得られるとは思いませんが、  
一定数は支持してくれる気がします。

> わざわざ「nextよりもrestを使いなさい」と識者が書いているくらいなのだからそれには理由があるのだと思います。

疑問を持つ事は大切だと思いますが、  
この考え方は場合によっては捨ててもいいのかなと思います。

というのも、Clojure自体がある程度バージョンアップも頻繁で  
進行形だからです。  
<http://clojournal.com/entry/56a731b821d1fd000328b2da>  
半年で 1.7.0 → 1.8.0 になっているのでハイペースなのかと思います。  
C, C++ と比べるとすごく速いと思います。

だから、そんなにガッチリしてなくて  
Clojure っていうのは、こんなもんなんだって  
ぐらいに考えておいて良いんじゃないでしょうか?  
でも、関数型言語? とかに興味を持つ様な人からすると、  
こういうのはキモく感じるかもしれませんが。

「こんなもん」っていう事の例として、僕が知っている事を1つ上げると  
ご存知かもしれませんが nil punning というのがあって  
<https://github.com/bbatsov/clojure-style-guide#nil-punning>  
<https://github.com/totakke/clojure-style-guide#nil-punning>  
ここに、上の print-seq と全く同じ例あるのですが、  
なんか理由は分からないけど

[code]

    (when-not (empty? s)
      ...)
    
[/code]

より

[code]

    (when (seq s)
      ...)
    
[/code]

の方がいいよね☆  
っていう事になっている風潮があるっぽいです。

ググるといろいろ出てくると思いますが、  
困惑している人も多いみたいです。  
<https://github.com/bbatsov/clojure-style-guide/issues/22>  
<https://github.com/bbatsov/clojure-style-guide/issues/44>  
これを読んだ感じだと  
よく分かんないけど Rich Hickey 氏 が推してる  
ってだけなのかなと思います。

僕は `empty?` 使った方が分かりやすいと思いますが。  
どう見ても true か false を返しそうな感じがしますから


CC BY-SA 3.0
2016-04-03T13:09:33.067
23702
2016-04-03T13:09:33.067
15007
23620
answer