usernolan / fm Goto Github PK
View Code? Open in Web Editor NEWConcise syntax for embedding validation within functions
Concise syntax for embedding validation within functions
See #6
We're adding automatic conversion from defm
definitions to s/fdef
so that we can pipe straight through to stest/check
. Unfortunately fdef doesn't work with the style that fm allows providing a set of predicates in the same shape as the args list for a fn, so fm-form
needs to rewrite the provided :fm/args
argument list into an (s/cat ...)
expression with the arg names tagged appropriately.
;; fm-form should turn this...
(defm test
^{:fm/args [int? [int? int?]]}
[x [y z]]
(+ x y z))
;; into this...
(defm test
^{:fm/args (s/cat :x int? (s/cat :y int? :z int?))}
[x [y z]]
(+ x y z))
We may also want to consider providing gen/gen-from-pred
where available on incoming predicates.
fn
s can have multiple signatures:
(fn
([x] (inc x))
([x y] (inc (+ x y)))
It's easy enough to break this up between different fm
s, but it could be pretty straightforward to support the familiar syntax:
(fm
(^{,,,} [x] (inc x))
(^{,,,} [x y] (inc (+ x y)))
You could alternatively provide it once:
(fm
^{:fm/args ([int?]
[int? int?])}
([x] (inc x))
([x y] (inc (+ x y)))
Intuitively, either should work and be relatively straightforward (albeit requiring some structural tweaks). Not a super high priority, but something similar to #2 in that it could open up additional applicability for fm
, and break fewer fn
expectations.
It'd be a really useful tool to be able to ask an anomaly to describe itself (similar to s/explain
). This would be valuable not just at the REPL but also when building developer tooling. Consider the following:
(require [fm.macros :refer [defm])
(require [fm.anomaly :as anom])
(defm inc_
^{:fm/args int?}
[n]
(inc n))
(anom/explain (inc 'a)) ;; => "`inc` encountered an `args` anomaly for argument `a` at `0`: `a` failed predicate `int?`
What's even cooler is that you could potentially leverage additional :fm/*
keys for providing a descriptor that anom/explain
could utilize when defining predicates to be used by other specs. If that predicate is the one that failed in the spec, anom/explain
could utilize the provided :fm/explain
fn to generate a more specific explanation. As a bonus we can pass the entire anomaly to the explainer so they can fully leverage the given anomaly data.
;; Pred to be used in some other spec
(defm success?
^{:fm/explain
(fn [anomaly]
(str "Successful exit codes are zero. Got " (first (:fm.anomaly/args anomaly)) " instead"))}
[exit-code]
(zero? exit-code))
(anom/explain (success? 42)) ;; => "`success?` encountered an `args` anomaly for argument `42` at `0`: 42 isn't zero
Thoughts?
resolve
is a macro in clojurescript, rather than a regular fn
like in clojure, which keeps certain predicates from compiling correctly. there are also slight incompatibilities in other common interop pitfalls such as exception handling.
This is a test.
might muddy the macro a bit, but it's worth being able to give these a name like you can with fn
:
(fm assoc ^{,,,} [x] (assoc m :k x)) ; => #function[the.ns/assoc--28219]
Beyond supporting all function signatures, variadic specification would be useful in wrapping existing functions unchanged. In addition to being useful on it's own, e.g. in library consumption, a concise way to wrap existing functions would improve anomaly propagation through ->
, ->>
, comp
, etc..
(defm merge2
^{:fm/args [map? map? & map?]
:fm/ret map?}
[m1 m2 & ms]
(apply merge m1 m2 ms))
;; `fm/wrap` interface
(fm/wrap <fmeta> <f>)
(fm/wrap
^{:fm/args [& map?]
:fm/ret map?
merge)
To support variadic signatures, fm.utils/fm-form
should (partition-by #{'&} args-form)
and reduce first
and last
using a modified fm.utils/spec-form
. The fm.utils/wrap
facility will precipitate out of that improvement.
It seems there are three variadic cases to handle:
;; variadic `args-form` cases
[& xs] ; case 1: symbolic variadic args, xs will be a seq
[& {:k v}] ; case 2: keyword variadic args, xs will be a seq `interleaved` with keywords
[& [x1]] ; case 3: positional variadic args
For case 1
, :fm/args
should accept a single spec to be applied to every variadic argument (e.g. [& map?]
above), which the updated fm.utils/spec-form
would wrap in s/*
, inferring every variadic argument should conform to that spec.
:fm/args
should also accept a sequence spec form, such as s/+
, s/alt
, s/?
etc., to apply directly.
It isn't clear if inferring s/alt
would be useful or just increase ambiguity, but technically could work.
;; case 1 variadic `:fm/args`
[& map?] ; infer `(s/* map?)`
[& ::spec1] ; infer `(s/* ::spec1)`
[& (s/alt :tag1 ::spec1 :tag2 ::spec2)] ; use form directly
;; may not be worthwhile
[& ::spec1 ::spec2] ; infer `(s/alt ::spec1 ::spec1 ::spec2 ::spec2)`
case 2
needs more thought, but I'm thinking it will boil down to using s/cat
.
[& {:k v}] ; `args-form`
[& {:k string?}] ; `:fm/args` form
case 3
may be as simple as reducing both partitions using the existing facilities.
[& [x]] ; `args-form`
[& [map?]] ; `:fm/args` form
in general these should be used carefully, and i'm not totally sold on it yet due to the potential risks, but being able to "flip on" e.g. :fm/trace
globally would be extremely useful:
(require '[fm.,,, :as fm.,,,])
(defn log-trace! [t] (,,, t))
(set! *fm.,,,/trace* log-trace!)
,,,
:fm/handler
(e.g. for #22) and :fm/conform
may also be useful (or at least match the use-case).
one way to potentially reduce accidental mutation is to make fm.,,,
something likefm.global
or fm.opts
or something that needs to be explicitly require
'd in order to mutate these bindings, that isnt part of e.g. fm.macro
, or another ns
commonly require
'd in typical fm
usage.
reference on dynamic vars.
(->>
is-it-pronounced-data-or-data
(map xf1)
,,,
(fm/branch ::spec1
(true-expr ,,,)
(false-expr ,,,)))
(fm->>
^{:fm/args ,,,}
(map xf1 data1)
,,,
^{:fm/ret ,,,}
(into {}))
etc., etc..
sincerely,
@localshred
Currently defm
doesn't support supplying a docstring for a defm
'd function:
(defm inc
"I should be able to add a docstring"
^{:fm/args number?
:fm/ret number?}
[n]
(inc n))
This currently fails with:
Syntax error macroexpanding clojure.core/fn at (*cider-repl localshred/fm:localhost:62824(clj)*:272:8).
(...[charlist of the docstring]...) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/\
param-list
\T - failed: number? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list```
Which basically just means that it's expecting the first thing passed to `defm` to be the meta piece. I'm guessing this is trivial to support but definitely not a high priority.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.