This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-12-06
Channels
- # adventofcode (99)
- # announcements (9)
- # aws (3)
- # babashka (22)
- # beginners (90)
- # boot (2)
- # calva (22)
- # cider (8)
- # clj-kondo (14)
- # cljsrn (20)
- # clojure (24)
- # clojure-europe (4)
- # clojure-italy (3)
- # clojure-losangeles (1)
- # clojure-nl (83)
- # clojure-spain (1)
- # clojure-spec (46)
- # clojure-uk (43)
- # clojuredesign-podcast (70)
- # clojurescript (40)
- # cursive (25)
- # datomic (9)
- # duct (3)
- # emacs (14)
- # figwheel-main (2)
- # fulcro (61)
- # graalvm (8)
- # juxt (7)
- # kaocha (2)
- # leiningen (19)
- # luminus (5)
- # malli (58)
- # off-topic (4)
- # re-frame (11)
- # reitit (5)
- # rewrite-clj (3)
- # shadow-cljs (63)
- # sql (5)
- # testing (5)
- # tools-deps (26)
- # uncomplicate (2)
- # vim (4)
But I was able to do that in the old system as well. My transform function was simply: call the child transformer and then finalize and return a value.
@roklenarcic could you show example how do you call the child transformer from a transformer?
the lib is at pre-alpha , so we can still rollback anything if there is unneeded complexity around
Assuming you have in -into-schema
schema' (-> children first (m/schema opts))
(-transformer [this transformer context]
(let [child-xf (m/-value-transformer transformer schema' context)]
(if child-xf
(if (= :decode context)
(comp child-xf xf)
(comp xf child-xf))
xf)))
example: you want to transform map after the keys have been renamed, using transformed keys. This is a good place to do it in leave.
you can create a transformer like this:
my enter logic
call child
my leave logic
the difference is that in the interceptor pattern, calling the child (i.e. next in chain) is handled by the function that runs the whole thing
but in http request/response interceptors that code is doable because it is obvious how to handle calling the next interceptor in chain
there is 0 or 1 next interceptors
in malli, you have maps and vectors and custom types, which means you cannot have standard function which will invoke next interceptors and combine results
the current interceptor runner is quite simple, but my idea was to use next version of sieppari, on which, the chain is a vector of IntoInterceptor
instances, e.g. functions, maps or Interceptor Instances. The runner composes the (optimized) chain. This allows things like visual chain debugging, good perf and overall, and reuse.
Are you talking about multiple interceptors on same schema?
manual chaining is not that handy if you want to inline the transformation via schema properties
or are you talking about composing say an interceptor on :sequential
schema with those on child schemas
Those are serializable?
#()
is serializable?
I never knew
as said, the biggest difference is that in request/response interceptor chains, they are strictly linear
:map-of
and :vector
combine enter and exit function in non-linear maner
yes, when writing -transformer
function you would expect that with interceptors one doesn’t have to do the composing with the next interceptor in chain
but you still have to manually write -transformer
function to compose your interceptor with the others
e.g. in :vector
you need to write code that will return a map {:enter … :leave ...}
such that it correctly composes its enter/leave logic with the elements enter/leave logicks
I guess I’ll see where this is going
@rschmukler yesterday I hit this snag https://clojurians.slack.com/archives/CLDK6MFMK/p1575567535304400
you made a patch
I found another problem
It was @rschmukler's patch
(m/encode [:sequential {:encode/string (constantly {:leave #(clojure.string/join "," %)})}
string?]
["A" "B" "C"]
mt/string-transformer)
Error printing return value (NullPointerException) at clojure.core/map$fn (core.clj:2755).
null
so if I wrap it in :leave
I get NPE
I know it was his patch that’s why I @
him
please report all issues, clearly need to fix or rethink this. If you have suggestions how to make the transforming good with/without interceptors, while supporting the schema property based extension (serializable), I'm all ears.
my question before thinking about this is what is the desired logic for this part:
• if multiple transformers define encoder/decoder for same symbol/schema what is desired, last one wins?
• if schema use-site property map defined encode
decode
keys, what is the desired interaction with existing encode decode operation already defined by schema type itself, full override? does it also override the construction in -transformer
?
@roklenarcic Try the latest of that patch. Fixed that issue.
That was actually a lingering issue from a work-around that I did because fwrap
wasn't skipping on non-collection input. Should be good now
my 2 cents:
• transformer should not have only one -transformer-name
, but instead, a chain of names. strip-extra-keys
should have an unique name
• with mt/transformer
, one can create a chain of transformations, wehre both names and encoders & decoders are chained together and the chain of names is used for schema property based lookups. Any schema-defined will override the one defined in the transformer
• encoders and decoders are defined as a transformation function of type schema opts => IntoInterceptor
. This allows any transformation function to access the schema in question at “compile time”, allowing fast “drop extran keys” etc.
• interceptor is to describe a transformation step. There is a IntoInterceptor
Protocol, which is extended for a function (maps to :enter
), a map, Interceptor Record or a chain of IntoInterceptor
. Malli could use sieppari later, as it already defines all of these.
In most cases, users don’t have to worry anything about the interceptor machineny, just use plain functions and it works. When you need more batteries on what happens when, you can either a) start using interceptors (enter & leave) b) add more steps into the transformation chain c) both
Simplest thing:
(m/decode
[string? {:decode/string '(constantly #(str "olipa_" %))}]
"kerran" mt/string-transformer)
; => olipa_kerran
With Interceptor:
(m/decode
[string? {:decode/string '(constantly {:enter #(str "olipa_" %)
:leave #(str % "_avaruus")})}]
"kerran" mt/string-transformer)
; => "olipa_kerran_avaruus"
A custom transformaton chain:
(m/decode
[:and
{:decode/before '(constantly inc)
:decode/after '(constantly (partial * 2))}
int?]
1
(mt/transformer {:name :before} {:name :after}))
; => 4
both:
(m/decode
[:and
{:decode/before '(constantly {:enter inc
:leave (partial * 2)})
:decode/after '(constantly {:enter (partial * 4)
:leave inc})}
int?]
1
(mt/transformer {:name :before} {:name :after}))
; => 18
using a chain:
(m/decode
[:and
{:decode/before '(constantly [inc inc #(* % 2) inc])}
int?]
1
(mt/transformer {:name :before} {:name :after}))
; => 7
As we’ll soon lose the Slack History, wrote it down as issue: https://github.com/metosin/malli/issues/136
also, idea to remote the schema opts => interceptor
intermediate step in favor of reitit-style interceptors that have a separate compile-step. Sounds even more complex, but actually would simplify things.
with it, the simple case:
(m/decode
[:and
{:decode/after 'inc}
int?]
"1"
(mt/transformer {:name :before} mt/string-transformer {:name :after}))
; => 2
the complex case:
;; mounts only if there :multiplier property in schema
(def multiply-interceptor
{:name ::multiply
:compile (fn [schema _]
(if-let [multiplier (:multiplier (m/properties schema))]
(partial * multiplier)))})
;; the interceptor does not mount as the `:multiplier` is missing:
(m/decode
[:and
{:decode/after multiply-interceptor}
int?]
"2"
(mt/transformer {:name :before} mt/string-transformer {:name :after}))
; => 2
(m/decode
[:and
{:decode/after multiply-interceptor
:multiplier 10}
int?]
"2"
(mt/transformer {:name :before} mt/string-transformer {:name :after}))
; => 20