2011-02-17 10 views
6

he el siguiente código para una biblioteca de conversión genérica:Clojure: La generación de funciones de plantilla

(defn using-format [format] {:format format}) 

(defn- parse-date [str format] 
    (.parse (java.text.SimpleDateFormat. format) str)) 

(defn string-to-date 
    ([str] 
    (string-to-date str (using-format "yyyy-MM-dd"))) 
    ([str conversion-params] 
    (parse-date str (:format (merge (using-format "yyyy-MM-dd") conversion-params))))) 

tengo que ser capaz de llamar así:

(string-to-date "2011-02-17") 

(string-to-date "2/17/2011" (using-format "M/d/yyyy")) 

(string-to-date "2/17/2011" {}) 

El tercer caso es un tanto problemático: el mapa no contiene necesariamente la clave :format que es crítica para la función. Es por eso que merge con valor predeterminado.

Necesito tener una docena de funciones similares para las conversiones entre todos los demás tipos. ¿Hay alguna manera más elegante que no requiera que copie y pegue, use merge etc. en cada función?

Idealmente, buscando algo como esto (macro?):

(defn string-to-date 
    (wrap 
    (fn [str conversion-params] 
     (parse-date str (:format conversion-params))) ; implementation 
    {:format "yyyy-MM-dd"})) ; default conversion-params 

... que produciría una función sobrecargada (unario y binario), con tener un binario merge como en el primer ejemplo.

Respuesta

5

Así, para definir esto un poco más estricta, que desea crear una macro que crea convertidor funciona . Una función de conversión es una función con dos aries, un argumento y dos argumentos. El primer argumento para una función de conversión es el objeto a convertir. El segundo argumento es un mapa de opciones que afectará de alguna manera la conversión (como una cadena de formato en su ejemplo).

Se puede especificar un mapa de parámetros predeterminado. Cuando se llama con un argumento, una función de convertidor usará el mapa de parámetros predeterminado. Cuando se llama con dos argumentos, una función de convertidor fusionará el mapa de parámetros predeterminado con el mapa de parámetros pasados, de modo que los parámetros pasados ​​anulen los valores predeterminados si existen.

Llamemos a este convertidor de definición de macros. El convertidor Def tomará tres argumentos, el primero es el nombre de la función que se creará. El segundo es una función anónima de dos argumentos que implementa el convertidor de dos arias, sin la fusión predeterminada de parm. El tercer argumento es el mapa de parm predeterminado.

Algo como esto funcionará:

(defmacro def-converter [converter-name converter-fn default-params] 
    (defn ~converter-name 
    ([to-convert#] 
    (let [default-params# ~(eval default-params)] 
     (~converter-fn to-convert# default-params#))) 
    ([to-convert# params#] 
    (let [default-params# ~(eval default-params)] 
     (~converter-fn to-convert# (merge default-params# params#)))))) 

A continuación, puede usarla como:

(def-converter 
    string-to-date 
    (fn [to-convert conversion-params] 
    (parse-date to-convert conversion-params)) 
    (using-format "yyyy-MM-dd")) 

Pero hay que realizar un cambio en una de sus funciones de ayuda:

(defn- parse-date [str params] 
    (.parse (java.text.SimpleDateFormat. (:format params)) str)) 

Esto se debe a que la macro necesita ser lo suficientemente general como para manejar mapas arbitrarios de parámetros, por lo que no podemos contar. Probablemente haya formas de evitarlo, pero no puedo pensar en uno que no sea más desordenado que simplemente pasarlo a una función auxiliar (o la función anónima que debe pasarse al convertidor def).

+0

¿Por qué necesitamos este 'let' y' eval' aquí (en comparación con mi propia respuesta)? Es posible sin cambiar 'parse -date' - Puedo usar '(parse-date to-convert (: format conversion-params))' en la función anónima. –

+0

Sí, eso es cierto, "tener que" está mal, "pensé que era más limpio pasar el todo el conjunto de parámetros para las funciones auxiliares como un patrón general, pero eso es una cuestión de gusto "es lo que debería haber dicho. Lo único que le otorga let/eval es la capacidad de pasar de alguna forma que se evalúa a un mapa de parámetros , en lugar del propio mapa de parámetros. Como los macro argumentos no se evalúan, debe evaluarlos usted mismo en el cuerpo de la macro. Esto solo le permite especificar (formato de uso "aaaa-MM-dd") a s el mapa predeterminado en lugar de literal. – mblinn

+0

Una nota rápida sobre por qué pensé que pasar el mapa de param a una función de ayuda es más claro como un patrón general: como puedes desestructurar ese mapa como parte del enlace de funciones, tienes una sintaxis agradable y concisa para obtener los parámetros. Esto sería útil para las funciones de conversión que tienen mapas de parm con varias claves. – mblinn

2

clojure.contrib.def/defnk es útil si necesita funciones con argumentos de palabras clave por defecto:

(use 'clojure.contrib.def) 
    ... 

    (defnk string-to-date [str :format "yyyy-MM-dd"] 
    (parse-date str format)) 

    (string-to-date "2011-02-17") 

    (string-to-date "2/17/2011" :format "M/d/yyyy") 
+0

Genial, pero ¿y si el segundo parámetro es un mapa? Parece que podría llamar '(string-to-date" 2/17/2011 ": params {: format" M/d/yyyy}) '... Ahora, ¿y si el mapa tiene como 3 palabras clave que se pueden usar? en el cuerpo, y quiero aceptar la entrada con solo uno de ellos (anulando el valor predeterminado). –

1

Para el registro, esto es lo que me di cuenta más tarde en la noche:

(defmacro defconvert [name f default] 
    `(defn ~name 
    ([v#] (~name v# ~default)) 
    ([v# conversion-params#] (~f v# (merge ~default conversion-params#))))) 

Parece funcionar y generar exactamente la definición que he tenido hasta allí. ¿Es posible con defnk o algún otro mecanismo incorporado, teniendo un mapa de valores predeterminados y aceptando anular algunos pero no necesariamente todos?

Cuestiones relacionadas