2314


例えば次のような関数を定義したとして、渡されたコレクションと同じ型を返す良い方法はないでしょうか？
ただし、評価値の順序がおかしくならないようにお願いします。

[code]

    (defn rotate [n coll]
      (let [at (if (neg? n) (Math/abs n) (- (count coll) n))
           [left right] (split-at at coll)]
        (concat right left)))
    
[/code]

こんなやりかたを考えてみましたが、ごちゃごちゃして気持ち悪いですよね。 何かシンプルな方法はないでしょうか？

[code]

    (defmulti vomit (fn [coll]
                      (if (list? coll) :list (if (vector? coll) :vector))) :default nil)
    (defmethod vomit :list [coll] (partial apply list))
    (defmethod vomit :vector [coll] (partial into (empty coll)))
    (defmethod vomit nil [coll] identity)
    
    (defn rotate [n coll]
      (let [at (if (neg? n) (Math/abs n) (- (count coll) n))
            [left right] (split-at at coll)]
        ((vomit coll) (concat right left))))
    
[/code]


CC BY-SA 3.0
2014-12-21T03:48:32.380
2307
2014-12-25T05:15:42.730
2014-12-21T12:11:25.330
5401
5401
question
clojure
Clojureの関数で渡されたものと同じ型のコレクションを返すには？


以下の様な書き方はいかがでしょうか。

[code]

    (defn lisptype [x]
      (cond
        (list? x) list
        (vector? x) vector
        (map? x) hash-map
        (string? x) (fn [& x] (clojure.string/join x))
        true list))
    
    (defn rotate [n coll]
      (let [at (if (neg? n) (Math/abs n) (- (count coll) n))
           [left right] (split-at at coll)]
      (apply (lisptype coll) (concat right left))))
    
[/code]

※ map を渡すと妙な感じになりますが…

clojure の `type function` が Java の型ではなくて clojure 本来の型(list や
vector)を返してくれればもう少し簡潔に書くことができるのですが…。

[code]

    (apply (type coll) (concat right left))
    
[/code]

追記:

rizaudo さんの回答を参考にさせていただいて、lisptype function を書き換えました。map に関しては array-map
のみの対応で、set に関しては [Are there sets that keep the insertion
order?](https://groups.google.com/forum/#!topic/clojure/vYND1CtKf2M) に記載されている
order-preserving-set function を拝借しました(少し長いのでここには載せていません)。

[code]

    (defn lisptype [x]
      (cond
        (list? x) list
        (vector? x) vector
        (set? x) order-preserving-set
        (map? x) (fn [& x] (apply array-map (flatten x)))
        (string? x) (fn [& x] (clojure.string/join x))
        :else list))
    
[/code]

言わずもがなですが、rotate function に渡す set 型データは order-preserving-set function
を使って作成する必要があります。


CC BY-SA 3.0
2014-12-21T07:03:02.317
2314
2014-12-21T15:36:29.730
2014-12-21T15:36:29.730
2307
answer


Clojure標準には、コレクションにジェネリックに適用できる関数がそれほど多くないため、コレクションがシーカブルであることを利用してシーケンス関数を使うケースが多いですね。
その場合、結果として得られる値がシーケンスになってしまうため、入力のコレクション型を保存したい場合には不便です。

`empty`関数を使うと、引数に与えたコレクションと同じ型の空のコレクションが得られるので、型を保存したい場合にはこれと`conj`や`into`を組み合わせて使うのが比較的便利な方法です。

[code]

    (defn rotate [n coll]
      (let [at (if (neg? n) (Math/abs n) (- (count coll) n))
            [left right] (split-at at coll)]
        (-> (empty coll) (into right) (into left))))
    
[/code]


CC BY-SA 3.0
2014-12-25T05:15:42.730
2538
2014-12-25T05:15:42.730
496
2307
answer