malli

https://github.com/metosin/malli :malli:
steveb8n 2021-02-25T07:17:23.067200Z

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

ikitommi 2021-02-25T08:32:17.070100Z

there is https://github.com/metosin/malli/issues/302. Help most welcome

borkdude 2021-02-25T08:35:56.070400Z

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

ikitommi 2021-02-25T08:37:02.070600Z

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

ikitommi 2021-02-25T08:37:48.070800Z

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

steveb8n 2021-02-25T08:39:33.071Z

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

borkdude 2021-02-25T08:40:33.071200Z

@steveb8n OK. This lib https://github.com/green-coder/minimallist also works with bb btw

steveb8n 2021-02-25T08:42:13.071600Z

cool. I’ll give it a try. thx

steveb8n 2021-02-25T08:44:23.071800Z

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

steveb8n 2021-02-25T08:44:55.072Z

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

borkdude 2021-02-25T08:45:37.072200Z

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

borkdude 2021-02-25T08:46:06.072400Z

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

ikitommi 2021-02-25T08:46:32.072600Z

malli reifies just protocols, does that work ok?

ikitommi 2021-02-25T08:46:54.072800Z

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

steveb8n 2021-02-25T08:47:00.073Z

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

1👍
borkdude 2021-02-25T08:47:22.073300Z

it does work:

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

borkdude 2021-02-25T08:48:08.073500Z

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

borkdude 2021-02-25T08:48:16.073700Z

for the parts that bb doesn't support

ikitommi 2021-02-25T08:48:34.073900Z

sure, happy to add. which parts? 🙂

borkdude 2021-02-25T08:48:59.074100Z

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

ikitommi 2021-02-25T08:49:05.074300Z

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

ikitommi 2021-02-25T08:49:31.074500Z

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

borkdude 2021-02-25T08:50:33.074700Z

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

ikitommi 2021-02-25T08:50:34.074900Z

example/test-bed most welcome :)

ikitommi 2021-02-25T08:51:08.075100Z

that simple. cool.

ikitommi 2021-02-25T08:51:58.075300Z

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

2🙏
ikitommi 2021-02-25T08:53:22.075600Z

so, does the :bb conditional work already?

borkdude 2021-02-25T08:53:23.075800Z

@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])

borkdude 2021-02-25T08:53:42.076Z

@ikitommi Yes, :bb is prioritized over :clj

1👍
borkdude 2021-02-25T08:53:49.076300Z

it's order dependent

borkdude 2021-02-25T08:56:00.076500Z

@ikitommi So the first hack I did:

(ns malli.sci
  #?@(: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!)))))

borkdude 2021-02-25T08:56:33.076700Z

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

borkdude 2021-02-25T08:56:46.076900Z

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

borkdude 2021-02-25T08:57:36.077100Z

I see that for cljs you didn't use it

borkdude 2021-02-25T08:58:07.077300Z

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

borkdude 2021-02-25T08:59:47.077500Z

Then after doing that, I run into:

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

borkdude 2021-02-25T09:00:26.077700Z

There are other deftypes in regex.cljc

ikitommi 2021-02-25T09:00:47.077900Z

what would be a good workaround for those?

ikitommi 2021-02-25T09:00:59.078100Z

or, will bb support those at some point?

ikitommi 2021-02-25T09:01:43.078300Z

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

borkdude 2021-02-25T09:01:53.078500Z

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.

borkdude 2021-02-25T09:04:11.078700Z

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.

borkdude 2021-02-25T09:05:01.079100Z

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

steveb8n 2021-02-25T07:17:52.067300Z

this would be useful for validating tools.cli parsed options

borkdude 2021-02-25T07:43:17.069800Z

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

ikitommi 2021-02-25T08:32:17.070100Z

there is https://github.com/metosin/malli/issues/302. Help most welcome

borkdude 2021-02-25T08:35:56.070400Z

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

ikitommi 2021-02-25T08:37:02.070600Z

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

ikitommi 2021-02-25T08:37:48.070800Z

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

steveb8n 2021-02-25T08:39:33.071Z

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

borkdude 2021-02-25T08:40:33.071200Z

@steveb8n OK. This lib https://github.com/green-coder/minimallist also works with bb btw

steveb8n 2021-02-25T08:42:13.071600Z

cool. I’ll give it a try. thx

steveb8n 2021-02-25T08:44:23.071800Z

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

steveb8n 2021-02-25T08:44:55.072Z

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

borkdude 2021-02-25T08:45:37.072200Z

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

borkdude 2021-02-25T08:46:06.072400Z

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

ikitommi 2021-02-25T08:46:32.072600Z

malli reifies just protocols, does that work ok?

ikitommi 2021-02-25T08:46:54.072800Z

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

steveb8n 2021-02-25T08:47:00.073Z

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

1👍
borkdude 2021-02-25T08:47:22.073300Z

it does work:

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

borkdude 2021-02-25T08:48:08.073500Z

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

borkdude 2021-02-25T08:48:16.073700Z

for the parts that bb doesn't support

ikitommi 2021-02-25T08:48:34.073900Z

sure, happy to add. which parts? 🙂

borkdude 2021-02-25T08:48:59.074100Z

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

ikitommi 2021-02-25T08:49:05.074300Z

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

ikitommi 2021-02-25T08:49:31.074500Z

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

borkdude 2021-02-25T08:50:33.074700Z

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

ikitommi 2021-02-25T08:50:34.074900Z

example/test-bed most welcome :)

ikitommi 2021-02-25T08:51:08.075100Z

that simple. cool.

ikitommi 2021-02-25T08:51:58.075300Z

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

2🙏
ikitommi 2021-02-25T08:53:22.075600Z

so, does the :bb conditional work already?

borkdude 2021-02-25T08:53:23.075800Z

@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])

borkdude 2021-02-25T08:53:42.076Z

@ikitommi Yes, :bb is prioritized over :clj

1👍
borkdude 2021-02-25T08:53:49.076300Z

it's order dependent

borkdude 2021-02-25T08:56:00.076500Z

@ikitommi So the first hack I did:

(ns malli.sci
  #?@(: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!)))))

borkdude 2021-02-25T08:56:33.076700Z

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

borkdude 2021-02-25T08:56:46.076900Z

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

borkdude 2021-02-25T08:57:36.077100Z

I see that for cljs you didn't use it

borkdude 2021-02-25T08:58:07.077300Z

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

borkdude 2021-02-25T08:59:47.077500Z

Then after doing that, I run into:

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

borkdude 2021-02-25T09:00:26.077700Z

There are other deftypes in regex.cljc

ikitommi 2021-02-25T09:00:47.077900Z

what would be a good workaround for those?

ikitommi 2021-02-25T09:00:59.078100Z

or, will bb support those at some point?

ikitommi 2021-02-25T09:01:43.078300Z

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

borkdude 2021-02-25T09:01:53.078500Z

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.

borkdude 2021-02-25T09:04:11.078700Z

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.

borkdude 2021-02-25T09:05:01.079100Z

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

borkdude 2021-02-25T12:13:39.080300Z

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

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

ikitommi 2021-02-25T14:19:16.082700Z

you can either:

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

ikitommi 2021-02-25T14:20:27.084100Z

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

ikitommi 2021-02-25T14:22:01.086700Z

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.

ikitommi 2021-02-25T14:22:59.087700Z

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

ikitommi 2021-02-25T14:26:30.087900Z

oh, with nil value you mean?

borkdude 2021-02-25T14:32:31.088100Z

with no value at all

borkdude 2021-02-25T14:32:42.088300Z

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

borkdude 2021-02-25T14:53:14.089600Z

@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

borkdude 2021-02-25T14:53:36.089900Z

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

borkdude 2021-02-25T14:54:14.090500Z

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

ikitommi 2021-02-25T16:09:59.092200Z

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

ikitommi 2021-02-25T16:10:32.093200Z

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

ikitommi 2021-02-25T16:11:43.095100Z

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

ikitommi 2021-02-25T16:14:36.097Z

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