Last active
January 12, 2021 17:18
-
-
Save victoraldecoa/4be264bde2c1bb9188e8c9b698da08e4 to your computer and use it in GitHub Desktop.
Walk Tutorial
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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