Fonctions d'ordre supérieur dans Clojure

Clojure est génial, nous le savons tous, mais ce n'est pas le but. Je me demande Quelle est la façon idiomatique de créer et de gérer des fonctions d'ordre supérieur d'une manière similaire à Haskell. Dans Clojure je peux faire ce qui suit:

(defn sum [a b] (+ a b))

Mais (sum 1) ne renvoie pas de fonction: cela provoque une erreur. Bien sûr, vous pouvez faire quelque chose comme ceci:

(defn sum
  ([a] (partial + a)) 
  ([a b] (+ a b)))

Dans ce cas:

user=> (sum 1)
#<core$partial$fn__3678 clojure.core$partial$fn__3678@1acaf0ed>
user=> ((sum 1) 2)
3

Mais cela ne semble pas être la bonne façon de procéder. Des idées?
Je ne parle pas de la mise en œuvre de la sum Fonction, je parle à un niveau plus élevé d'abstraction. Y a-t-il des modèles idiomatiques à suivre? Certains macro? Est la meilleure façon de définir une macro ou existe t il d'autres solutions?

36
demandé sur rightfold 2011-03-16 20:17:04

3 réponses

Quelqu'un a déjà implémenté ce sur le groupe Clojure. Vous pouvez spécifier combien d'args une fonction A, et il se curry pour vous jusqu'à ce qu'il en obtienne autant.

La raison pour laquelle cela n'arrive pas par défaut dans Clojure est que nous préférons les fonctions variadiques aux fonctions auto-curry, je suppose.

32
répondu amalloy 2011-08-12 21:48:15

J'ai joué un peu avec les fonctions suggérées par amalloy. Je n'aime pas la spécification explicite du nombre d'arguments à utiliser. J'ai donc créé ma macro personnalisée. C'est l'ancienne façon de spécifier une fonction d'ordre élevé:

(defn-decorated old-sum
  [(curry* 3)]
  [a b c]
  (+ a b c))

, C'est ma nouvelle macro:

(defmacro defn-ho
  [fn-name & defn-stuff]
  (let [number-of-args (count (first defn-stuff))]
    `(defn-decorated ~fn-name [(curry* ~number-of-args)] ~@defn-stuff)))

Et c'est la nouvelle manière implicite:

(defn-ho new-sum [a b c] (+ a b c))

Comme vous pouvez le voir, il n'y a aucune trace de (curry) et d'autres choses, définissez simplement votre fonction currifiée comme avant.

Les gars, qu'en pensez-vous? Des idées? Des Suggestions? Au revoir!

Alfedo

Edit: j'ai modifié la macro en fonction du problème amalloy à propos de docstring. C'est la version mise à jour:

(defmacro defhigh
  "Like the original defn-decorated, but the number of argument to curry on
  is implicit."
  [fn-name & defn-stuff]
  (let [[fst snd] (take 2 defn-stuff)
         num-of-args (if (string? fst) (count snd) (count fst))]
    `(defn-decorated ~fn-name [(curry* ~num-of-args)] ~@defn-stuff)))

Je n'aime pas l'instruction if à l'intérieur de la deuxième liaison. Des idées pour le rendre plus succint?

8
répondu Alfredo Di Napoli 2011-03-17 13:37:50

Cela vous permettra de faire ce que vous voulez:

(defn curry
  ([f len] (curry f len []))
  ([f len applied]
    (fn [& more]
      (let [args (concat applied (if (= 0 (count more)) [nil] more))]
        (if (< (count args) len)
          (curry f len args)
          (apply f args))))))

Voici comment l'utiliser:

(def add (curry + 2)) ; read: curry plus to 2 positions
((add 10) 1) ; => 11

Le conditionnel avec le [nil] est destiné à assurer que chaque application assure des progrès vers l'état curry. Il y a une longue explication derrière cela, mais je l'ai trouvé utile. Si vous n'aimez pas ce bit, Vous pouvez définir args comme:

[args (concat applied more)]

Contrairement à JavaScript, nous n'avons aucun moyen de connaître l'arité de la fonction passée et vous devez donc spécifier la longueur que vous attendez. Ce cela a beaucoup de sens dans Clojure[Script] où une fonction peut avoir plusieurs arités.

0
répondu Mario 2016-12-16 21:31:34