Skip to content

Instantly share code, notes, and snippets.

@victoraldecoa
Last active January 12, 2021 17:18
Show Gist options
  • Save victoraldecoa/4be264bde2c1bb9188e8c9b698da08e4 to your computer and use it in GitHub Desktop.
Save victoraldecoa/4be264bde2c1bb9188e8c9b698da08e4 to your computer and use it in GitHub Desktop.
Walk Tutorial
(ns walk-tutorial.core
(:require [clojure.pprint :as p]
[clojure.walk :as w]))
; estamos recebendo como input um mapa com strings ao invés de symbols
; como chaves
(def tree-map {"a" {"b" {"d" 4
"e" 5}
"c" 3}
"f" 6})
; a
; b c
; d e
; precisamos gerar outro mapa com as chaves como symbols!
; como iteramos um mapa?
; vamos começar sem modificar, apenas imprimir o que estamos iterando
; tentativa super naive, fazer um map sobre o mapa
(println "map naive")
(println "Result:" (into {} (map (fn [[key val :as entry]]
(p/pprint entry)
[(symbol key) val]) tree-map)))
(println)
; Iterated: [:a {:b {:d 4, :e 5}, :c 3}]
; recebeu apenas dois map entries
; ["a" {"b" {"d" 4, "e" 5}, "c" 3}]
; ["f" 6]
; problema: map não é recursivo
; walk é uma solução!
; o que temos nesse namespace?
; temos uma fn chamada walk, mas ela por si não é recursiva!
; ela é apenas uma função auxiliar
;(w/walk identity identity tree-map)
; ela pode receber qualquer tipo de objeto e vai executar suas funções inner e outer de acordo com o tipo
;
; então quais funções usar?
;
; prewalk [f form] e postwalk [f form]
; funciona igual map, mas iterando recursivamente uma árvore profunda
; de um hash-map. Ele itera da esquerda pra direita raiz primeiro collections primeiro.
; prewalk faz isso executando f na form diretamente e passando identity como outer
; postwalk é igual, mas executa folhas prmeiro. Ela faz isso passando o objeto puro
; para walk e f como outer, deixando que walk a execute na forma
(println "prewalk map (collection forms first)")
; como estamos retornando o próprio argumento, a função não vai alterar nada
(w/prewalk (fn [x] (println x (class x)) x) tree-map)
;{a {b {d 4, e 5}, c 3}, f 6} clojure.lang.PersistentArrayMap
;[a {b {d 4, e 5}, c 3}] clojure.lang.MapEntry
;a java.lang.String
;{b {d 4, e 5}, c 3} clojure.lang.PersistentArrayMap
;[b {d 4, e 5}] clojure.lang.MapEntry
;b java.lang.String
;{d 4, e 5} clojure.lang.PersistentArrayMap
;[d 4] clojure.lang.MapEntry
;d java.lang.String
;4 java.lang.Long
;[e 5] clojure.lang.MapEntry
;e java.lang.String
;5 java.lang.Long
;[c 3] clojure.lang.MapEntry
;c java.lang.String
;3 java.lang.Long
;[f 6] clojure.lang.MapEntry
;f java.lang.String
;6 java.lang.Long
(println)
; como podemos ver, x vai receber tipos diferentes. Vc geralmente vai precisar
; filtrar para o tipo que vc quer modificar
; postwalk é exatamente igual exceto que folhas dos values executam primeiro
; mas cuidado! a ordem das keys continua sendo raiz primeiro
(println "postwalk map (pure forms first)")
(println "postwalk result"
(w/postwalk (fn [x] (println x (class x)) x) tree-map))
(println)
;a java.lang.String
;b java.lang.String
;d java.lang.String
;4 java.lang.Long
;[d 4] clojure.lang.MapEntry
;e java.lang.String
;5 java.lang.Long
;[e 5] clojure.lang.MapEntry
;{d 4, e 5} clojure.lang.PersistentArrayMap
;[b {d 4, e 5}] clojure.lang.MapEntry
;c java.lang.String
;3 java.lang.Long
;[c 3] clojure.lang.MapEntry
;{b {d 4, e 5}, c 3} clojure.lang.PersistentArrayMap
;[a {b {d 4, e 5}, c 3}] clojure.lang.MapEntry
;f java.lang.String
;6 java.lang.Long
;[f 6] clojure.lang.MapEntry
;{a {b {d 4, e 5}, c 3}, f 6} clojure.lang.PersistentArrayMap
;postwalk result {a {b {d 4, e 5}, c 3}, f 6}
; como usar na prática?
; vamos transformar as chaves do mapa de string pra keyword!
(println "chaves como keyword, filtrando por string")
(p/pprint (w/postwalk (fn [x] (if (string? x) (symbol x) x)) tree-map))
;{a {b {d 4, e 5}, c 3}, f 6}
(println)
; qual o problema dessa função? se tiver strings no valor, ela vira keyword também
(def tree-map' {"a" {"b" {"d" 4
"e" 5}
"c" 3}
"f" "g"})
(println "quando tem string como valor, quebra")
(p/pprint (w/postwalk (fn [x] (if (string? x) (symbol x) x)) tree-map'))
;{a {b {d 4, e 5}, c 3}, f g}
(println)
; para corrigir isso, ao invés de iterarmos pelas strings, vamos iterar por
; hashmap. Se map?, a gente retorna o hashmap só com as keys modificadas de string pra keyword
(println "solução: filtrar por map?")
(p/pprint
(w/postwalk
(fn [x] (if (map? x)
(into {} (map (fn [[key val]] [(symbol key) val]) x))
x)) tree-map'))
;{a {b {d 4, e 5}, c 3}, f "g"}
(println)
; agora funcionou. Repare também que acabamos usando exatamente a
; mesma função não recursiva que utilizamos acima, para iterar por cada um
; dos mapas.
; também é possível obter o mesmo resultado filtrando por map-entry invés
; do map. Fica só um pouco mais verboso.
(println "filtrando por map-entry? também funciona")
(p/pprint (w/postwalk (fn [x]
(if (map-entry? x)
(first {(symbol (key x)) (val x)})
x)) tree-map'))
;{a {b {d 4, e 5}, c 3}, f "g"}
(println)
; Extra:
; E como funciona o walk por dentro?
;; (defn walk
;; "Traverses form, an arbitrary data structure. inner and outer are
;; functions. Applies inner to each element of form, building up a
;; data structure of the same type, then applies outer to the result.
;; Recognizes all Clojure data structures. Consumes seqs as with doall."
;; {:added "1.1"}
;; [inner outer form]
;; (cond
;; (list? form) (outer (apply list (map inner form)))
;; (instance? clojure.lang.IMapEntry form)
;; (outer (clojure.lang.MapEntry/create (inner (key form)) (inner (val form))))
;; (seq? form) (outer (doall (map inner form)))
;; (instance? clojure.lang.IRecord form)
;; (outer (reduce (fn [r x] (conj r (inner x))) form form))
;; (coll? form) (outer (into (empty form) (map inner form)))
;; :else (outer form)))
; o inner nunca vai ser a sua função a ser executada
; sua função será executada dentro de prewalk e o valor da resposta passado pra walk
; ou no caso de postwalk ele vai passar sua função como outer. O inner é quem cria
; a recursão apenas, chamando de volta o prewalk/postwalk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment