Fork me on GitHub
#malli
<
2021-02-25
>
steveb8n07:02:23

Q: can I use Malli inside a babashka script? (i.e. does it run inside sci?)

steveb8n07:02:52

this would be useful for validating tools.cli parsed options

borkdude07:02:17

Not yet, but feel free to post an issue, so we can gather community feedback. Meanwhile there are two options: spartan.spec and minimallist both work

borkdude08:02:56

I think it will be quite a lot of work to port malli to bb compatible code. @ikitommi What is the API stability of malli? At one point we could add it to bb proper perhaps. I want to consider this, but also want some feedback from the "bb community" on this

ikitommi08:02:02

Very stable. the named-schemas will change, but won’t change after release.

ikitommi08:02:48

public api has been immutable for 6+ months, no plans on breaking.

steveb8n08:02:33

thanks guys. since it’s only tools.cli in CI (i.e. almost never changes), I can write a custom vaildator instead

steveb8n08:02:13

cool. I’ll give it a try. thx

steveb8n08:02:23

I like that it explicitely does not try to be fast. probably means they can deliver features faster

steveb8n08:02:55

unlike Malli where I’m getting value out of the perf vs spec. perf is a feature

borkdude08:02:37

perf is a feature and the style of writing code (reifying Java classes) isn't well supported by bb from source for any Java class :)

borkdude08:02:06

Well, it is supported for records, etc, but definitely not fast

ikitommi08:02:32

malli reifies just protocols, does that work ok?

ikitommi08:02:54

is there something that we could do on malli side to make it work?

steveb8n08:02:00

it’s all tradeoffs. I’m glad Tommi likes fast things 🙂

👍 1
borkdude08:02:22

it does work:

$ bb -e '(defprotocol Foo) (instance? Foo (reify Foo))'
true

borkdude08:02:08

@ikitommi Maybe using reader conditionals helps, #?(:bb :foo :clj :bar)

borkdude08:02:16

for the parts that bb doesn't support

ikitommi08:02:34

sure, happy to add. which parts? 🙂

borkdude08:02:59

try and you will find out :) happy to comment on those parts

ikitommi08:02:05

the protocol cache thing at least? (hacking over dead slow satisfies?)

ikitommi08:02:31

just write a bb script of some malli code and see what breaks?

borkdude08:02:33

yes. in the malli repo, write a script like:

(require '[babashka.classpath :as cp])
(cp/add-classpath "src")
and then
(require '[malli.core :as m])

ikitommi08:02:34

example/test-bed most welcome :)

ikitommi08:02:08

that simple. cool.

ikitommi08:02:58

will add that to our next tech/hack-friday, which is… tomorrow.

🙏 2
ikitommi08:02:22

so, does the :bb conditional work already?

borkdude08:02:23

@ikitommi Actually, this is better:

(ns bb-script)

(require '[babashka.deps :as deps]
         '[clojure.edn :as edn])

(deps/add-deps (edn/read-string (slurp "deps.edn")))

(require '[malli.core :as m])

borkdude08:02:42

@ikitommi Yes, :bb is prioritized over :clj

👍 1
borkdude08:02:49

it's order dependent

borkdude08:02:00

@ikitommi So the first hack I did:

(ns malli.sci
  #[email protected](:bb [] :clj [(:require [borkdude.dynaload :as dynaload])]))

(defn evaluator [options fail!]
  #?(:bb fail! :clj
     (let [eval-string* (dynaload/dynaload 'sci.core/eval-string* {:default nil})
           init (dynaload/dynaload 'sci.core/init {:default nil})
           fork (dynaload/dynaload 'sci.core/fork {:default nil})]
       (fn [] (if (and @eval-string* @init @fork)
                (let [ctx (init options)]
                  (fn eval [s] (eval-string* (fork ctx) (str s))))
                fail!)))))

borkdude08:02:33

The nest error:

----- Error --------------------------------------------------------------------
Type:     java.lang.Exception
Message:  Unable to resolve classname: java.util.ArrayDeque
Location: /private/tmp/malli/src/malli/impl/regex.cljc:35:3

----- Context ------------------------------------------------------------------
31:   recognition for `validate`."
32:
33:   (:refer-clojure :exclude [+ * repeat cat])
34:   (:require [malli.impl.util :as miu])
35:   #?(:clj (:import [java.util ArrayDeque])))
      ^--- Unable to resolve classname: java.util.ArrayDeque

borkdude08:02:46

So we don't have that class in bb (yet)

borkdude08:02:36

I see that for cljs you didn't use it

borkdude08:02:07

I guess for bb you can take the :cljs branches there

borkdude08:02:47

Then after doing that, I run into:

(deftype ^:private CacheEntry [^long hash f ^long pos regs])

borkdude09:02:26

There are other deftypes in regex.cljc

ikitommi09:02:47

what would be a good workaround for those?

ikitommi09:02:59

or, will bb support those at some point?

ikitommi09:02:43

good to understand how to create bb-compatible code for the future.

borkdude09:02:53

So these are the types of things that are not supported yet. Classes can be added, but deftype isn't supported. I don't know what to do with deftype yet. Records are "faked" using normal maps + metadata.

borkdude09:02:11

I have seen a similar thing with meander vs matchete. Matchete is written in a "simple" Clojure style so it just works with bb out of the box. https://github.com/xapix-io/matchete Where meander focuses on performance and this results in incompatible code.

borkdude09:02:01

Meander now has an "interpreter" which is compatible with bb which also more flexible than the macro style enforced in the main lib

borkdude12:02:39

Is there a way to get the humanized string for a schema, without any input?

(prn (me/humanize [:< 100]))
;;=> "should be smaller than 100"

ikitommi14:02:30

oh, with nil value you mean?

borkdude14:02:31

with no value at all

borkdude14:02:42

it seems that the returned string doesn't depend on the input

ikitommi14:02:16

you can either:

[:int {:max 100}]
or:
[:< {:error/message "should be smaller than 100"} 100]

ikitommi14:02:27

there are set of "type" schemas, which read properties: :int, :string , ...

ikitommi14:02:01

might map all predicates schemas into these internally, which would make things like JSON Schema transformations & normal transformations simpler, need just to be defined for the base type, not to all predicates.

ikitommi14:02:59

pos-int? -> [:int {:min 1}]...

borkdude14:02:14

@ikitommi What I mean is: you can get a string from humanize but this needs some input too which you first have to validate. However, the resulting string doesn't seem to be related to the input at all. This made me wonder: is it possible to get some "model" error message from a schema only, without validating anything

borkdude14:02:36

This can then be passed to the second arg of the :validate tuple in tools.cli

borkdude14:02:14

Not that important, I can make the string itself manually or by validating some dummy input

ikitommi16:02:59

malli supports both fixed error messages and functions generating error messages, the two examples here: https://github.com/metosin/malli/blob/master/src/malli/error.cljc#L62-L68

ikitommi16:02:32

... and messages can be localized. So, no simple way to do without value.

ikitommi16:02:43

there are helpers in malli.error to pull out the error message/fn out of a schema, but value is needed for the fn case

ikitommi16:02:36

also, one schema can emit many different errors, humanized by the error type. Maps emit extra-keys, missing-keys, missspelled-keys, etc.