Fork me on GitHub
#malli
<
2019-12-06
>
roklenarcic09:12:59

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.

ikitommi09:12:58

@roklenarcic could you show example how do you call the child transformer from a transformer?

ikitommi09:12:39

the lib is at pre-alpha , so we can still rollback anything if there is unneeded complexity around

roklenarcic09:12:48

Assuming you have in -into-schema schema' (-> children first (m/schema opts))

roklenarcic09:12:16

(-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)))

ikitommi09:12:19

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.

roklenarcic09:12:52

you can create a transformer like this:

my enter logic
call child
my leave logic

roklenarcic09:12:23

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

roklenarcic09:12:04

but in http request/response interceptors that code is doable because it is obvious how to handle calling the next interceptor in chain

roklenarcic09:12:19

there is 0 or 1 next interceptors

roklenarcic09:12:27

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

ikitommi09:12:52

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.

roklenarcic09:12:32

Are you talking about multiple interceptors on same schema?

ikitommi09:12:37

manual chaining is not that handy if you want to inline the transformation via schema properties

roklenarcic09:12:11

or are you talking about composing say an interceptor on :sequential schema with those on child schemas

ikitommi09:12:46

added some tests to document how the chaining works now. Grep for :enter

roklenarcic09:12:21

Those are serializable?

roklenarcic09:12:40

#() is serializable?

ikitommi09:12:11

Thanks to sci by @borkdude. Just quote them and it works

roklenarcic09:12:50

as said, the biggest difference is that in request/response interceptor chains, they are strictly linear

ikitommi09:12:01

Malli uses a safe/always-terminating subset of Clojure core with sci

ikitommi09:12:28

is non-linear also needed?

ikitommi10:12:01

that's sieppari-based data visualization that we get for free with intercwptors

roklenarcic10:12:17

:map-of and :vector combine enter and exit function in non-linear maner

ikitommi10:12:07

hmm. haven't had time to check out that. Could you please explain?

roklenarcic10:12:25

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

roklenarcic10:12:05

but you still have to manually write -transformer function to compose your interceptor with the others

roklenarcic10:12:51

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

roklenarcic10:12:09

I guess I’ll see where this is going

ikitommi10:12:26

I see. Need to think about this when at computer.

roklenarcic10:12:11

you made a patch

roklenarcic10:12:50

I found another problem

roklenarcic10:12:36

(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

roklenarcic10:12:09

so if I wrap it in :leave I get NPE

roklenarcic10:12:48

I know it was his patch that’s why I @ him

ikitommi10:12:18

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.

roklenarcic10:12:00

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?

rschmukler15:12:15

@roklenarcic Try the latest of that patch. Fixed that issue.

rschmukler15:12:53

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

👍 4
ikitommi20:12:05

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.

ikitommi20:12:40

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

ikitommi20:12:03

Simplest thing:

(m/decode
  [string? {:decode/string '(constantly #(str "olipa_" %))}]
  "kerran" mt/string-transformer)
; => olipa_kerran

ikitommi20:12:18

With Interceptor:

(m/decode
  [string? {:decode/string '(constantly {:enter #(str "olipa_" %)
                                         :leave #(str % "_avaruus")})}]
  "kerran" mt/string-transformer)
; => "olipa_kerran_avaruus"

ikitommi20:12:06

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

ikitommi20:12:00

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

ikitommi20:12:05

using a chain:

(m/decode
  [:and
   {:decode/before '(constantly [inc inc #(* % 2) inc])}
   int?]
  1
  (mt/transformer {:name :before} {:name :after}))
; => 7

ikitommi20:12:44

As we’ll soon lose the Slack History, wrote it down as issue: https://github.com/metosin/malli/issues/136

ikitommi20:12:08

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.

ikitommi20:12:24

with it, the simple case:

(m/decode
  [:and
   {:decode/after 'inc}
   int?]
  "1"
  (mt/transformer {:name :before} mt/string-transformer {:name :after}))
; => 2

ikitommi20:12:45

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

ikitommi20:12:41

… and for the question: > does it also override the construction in -transformer no.