tutorial online framework example clojure

online - Inicializando elementos de un mapa condicionalmente en Clojure.



clojure web framework (6)

Estoy buscando la mejor manera de evitar condicionalmente agregar un elemento a un mapa cuando se está inicializando / definiendo. En este caso, quiero evitar agregar un elemento a un mapa si el valor de la clave es nulo.

(defn create-record [data] (let [res { :username (data :username) :first-name (get-in data [:user-info :name :first]) :last-name (get-in data [:user-info :name :last]) :gender (get-in data [:user-info :sex]) }]) )

No quiero agregar género al mapa si los resultados del ingreso son nulos (el campo de sexo en los datos no existe). ¿Hay una manera de hacer esto cuando creo el mapa? Podría eliminar todas las claves cuyo valor sea nulo después de crear el mapa pero, en algunos casos, quiero que algunas claves tengan valores nulos y otras no estén en el mapa si tuvieran valores nulos.


Aquí hay un intento:

(defn exclude-nils-for [m kw-set] (apply hash-map (apply concat (remove (fn [[k v]] (and (kw-set k) (nil? v))) m))))

Prueba:

user> (exclude-nils-for {:gender "m" :name "Thomas" :age "24"} #{}) {:age "21", :gender "m", :name "Thomas"} user> (exclude-nils-for {:gender "m" :name "Thomas" :age "24"} #{:name}) {:age "21", :gender "m", :name "Thomas"} user> (exclude-nils-for {:gender "m" :name nil :age "24"} #{:name}) {:age "21", :gender "m"} user> (exclude-nils-for {:gender "m" :name nil :age nil} #{:age}) {:gender "m", :name nil}


La construcción de un mapa y la dissoc de las claves sobre las que desea imponer condiciones basadas en un predicado (¿aquí nil? ) Podría ser el enfoque más sencillo (NB. Esta función solo prueba las claves mencionadas explícitamente como argumentos; las que no se mencionan nunca se eliminan. , si los valores adjuntos a ellos satisfacen el predicado o no):

(defn dissoc-when "Dissoc those keys from m which are mentioned among ks and whose values in m satisfy pred." [pred m & ks] (apply dissoc m (filter #(pred (m %)) ks)))

En el REPL:

user> (dissoc-when nil? {:foo nil :bar true :quux nil} :foo :bar) {:quux nil, :bar true}

Aunque en términos generales, si espera trabajar con muchos mapas que representan entidades del mundo real de algún tipo en particular, es posible que desee ir con los registros, y luego puede omitir todos los valores nil en la etapa en la que extrae valores de su ingrese el mapa, porque los registros, cuando se ven como mapas, siempre parecen incluir las claves correspondientes a sus campos. P.ej

(defrecord Person [username first-name last-name])

Luego puedes factorizar la lógica de las "conversiones de esquema" entre los mapas:

(defn translate-map "Transforms the data map in accordance with the spec in table. Skips nil-valued entries." [data table] (->> table (keep (fn [[to from]] (when-let [from-val (get-in data from)] [to from-val]))) (into {})))

Ahora su función de create-record convierte en una composición de translate-map y map->Person :

(defn create-person [data] (map->Person (translate-map data {:username [:username] :first-name [:user-info :name :first] :last-name [:user-info :name :last] :gender [:user-info :sex]})))

Si prefiere trabajar con mapas regulares, puede usar algo como lo siguiente para obtener resultados equivalentes:

(defn create-person [data] (merge (zipmap [:username :first-name :last-name] (repeat nil)) (translate-map data {:username [:username] :first-name [:user-info :name :first] :last-name [:user-info :name :last] :gender [:user-info :sex]})))

En el REPL (versión de registro en Clojure 1.3):

user> (create-person {:username "jsmith" :user-info {:name {:first "John" :last "Smith"}}}) #user.Person{:username "jsmith", :first-name "John", :last-name "Smith"} user> (create-person {:username "jsmith" :user-info {:name {:first "John" :last "Smith"} :sex :male}}) #user.Person{:username "jsmith", :first-name "John", :last-name "Smith", :gender :male}


Podrías hacer algo como

(let [not-nils #{:gender}] (defn create-record [data] (into {} (for [[k v] {:username (data :username) :first-name (get-in data [:user-info :name :first]) :last-name (get-in data [:user-info :name :last]) :gender (get-in data [:user-info :sex])} :when (not (and (nil? v) (not-nils k)))] [k v]))))


Puedes definir tus campos y cuáles son opcionales:

(def fields [[:username [:username]] [:first-name [:user-info :name :first]] [:sex [:user-info :sex] true]])

y luego escribe una función para usar esa información:

(defn create-record [data keys] (->> (for [[n k ignore-nil?] keys :let [v (get-in data k)] :when (or (not ignore-nil?) v)] [n v]) (into {})))

y funcionará así:

; If :sex is missing don''t create a field user=> (create-record {:username "dr" :user-info { :name {:first "Dave"} }} fields) {:username "dr", :first-name "Dave"} user=> (create-record {:username "dr" :user-info { :name {:first "Dave"} :sex :m }} fields) {:username "dr", :first-name "Dave", :sex :m} ; If :first is missing, create a nil field user=> (create-record {:username "dr" :user-info { :name {} :sex :m }} fields) {:username "dr", :first-name nil, :sex :m}

Modificar según sea necesario :)


Yo usaría una combinación de merge y when-let para estos parámetros opcionales.

La idea central es fusionar ya sea en un mapa de elemento único o nil para cada uno de los parámetros opcionales. La fusión en nil no hará nada, por lo que no verá el nulo en el mapa.

(defn create-record [data] (let [res (merge {:username (data :username) :first-name (get-in data [:user-info :name :first]) :last-name (get-in data [:user-info :name :last])} (when-let [gender (get-in data [:user-info :sex])] {:gender gender}))] res))

Dependiendo de la frecuencia con la que necesite hacer esto, recomendaría escribir una macro corta o una función alrededor de cuándo dejar que el código sea más conciso.


(defn create-record [data] (let [gender (get-in data [:user-info :sex])] (->> {:username (data :username) :first-name (get-in data [:user-info :name :first]) :last-name (get-in data [:user-info :name :last])} (#(if gender (assoc % :gender gender) %)))))