web-dev-qa-db-ja.com

Clojureで「apply」を使用する必要があるのはなぜですか?

これは、Rich Hickeyがブログ投稿の1つで言ったことですが、applyを使用する動機がわかりません。助けてください。

ClojureとCLの大きな違いは、ClojureはLISP-1であるため、funcallは不要であり、applyは、ランタイムで定義された引数のコレクションに関数を適用するためにのみ使用されることです。したがって、(apply f [i])は(f i)と書くことができます。

また、彼は「Clojure is LISP-1」とはどういう意味で、funcallは必要ありませんか? CLでプログラミングしたことはありません。

ありがとう

43
unj2

関数に渡す引数の数がわからない場合は、applyを使用しますコンパイル時(申し訳ありませんが、スキームに頼って、Clojure構文をそれほどよく知らない):

(define (call-other-1 func arg) (func arg))
(define (call-other-2 func arg1 arg2) (func arg1 arg2))

コンパイル時に引数の数がわかっている限り、上記の例のように引数を直接渡すことができます。ただし、コンパイル時に引数の数がわからない場合は、これを行うことはできません(まあ、次のようなことを試すことができます)。

(define (call-other-n func . args)
   (case (length args)
      ((0) (other))
      ((1) (other (car args)))
      ((2) (other (car args) (cadr args)))
      ...))

しかし、それはすぐに悪夢になります。ここでapplyが画像に入ります:

(define (call-other-n func . args)
   (apply other args))

リストの最後の引数として指定された引数の数を受け取り、最初の引数として渡された関数をapplyにそれらの値で呼び出します。

52
Dirk

LISP-1およびLISP-2という用語は、関数が変数と同じ名前空間にあるかどうかを示します。

LISP-2(つまり、2つの名前空間)では、フォームの最初の項目は、実際には関数値を持つ変数の名前であっても、関数名として評価されます。したがって、変数関数を呼び出したい場合は、変数を別の関数に渡す必要があります。

SchemeやClojureのようなLISP-1では、関数に評価される変数は初期位置に配置できるため、関数として評価するためにapplyを使用する必要はありません。

40
Chuck

applyは基本的にシーケンスをアンラップし、関数を個別の引数としてそれらに適用します。

次に例を示します。

(apply + [1 2 3 4 5])

これは15を返します。基本的には(+ 1 2 3 4 5)ではなく(+ [1 2 3 4 5])に展開されます。

31
Rayne

applyを使用して、複数の引数で機能する関数を、単一の引数シーケンスで機能する関数に変換します。シーケンスの前に引数を挿入することもできます。たとえば、mapは複数のシーケンスで機能します。この例( ClojureDocs から)は、mapを使用して行列を転置します。

user=> (apply map vector [[:a :b] [:c :d]])
([:a :c] [:b :d])

ここに挿入された引数の1つはvectorです。したがって、applyは次のように展開されます

user=> (map vector [:a :b] [:c :d])

可愛い!

PSベクトルのシーケンスではなく、ベクトルのベクトルを返すには、全体をvecでラップします。

user=> (vec (apply map vector [[:a :b] [:c :d]]))

ここにいる間、vec(partial apply vector)として定義できますが、そうではありません。

LISP-1とLISP-2について:1と2は、特定のコンテキストで名前が示すことができるものの数を示します。 LISP-2では、同じ名前の2つの異なるもの(関数と変数)を持つことができます。したがって、どちらかが有効である可能性がある場合は常に、プログラムを何かで装飾して、どちらを意味するかを示す必要があります。ありがたいことに、Clojure(またはScheme ...)では、名前で1つだけを表すことができるため、そのような装飾は必要ありません。

6
Thumbnail

タイプの適用操作の通常のパターンは、実行時に提供される関数を一連の引数dittoと組み合わせることです。

私はclojureを十分に活用していないため、その特定の言語の微妙な点に自信を持って、その場合にapplyを使用する必要があるかどうかを判断できません。

2
Steve Gilham

適用は、プロトコル、特にスレッドマクロと組み合わせて使用​​すると便利です。私はこれを発見しました。 コンパイル時に&マクロを使用してインターフェイス引数を展開することはできません なので、代わりに予測できないサイズのベクトルを適用できます。

したがって、たとえば、特定のxmlファイルに関するメタデータを保持するレコードとファイル自体の間のインターフェイスの一部としてこれを使用します。

(query-tree [this forms]
  (apply xml-> (text-id-to-tree this) forms)))

text-id-to-treeは、ファイルをxmlジッパーに解析するこの特定のレコードの別のメソッドです。別のファイルでは、query-treeを実装する特定のクエリでプロトコルを拡張し、xml->マクロを介してスレッド化されるコマンドのチェーンを指定します。

(tags-with-attrs [this]
  (query-tree this [zf/descendants Zip/node (fn [node] [(map #(% node) [:tag :attrs])])])

(注:このクエリ自体は、属性を持たないタグに対して多くの「nil」結果を返します。一意の値のクリーンなリストを取得するためにフィルタリングおよび削減します)。

ちなみに、zfはclojure.contrib.Zip-filterを指し、Zipはclojure.Zipを指します。 xml->マクロはclojure.contrib.Zip-filter.xmlライブラリからのもので、私は:use

0
tborg