bhb / expound Goto Github PK
View Code? Open in Web Editor NEWHuman-optimized error messages for clojure.spec
License: Eclipse Public License 1.0
Human-optimized error messages for clojure.spec
License: Eclipse Public License 1.0
Used from the terminal:
boot.user=> (expound/expound :example/place {})
-- Spec failed --------------------
{}
should contain keys: `:city`,`:state`
-- Relevant specs -------
:example/place:
(clojure.spec.alpha/keys
:req-un
[:example.place/city :example.place/state])
-------------------------
Detected 1 errornil
The output (nil
) is glued to the text. I think the last line printed by expound
should end with a newline (like s/explain
).
(To reproduce I use BOOT_VERSION=2.7.2-SNAPSHOT BOOT_CLOJURE_VERSION=1.9.0-alpha17 rlwrap boot -d expound bare-repl
)
For one of my api endpoints I have a quite long response which is compressed of several so-called steps
. Each step is an object on its own.
The problem arises when one of the last item of the validation fails. It seems that expound
simply replaces each object in the sequence with ...
which is fine for short sequences but for longer ones becomes a problem (See output below)
-- Spec failed --------------------
{:code ...,
:uuid ...,
:waypoints ...,
:route
{:distance ...,
:duration ...,
:steps
(...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
{:mode "transit",
^^^^^^^^^
:maneuver ...,
:distance ...,
:duration ...,
:geometry ...,
:name ...}
...
...)}}
should be: "walking"
-- Relevant specs -------
:walk/mode:
#{"walking"}
:walk/step:
(clojure.spec.alpha/merge
(clojure.spec.alpha/keys :req-un [:walk/mode :walk/maneuver])
:step/base)
:hiposfer.kamal.specs.directions/step:
(clojure.spec.alpha/or :walk :walk/step :transit :transit/step)
:hiposfer.kamal.specs.directions/steps:
(clojure.spec.alpha/coll-of
:hiposfer.kamal.specs.directions/step
:kind
clojure.core/sequential?)
:hiposfer.kamal.specs.directions/route:
(clojure.spec.alpha/keys
:req-un
[:hiposfer.kamal.specs.directions/distance
:hiposfer.kamal.specs.directions/duration
:hiposfer.kamal.specs.directions/steps])
:hiposfer.kamal.specs.directions/response:
(clojure.spec.alpha/keys
:req-un
[:hiposfer.kamal.specs.directions/code]
:opt-un
[:hiposfer.kamal.specs.directions/waypoints
:hiposfer.kamal.specs.directions/route])
I use expound in edna and I noticed that errors in 0.7.1 were harder to read than in 0.7.0 when they occur inside a large outer data structure. In 0.7.0, it points to the specific part of the data structure first:
Exception in thread "main" java.lang.Exception: -- Syntax error -------------------
[...
[:guitarx
^^^^^^^^
...
...
...
...
...
...
...
...
...
...
...
...]
<snip>
In 0.7.1, it prints the entire data structure first, and you have to scroll (sometimes far) down to see it eventually point to the specific part of the data structure where the error is:
Exception in thread "main" java.lang.Exception: -- Spec failed --------------------
[{:tempo 80}
[:guitarx {:octave 3} 1/16 :b :+c 1/8 :+d :b :+c :a :b :g :a]
[:banjo {:octave 3} 1/16 :b :+c 1/8 :+d :b :+c :a :b :g :a]
[:guitar {:octave 3} 1/16 :b :+c 1/8 :+d :b :+c :a :b :g :a]
[:guitar {:octave 3} 1/2 :d 1/8 :g :g :a :b :g :b 1/2 :a]
[:banjo {:octave 3} 1/8 :g :g :a :b 1/2 :g]
[:guitar {:octave 2} 1/16 :g :g 1/8 :g :a :b :+c :+d :+c 1/2 :b]
[:banjo {:octave 3} 1/16 :g :g 1/8 :g :a :b :+c :+d :+c 1/2 :b]
[:guitar {:octave 2} 1/16 :g :g 1/8 :g :a :b :+c :+d :+c 1/2 :b]
[:banjo {:octave 3} 1/16 :g :g 1/8 :g :a :b :+c :+d :+c 1/2 :b]
[:guitar {:octave 3} 1/16 :g :g 1/8 :g :a :b :+c :+d :+c 1/2 :b]
[:banjo {:octave 3} 1/16 :g :g 1/8 :g :a :b :+c :+d :+c 1/2 :b]
[:guitar
{:octave 4}
1/16
#{:-g :-b :d}
#{:-g :-b :d}
<snip>
-- Spec failed --------------------
[...
[:guitarx {:octave 3} 1/16 :b :+c 1/8 :+d :b :+c :a :b :g :a]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
...
...
...
...
...
...
...
<snip>
-- Syntax error -------------------
[...
[:guitarx
^^^^^^^^
...
...
...
...
...
...
...
...
...
...
...
...]
<snip>
I posted a code example and the output of both versions here https://gist.github.com/oakes/dbc902e8fb077d6430fa0909cf4d7272
None of the functions have docstrings, and a page listing all public functions would be good.
I have a case that's working with spec, but failing with expound:
(require '[clojure.spec.alpha :as s]
'[expound.alpha :as e])
(s/def :db/valueType #{:db.type/ref})
(s/def :db/cardinality #{:db.cardinality/one :db.cardinality/many})
(s/def :db/unique #{:db.unique/identity})
(s/def ::attribute-schema (s/keys :opt [:db/valueType :db/cardinality :db/unique]))
(s/def ::schema (s/map-of keyword? ::attribute-schema))
(s/explain-str ::schema {:some/value {:db/unique :db.unique/value}})
;;In: [:some/value 1 :db/unique] val: :db.unique/value fails spec: :db/unique at: [1 :db/unique] ;;predicate: #{:db.unique/identity}
;;:clojure.spec.alpha/spec :user/schema
;;:clojure.spec.alpha/value #:some{:value #:db{:unique :db.unique/value}}
(e/expound-str ::schema {:some/value {:db/unique :db.unique/value}})
;;AssertionError Assert failed: clojure.lang.LazySeq@8de948aa
;;(every? :expound/in leaf-problems) expound.alpha/printer-str (alpha.cljc:541)
Clojure 1.9.0-alpha19
(require '[clojure.spec.alpha :as s])
(require '[expound.alpha :as expound])
(s/def ::my-fn (s/fspec
:args (s/cat :x int? :y int?)))
(defn foobar [x y]
(/ x y))
(s/explain ::my-fn foobar)
;; val: (0 0) fails spec: :user/my-fn predicate: (apply fn), Divide by zero
;; :clojure.spec.alpha/spec :user/my-fn
;; :clojure.spec.alpha/value #function[user/foobar]
(expound/expound ::my-fn foobar)
;; -- Spec failed --------------------
;; #function[user/foobar]
;; should satisfy
;; (apply fn)
;; -- Relevant specs -------
;; :user/my-fn:
;; (clojure.spec.alpha/fspec
;; :args
;; (clojure.spec.alpha/cat :x clojure.core/int? :y clojure.core/int?)
;; :ret
;; clojure.core/any?
;; :fn
;; nil)
;; -------------------------
;; Detected 1 error
Repro:
(st/instrument)
(set! s/*explain-out* expound/printer)
(ns foo (:refer-clojure :as [rand-int]))
Actual:
Call to clojure.core/refer-clojure did not conform to spec:
-- Spec failed --------------------
((... :as) ...)
^^^
should be: `:exclude`
Expected: The value could actually be :exclude
or :only
or :rename
etc. Here is the default error message via explain.
Call to clojure.core/refer-clojure did not conform to spec:
In: [0 1] val: :as fails at: [:args :exclude :op :quoted-spec :spec] predicate: #{:exclude}
In: [0 1] val: :as fails at: [:args :only :op :quoted-spec :spec] predicate: #{:only}
In: [0 1] val: :as fails at: [:args :rename :op :quoted-spec :spec] predicate: #{:rename}
In: [0] val: (quote :as) fails at: [:args :exclude :op :spec] predicate: #{:exclude}
In: [0] val: (quote :as) fails at: [:args :only :op :spec] predicate: #{:only}
In: [0] val: (quote :as) fails at: [:args :rename :op :spec] predicate: #{:rename}
Details https://www.reddit.com/r/Clojure/comments/778tc2/is_clojure_19_improving_error_messages/dokmvma/
Hi again!
By evaluating (expound/expound (complement string/blank?) "")
one will get:
nil
-- Spec failed --------------------
""
should satisfy
/
-------------------------
Detected 1 error
The /
is quite confusing.
If I try with (expound/expound (fn [x] (not (string/blank? x))) "")
I'll get the same output.
Tried out with clojurescript.
Hope this one can be improved!
Cheers - Victor
Is there a way to pass a fn to spec and value and that it would return error message string.
(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$")
(expound/def :example/email (s/and string? #(re-matches email-regex %)) "should be a valid email address")
(expound/expound-error :example/email "sally@")
;; => "should be a valid email address"
(expound/expound string? 1)
Actual:
-- Spec failed --------------------
1
should satisfy
unknown
Expected:
-- Spec failed --------------------
1
should satisfy
string?
We are looking just for the predicate, which is unknown in this case. We should look at the :clojure.spec.alpha/spec
key (top-level)
(defmulti fruit :fruit/type)
(defmethod fruit :orange [_]
(s/keys))
(defmethod fruit :apple [_]
(s/keys))
(s/def ::fruit1 (s/multi-spec fruit :fruit/type))
(expound/expound ::fruit1 {}) ;; works
(s/def ::fruit2 (s/and
map?
(s/multi-spec fruit :fruit/type)))
(expound/expound ::fruit2 {:fruit/type :orange}) ;; works
(expound/expound ::fruit2 {:fruit/type :banana}) ;; error
If you attach a message to say, a set-based spec like
(expound/def ::myspec #{1 2 3} "should be foo")
it won't print the message
Take this example code:
(ns dev.expound
(:require
[clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen]
[expound.alpha :as expound]))
(defmulti bar-spec :type)
(s/def ::b string?)
(defmethod bar-spec ::b
[_]
(s/keys :req [::b]))
(s/def ::bar (s/multi-spec bar-spec (fn [val tag]
(assoc val :type tag))))
I realize in this trivial example the retag function should be :type
but for the sake of producing the error in more complex cases, I made it a function.
Running (expound/expound ::bar {})
produces this exception:
clojure.lang.ArityException: Wrong number of args (1) passed to: expound/fn--1994
at clojure.lang.AFn.throwArity(AFn.java:429)
at clojure.lang.AFn.invoke(AFn.java:32)
at expound.alpha$no_method.invokeStatic(alpha.cljc:255)
at expound.alpha$no_method.invoke(alpha.cljc:245)
at expound.alpha$eval1720$fn__1721$fn__1722.invoke(alpha.cljc:396)
at clojure.core$map$fn__5587.invoke(core.clj:2745)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.LazySeq.first(LazySeq.java:71)
at clojure.lang.RT.first(RT.java:685)
at clojure.core$first__5106.invokeStatic(core.clj:55)
at clojure.string$join.invokeStatic(string.clj:180)
at clojure.string$join.invoke(string.clj:180)
at expound.alpha$eval1720$fn__1721.invoke(alpha.cljc:397)
at clojure.lang.MultiFn.invoke(MultiFn.java:260)
at expound.alpha$eval1730$fn__1731.invoke(alpha.cljc:411)
at clojure.lang.MultiFn.invoke(MultiFn.java:260)
at expound.alpha$print_explain_data$iter__1855__1859$fn__1860$fn__1861.invoke(alpha.cljc:704)
at expound.alpha$print_explain_data$iter__1855__1859$fn__1860.invoke(alpha.cljc:702)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:528)
at clojure.core$seq__5124.invokeStatic(core.clj:137)
at clojure.core$apply.invokeStatic(core.clj:652)
at clojure.core$apply.invoke(core.clj:652)
at expound.alpha$print_explain_data.invokeStatic(alpha.cljc:701)
at expound.alpha$print_explain_data.invoke(alpha.cljc:687)
at expound.alpha$printer_str.invokeStatic(alpha.cljc:842)
at expound.alpha$printer_str.invoke(alpha.cljc:824)
at expound.alpha$expound_str.invokeStatic(alpha.cljc:904)
at expound.alpha$expound_str.invoke(alpha.cljc:896)
at expound.alpha$expound.invokeStatic(alpha.cljc:917)
at expound.alpha$expound.invoke(alpha.cljc:914)
at dev.expound$eval2002.invokeStatic(form-init5270577758905363292.clj:1)
at dev.expound$eval2002.invoke(form-init5270577758905363292.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:7062)
at clojure.lang.Compiler.eval(Compiler.java:7025)
at clojure.core$eval.invokeStatic(core.clj:3206)
at clojure.core$eval.invoke(core.clj:3202)
at clojure.main$repl$read_eval_print__8572$fn__8575.invoke(main.clj:243)
at clojure.main$repl$read_eval_print__8572.invoke(main.clj:243)
at clojure.main$repl$fn__8581.invoke(main.clj:261)
at clojure.main$repl.invokeStatic(main.clj:261)
at clojure.main$repl.doInvoke(main.clj:177)
at clojure.lang.RestFn.invoke(RestFn.java:1523)
at clojure.tools.nrepl.middleware.interruptible_eval$evaluate$fn__818.invoke(interruptible_eval.clj:87)
at clojure.lang.AFn.applyToHelper(AFn.java:152)
at clojure.lang.AFn.applyTo(AFn.java:144)
at clojure.core$apply.invokeStatic(core.clj:657)
at clojure.core$with_bindings_STAR_.invokeStatic(core.clj:1965)
at clojure.core$with_bindings_STAR_.doInvoke(core.clj:1965)
at clojure.lang.RestFn.invoke(RestFn.java:425)
at clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invokeStatic(interruptible_eval.clj:85)
at clojure.tools.nrepl.middleware.interruptible_eval$evaluate.invoke(interruptible_eval.clj:55)
at clojure.tools.nrepl.middleware.interruptible_eval$interruptible_eval$fn__863$fn__866.invoke(interruptible_eval.clj:222)
at clojure.tools.nrepl.middleware.interruptible_eval$run_next$fn__858.invoke(interruptible_eval.clj:190)
at clojure.lang.AFn.run(AFn.java:22)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
I'm running into this assertion error:
(defn key->spec [keys problems]
(assert (apply = (map :expound/via problems)))
....
https://github.com/bhb/expound/blob/master/src/expound/printer.cljc#L47
Any idea why this may be?
I'll dig in and see what the actual values are. If anything I think the error message here could be improved since the vanilla assert message is not helpful.
Here is the full stack trace:
1. Unhandled java.lang.AssertionError
Assert failed: (apply = (map :expound/via problems))
printer.cljc: 47 expound.printer$key__GT_spec/invokeStatic
printer.cljc: 46 expound.printer$key__GT_spec/invoke
printer.cljc: 186 expound.printer$print_spec_keys$fn__21784/invoke
printer.cljc: 186 expound.printer$print_spec_keys/invokeStatic
printer.cljc: 177 expound.printer$print_spec_keys/invoke
alpha.cljc: 225 expound.alpha$explain_missing_keys/invokeStatic
alpha.cljc: 216 expound.alpha$explain_missing_keys/invoke
alpha.cljc: 230 expound.alpha$eval21950$fn__21951/invoke
MultiFn.java: 260 clojure.lang.MultiFn/invoke
alpha.cljc: 242 expound.alpha$eval21954$fn__21955/invoke
MultiFn.java: 260 clojure.lang.MultiFn/invoke
alpha.cljc: 459 expound.alpha$problem_group_str1/invokeStatic
alpha.cljc: 457 expound.alpha$problem_group_str1/invoke
alpha.cljc: 504 expound.alpha$printer_str$iter__22065__22069$fn__22070/invoke
LazySeq.java: 40 clojure.lang.LazySeq/sval
LazySeq.java: 49 clojure.lang.LazySeq/seq
Cons.java: 39 clojure.lang.Cons/next
LazySeq.java: 81 clojure.lang.LazySeq/next
RT.java: 703 clojure.lang.RT/next
core.clj: 64 clojure.core/next
string.clj: 180 clojure.string/join
string.clj: 180 clojure.string/join
alpha.cljc: 503 expound.alpha$printer_str/invokeStatic
alpha.cljc: 479 expound.alpha$printer_str/invoke
alpha.cljc: 515 expound.alpha$custom_printer$fn__22092/invoke
alpha.cljc: 520 expound.alpha$printer/invokeStatic
alpha.cljc: 517 expound.alpha$printer/invoke
alpha.clj: 250 clojure.spec.alpha/explain-out
alpha.clj: 246 clojure.spec.alpha/explain-out
test.clj: 115 orchestra.spec.test/spec-checking-fn/conform!/fn
test.clj: 115 orchestra.spec.test/spec-checking-fn/conform!
test.clj: 125 orchestra.spec.test/spec-checking-fn/fn
RestFn.java: 137 clojure.lang.RestFn/applyTo
Var.java: 702 clojure.lang.Var/applyTo
core.clj: 659 clojure.core/apply
core.clj: 652 clojure.core/apply
Hi Ben!
I was trying a simple definition and I keep getting:
------ WARNING #1 --------------------------------------------------------------
File: ...:38:1
--------------------------------------------------------------------------------
35 | [x]
36 | (= :http/malformed x))
37 |
38 | (expound/def :my-namespace/js-error #(instance? js/Error %) "should be a JavaScript error instance")
-------^------------------------------------------------------------------------
No such namespace: clojure.spec.alpha, could not locate clojure/spec/alpha.cljs, clojure/spec/alpha.cljc, or JavaScript source providing "clojure.spec.alpha"
--------------------------------------------------------------------------------
39 |
...
--------------------------------------------------------------------------------
------ WARNING #2 --------------------------------------------------------------
File: .../http.cljs:38:1
--------------------------------------------------------------------------------
35 | [x]
36 | (= :http/malformed x))
37 |
38 | (expound/def :my-namespace/js-error #(instance? js/Error %) "should be a JavaScript error instance")
-------^------------------------------------------------------------------------
Use of undeclared Var clojure.spec.alpha/def-impl
--------------------------------------------------------------------------------
39 |
...
--------------------------------------------------------------------------------
It seems that the expound/def
macro cannot expand to s/def
because in cljs the alias does not actually expand correctly - maybe renaming from clojure.spec.alpha
to cljs.spec.alpha
is not done?
I do not know if true but that is what I gather from the errors above.
Hi.
And thanks for the awesome lib! Would it be possible to have a way to report on extra keys on s/keys
or s/merge
specs? I have a configuration with mostly all the keys unqualified and optional. There is no guard against misspelled keys at the moment.
Foremost, this would be useful on the development phase, thus looking at expound :)
Added helpers on spec-tools to extract all the keys from both s/keys
and s/merge
that could be used/copied to get the actual set of defined keys to be checked against.
And my use-case is here.
Hi!
I'm trying to use expound for generating nice ex-info
objects.
Example:
(throw (ex-info "Validation failed" {:explanation (with-out-str (println (apply expound/expound args)))}))
The problem is, the \n
s and --------------------
s don't make sense in this context, they make things less readable:
Is there currently an option to remove them?
Thanks - Victor
Thanks for expound, it's a great addition to spec.
I've been using clojure.spec/assert in combination with Clojure's pre-conditions. Would you be interested in a PR which adds support for assertions, or is that out-of-scope for expound?
Hi again ๐ I have a bit of an improvement request ๐
I have a spec with a set predicate:
(s/def :stm/success-state (s/tuple #{:start :end :latest-event :attempt-commit}
any?
nil?))
Spec error was:
Error: Spec assertion failed
In: [0] val: :error fails at: [0] predicate: #{:start :end :latest-event :attempt-commit}
In: [2] val: #object[Error Error: Oh noes!] fails at: [2] predicate: nil?
:cljs.spec.alpha/spec :stm/success-state
:cljs.spec.alpha/value [:error nil #object[Error Error: Oh noes!]]
:cljs.spec.alpha/failure :assertion-failed
And expound
error:
Error: Call to #'expound.alpha/value-in-context did not conform to spec:
<filename missing>:<line number missing>
-- Spec failed --------------------
Function arguments
(... 0 ... ... ...)
^
should be one of: `:args`,`:fn`,`:ret`
-- Spec failed --------------------
Function arguments
(... 0 ... ... ...)
^
should satisfy
nil?
-------------------------
Hi!
I found the following issue, where spec can handle NaN but Expound not:
(spec/conform (constantly false) js/NaN)
-> :cljs.spec.alpha/invalid
(expound/expound (constantly false) js/NaN)
:
{:message "Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound", :data {:form NaN, :val NaN, :in [], :in' []}}
Thanks for this library, we've been enjoying it at work for quite a few months now!
Cheers - Victor
The ommission of argument data, in failed calls to instrumented functions, is less helpful than clojure.spec would otherwise be. While I understand this brevity may be useful in some cases, my preference is to enjoy the formatting of expound without losing any data in translation. Are you open to making this truncation of irrelevant data configurable (say, using a dynamic var)?
Example:
Call to #'flarb did not conform to spec:
<filename missing>:<line number missing>
Function arguments
-- Spec failed --------------------
(... {:my-app.common.request/url-path "api/do-things",
:my-app.common.request/method :my-app.common.request/post,
:my-app.common.request/content-type :my-app.common.request/edn,
:my-app.common.request/headers {"Authorization" "Bearer meow"},
:my-app.common.request/body
{:my-app.common.transit.challenge/id 1,
:my-app.common.transit.challenge/num-games 0,
:my-app.common.transit.money/money 1},
:my-app.common.request/response-spec ::spam} ... ...)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
should satisfy
integer?
-------------------------
Detected 1 error
As you can see, it's hard to tell what's wrong here. This is partially due to the ommitted data and partially due to expound not showing the relevant specs in this case.
Thank you!
Hi,
Just a feature idea. The output of clojure.test.check.clojure-test/defspec
is a bit messy, maybe expound can do something about it ? Not sure it can/should, since the output shows you the first inputs that failed, then shrink them to minimal output that fails, so it's not directly related to a misused spec. But since expound humanize spec-related error messages, I thought maybe it could do for defspec too.
Why do the docs suggest:
;; Or set it globally
(set! s/*explain-out* expound/printer)
?
This results in a runtime error:
Caused by: java.lang.IllegalStateException: Can't change/establish root binding of: *explain-out* with set
Currently, it is an error to attempt to set the root binding of a var using set!, i.e. var assignments are thread-local.
The second arg to expound/def could either be a string or a function that is passed the problem (and maybe the spec itself?) and returns an error string. I believe that would allow you to grab any information youโve added to the problem and would also allow you to include any relevant information (e.g. the range information) in the message.
https://clojureverse.org/t/improving-error-messages-in-clojure-as-a-library/1765/14?u=bbrinck
Hi,
I'm beginning to use spec with clojurescript.
But I can't use this library.
s/explain works but not expound
cljs.user=> (expound/expound :example/place {})
#object[TypeError TypeError: Cannot read property 'cljs$lang$applyTo' of undefined]
TypeError: Cannot read property 'cljs$lang$applyTo' of undefined
Quick example:
(ns user.test
(:require [clojure.spec.alpha :as s]
[expound.alpha :as expound]))
(set! s/*explain-out* expound/printer)
(s/def ::a #{1})
(s/def ::b #{2})
(s/explain (s/keys* :opt-un [::a ::b]) [:a "foo"])
;; =>
;; ExceptionInfo Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound clojure.core/ex-info
As far as I understand, this is similar to #102 and would be resolved (kind of) by #78, but since there are no issues mentioning s/keys*
specifically I decided to create this issue anyway in case someone else runs into this.
By the way, thanks for creating this library, it's a must-have!
pretty-spec prints specs in an easier-to-read way than the default pprint.
Unfortunately, it looks like the latest version of Clojurescript won't work with Fipp (which is a dependency of pretty-spec):
brandonbloom/fipp#42
https://dev.clojure.org/jira/browse/CRRBV-15
When instrumenting, especially with orchestra, expound provides less information than clojure.spec would, since it doesn't say whether or not it was the :ret
or :args
or :fn
that failed to validate.
Are you open to adding this support?
=> (let [a] 2)
CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/let did not conform to spec:
-- Syntax error -------------------
([a] ...)
^^^
should have additional elements. The next element ":args" should satisfy
any?
-- Relevant specs -------
:clojure.core.specs.alpha/bindings:
(clojure.spec.alpha/and
clojure.core/vector?
(clojure.spec.alpha/* :clojure.core.specs.alpha/binding))
-------------------------
Hello,
I would like to use in Emacs + cider with that project setup:
(defproject nativeapi "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.8.0"] ;w/1.9.0 too
[org.clojure/spec.alpha "0.2.168"]
[clj-http "3.9.1"]
[expound "0.7.1"]
[org.clojure/test.check "0.9.0"]])
(ns nativeapi.core
(:require [clj-http.client :as client]
[clojure.spec.alpha :as s]
[clojure.test.check.generators :as gen]
[expound.alpha :as expound]
))
and I got this message:
CompilerException java.lang.Exception: namespace 'expound.paths' not found, compiling:(expound/problems.cljc:1:1)
Emacs 25.3, Cider version 20180805.1016
p.s. in Repl no problem
(s/def ::resolve (s/or :function ::resolver-fn
:protocol ::resolver-type))
(s/def ::resolver-fn fn?)
(expound/def ::resolver-type #(satisfies? resolve/FieldResolver %) "implement the com.walmartlabs.lacina.resolve/FieldResolver protocol")
(s/explain ::schema/resolve {})
-- Spec failed --------------------
{}
should satisfy
fn?
or
implement the com.walmartlabs.lacina.resolve/FieldResolver protocol
Note that last line looks like it is not indented correctly.
In some cases, we want custom error message to be all the way to the left though (this is current behavior):
(s/def :example/temp #{:hot :cold})
(expound/expound :example/temp 1)
;;-- Spec failed --------------------
;;
;; 1
;;
;;should be one of: :cold, :hot
(expound/def :example/name string? "should be a string")
(expound/expound :example/name 1)
;;-- Spec failed --------------------
;;
;; 1
;;
;;should be a string
I'm not sure if this is actually an issue with expound, or if I'm misunderstanding how it's supposed to work.
All spec errors are handled by expound's printer, no matter if they are made on the REPL during development or at any other place.
Add the following code to the namespace that contains my -main
function, which is also the namespace that CIDER loads by default.
(alter-var-root #'s/*explain-out* (constantly expound/printer))
When I deliberately make a mistake in the REPL, e.g. (defn a 1)
, I don't see expound's output, only the usual clojure.spec output.
Doing (set! s/*explain-out* expound/printer)
interactively after I start/connect to the REPL -- then syntax errors are caught and printed by expound. I'm not sure how this would work for spec errors deep inside the code in other threads.
$ lein repl
nREPL server started on port 62015 on host 127.0.0.1 - nrepl://127.0.0.1:62015
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.9.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_152-b16
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Exit: Control+D or (exit) or (quit)
Results: Stored in vars *1, *2, *3, an exception in *e
user=> (require '[expound.alpha :as expound]
#_=> '[clojure.spec.alpha :as s])
nil
user=> (defn a 1) ;; <------------ 1. expected, no expound integration yet
CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/defn did not conform to spec:
In: [1] val: 1 fails spec: :clojure.core.specs.alpha/arg-list at: [:args :bs :arity-1 :args] predicate: vector?
In: [1] val: 1 fails spec: :clojure.core.specs.alpha/args+body at: [:args :bs :arity-n :bodies] predicate: (cat :args :clojure.core.specs.alpha/arg-list :body (alt :prepost+body (cat :prepost map? :body (+ any?)) :body (* any?)))
#:clojure.spec.alpha{:problems ({:path [:args :bs :arity-1 :args], :pred clojure.core/vector?, :val 1, :via [:clojure.core.specs.alpha/defn-args :clojure.core.specs.alpha/args+body :clojure.core.specs.alpha/arg-list :clojure.core.specs.alpha/arg-list], :in [1]} {:path [:args :bs :arity-n :bodies], :pred (clojure.spec.alpha/cat :args :clojure.core.specs.alpha/arg-list :body (clojure.spec.alpha/alt :prepost+body (clojure.spec.alpha/cat :prepost clojure.core/map? :body (clojure.spec.alpha/+ clojure.core/any?)) :body (clojure.spec.alpha/* clojure.core/any?))), :val 1, :via [:clojure.core.specs.alpha/defn-args :clojure.core.specs.alpha/args+body :clojure.core.specs.alpha/args+body], :in [1]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x25f05c8d "clojure.spec.alpha$regex_spec_impl$reify__2436@25f05c8d"], :value (a 1), :args (a 1)}, compiling:(/private/var/folders/yw/d0ggvvcs5lz1fw09p76rl3_r0000gn/T/form-init4153292859114110702.clj:1:1)
user=> (alter-var-root #'s/*explain-out* (constantly expound/printer)) ;; <------------- 2. as suggested by the docs
#object[expound.alpha$printer 0x5a0e22ad "expound.alpha$printer@5a0e22ad"]
user=> (defn a 1)
CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/defn did not conform to spec:
In: [1] val: 1 fails spec: :clojure.core.specs.alpha/arg-list at: [:args :bs :arity-1 :args] predicate: vector?
In: [1] val: 1 fails spec: :clojure.core.specs.alpha/args+body at: [:args :bs :arity-n :bodies] predicate: (cat :args :clojure.core.specs.alpha/arg-list :body (alt :prepost+body (cat :prepost map? :body (+ any?)) :body (* any?)))
#:clojure.spec.alpha{:problems ({:path [:args :bs :arity-1 :args], :pred clojure.core/vector?, :val 1, :via [:clojure.core.specs.alpha/defn-args :clojure.core.specs.alpha/args+body :clojure.core.specs.alpha/arg-list :clojure.core.specs.alpha/arg-list], :in [1]} {:path [:args :bs :arity-n :bodies], :pred (clojure.spec.alpha/cat :args :clojure.core.specs.alpha/arg-list :body (clojure.spec.alpha/alt :prepost+body (clojure.spec.alpha/cat :prepost clojure.core/map? :body (clojure.spec.alpha/+ clojure.core/any?)) :body (clojure.spec.alpha/* clojure.core/any?))), :val 1, :via [:clojure.core.specs.alpha/defn-args :clojure.core.specs.alpha/args+body :clojure.core.specs.alpha/args+body], :in [1]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x25f05c8d "clojure.spec.alpha$regex_spec_impl$reify__2436@25f05c8d"], :value (a 1), :args (a 1)}, compiling:(/private/var/folders/yw/d0ggvvcs5lz1fw09p76rl3_r0000gn/T/form-init4153292859114110702.clj:1:1)
user=> (set! s/*explain-out* expound/printer) ;; <------------ 3. this seems to work
#object[expound.alpha$printer 0x5a0e22ad "expound.alpha$printer@5a0e22ad"]
user=> (defn a 1)
CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/defn did not conform to spec:
-- Spec failed --------------------
(... 1)
^
should satisfy
vector?
or
(clojure.spec.alpha/cat
:args
:clojure.core.specs.alpha/arg-list
:body
(clojure.spec.alpha/alt
:prepost+body
(clojure.spec.alpha/cat
:prepost
map?
:body
(clojure.spec.alpha/+ any?))
:body
(clojure.spec.alpha/* any?)))
-- Relevant specs -------
:clojure.core.specs.alpha/arg-list:
(clojure.spec.alpha/and
clojure.core/vector?
(clojure.spec.alpha/cat
:args
(clojure.spec.alpha/* :clojure.core.specs.alpha/binding-form)
:varargs
(clojure.spec.alpha/?
(clojure.spec.alpha/cat
:amp
#{'&}
:form
:clojure.core.specs.alpha/binding-form))))
:clojure.core.specs.alpha/args+body:
(clojure.spec.alpha/cat
:args
:clojure.core.specs.alpha/arg-list
:body
(clojure.spec.alpha/alt
:prepost+body
(clojure.spec.alpha/cat
:prepost
clojure.core/map?
:body
(clojure.spec.alpha/+ clojure.core/any?))
:body
(clojure.spec.alpha/* clojure.core/any?)))
:clojure.core.specs.alpha/defn-args:
(clojure.spec.alpha/cat
:name
clojure.core/simple-symbol?
:docstring
(clojure.spec.alpha/? clojure.core/string?)
:meta
(clojure.spec.alpha/? clojure.core/map?)
:bs
(clojure.spec.alpha/alt
:arity-1
:clojure.core.specs.alpha/args+body
:arity-n
(clojure.spec.alpha/cat
:bodies
(clojure.spec.alpha/+
(clojure.spec.alpha/spec :clojure.core.specs.alpha/args+body))
:attr
(clojure.spec.alpha/? clojure.core/map?))))
-------------------------
Detected 1 error
#:clojure.spec.alpha{:problems ({:path [:args :bs :arity-1 :args], :pred clojure.core/vector?, :val 1, :via [:clojure.core.specs.alpha/defn-args :clojure.core.specs.alpha/args+body :clojure.core.specs.alpha/arg-list :clojure.core.specs.alpha/arg-list], :in [1]} {:path [:args :bs :arity-n :bodies], :pred (clojure.spec.alpha/cat :args :clojure.core.specs.alpha/arg-list :body (clojure.spec.alpha/alt :prepost+body (clojure.spec.alpha/cat :prepost clojure.core/map? :body (clojure.spec.alpha/+ clojure.core/any?)) :body (clojure.spec.alpha/* clojure.core/any?))), :val 1, :via [:clojure.core.specs.alpha/defn-args :clojure.core.specs.alpha/args+body :clojure.core.specs.alpha/args+body], :in [1]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x25f05c8d "clojure.spec.alpha$regex_spec_impl$reify__2436@25f05c8d"], :value (a 1), :args (a 1)}, compiling:(/private/var/folders/yw/d0ggvvcs5lz1fw09p76rl3_r0000gn/T/form-init4153292859114110702.clj:1:1)
user=>
Project.clj
(defproject exp-bug "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0"]
[expound "0.7.1"]])
The output from clojure.spec.test.alpha/check
is unwieldy. Seems like Expound could provide a way to cleanly print these results.
As specs grow the failing part might be quite deep in the validation stack.
This creates some readability problems since the relevant-specs
section becomes too large to be usable at all. See example below:
I have a 21 inch screen (I think :D ) and even so I am not able to display the complete error without having to scroll around trying to find the errors.
I propose two options that might help solve this:
relevant specs
section optional and configurable when setting up the printer...
and show only the key-predicate that failedI think that expound already goes to great lengths to manage option 2 so I guess that would only leave option 1 ๐ ?
Hope it helps
with the following setup in v0.5.1-snaptshot
(st/instrument)
(set! s/*explain-out* (expound/custom-printer {:theme :figwheel-theme}))
(let [1 2]
3)
;; error printed twice with different exception types
;; - clojure.lang.ExceptionInfo: Call to clojure.core/let did not conform to spec:
;; - clojure.lang.Compiler$CompilerException: clojure.lang.ExceptionInfo: Call to clojure.core/let did not conform to spec:
Given that Clojure specs are quite complex the error message is equally large. Having it twice definitely doesnt help.
However the output from Expound is very organized so the problem becomes very manageable.
Hope it helps
Expound doesn't support using conformers to coerce values, because it significantly complicates the way Expound highlights the bad value (Expound uses a bunch of heurisitics to try to disambiguate the information returned by explain-data
and these heuristics rely on the original unconformed value appearing the larger structure).
However, this restriction means that Expound can't print error messages for some and
specs, since and
conforms the values.
user=> (require '[clojure.spec.alpha :as s])
nil
user=> (require '[expound.alpha :as expound])
nil
user=> (s/def ::sorted-pair (s/and (s/cat :x int? :y int?) #(< (-> % :x) (-> % :y))))
:user/sorted-pair
user=> (s/explain ::sorted-pair [1 0])
nil
val: {:x 1, :y 0} fails spec: :user/sorted-pair predicate: (< (-> % :x) (-> % :y))
user=> (expound/expound ::sorted-pair [1 0])
ExceptionInfo Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound clojure.core/ex-info (core.clj:4739)
Repro:
=> (ex/expound (s/fspec :args (s/cat :x int?) :ret int?) :inc)
clojure.lang.ExceptionInfo: Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound
=> (ex/expound (s/fspec :args (s/cat :x int?) :ret int?) 'inc)
clojure.lang.ExceptionInfo: Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound
=> (ex/expound (s/fspec :args (s/cat :x int?) :ret int?) #'str)
clojure.lang.ExceptionInfo: Cannot convert path. This can be caused by using conformers to transform values, which is not supported in Expound
Expected Expound would report the spec error that the input value didn't conform to the fspec, but throws a misleading error that says there may be a conformer (which is not true, in fact).
The other day we talked about your ideas to add "did you mean..." examples to an error message by filling in the missing arguments based on the spec. I still think that's a great idea. Here is another cool approach that @oakes laid out in a gist: https://gist.github.com/oakes/8db57ac808bf6ec144d627fd83a89da3
It uses defexample
s to show show an example of how a function can be used. (I appreciate his comment about how examples are especially helpful for non-English speakers).
Maybe if a defexample
is defined for a function, you could show that after an expound
ed error message.
Better yet - show everything:
expound
error messagedefexample
if it's available. This could also be collapsed by default in an IDEIf the expound
error message wasn't enough for me to figure out my problem, I would love to have multiple examples right there, in context, for me to figure it out. It would be nice to not be overwhelmed with information so it would be nice to optionally expand them with keybindings (or clicking in an IDE UI).
You could build this support directly into expound. Another direction could be that expound
turns into a type of platform, where a user could install plugins such as defexemple
or other things into it.
What do you think?
This ticket may be too broad - feel free to close it in favor of more specific tickets.
The following works with Clojure.spec explain but not on expound:
(s/def ::radius (s/or :unlimited #(= "unlimited" %) :distance (s/and number? #(> % 0))))
(s/def ::radiuses (s/coll-of ::radius))
(s/def ::radiuses-raw
(s/and string? #(re-matches rads-regex %)
(s/conformer parse-radiuses)
::radiuses))
;; works
(s/explain ::radiuses-raw "1200.50;100;500;unlimited;100")
;; crashes
(expound/expound ::radiuses-raw "1200.50;100;500;unlimited;100")
clojure "1.9.0-alpha17"
expound "0.1.1"
Here is the stacktrace that I obtained using aviso/pretty (let me know if you need the full raw one)
clojure.core/eval core.clj: 3194
...
user/eval3488 REPL Input
...
service.routing.spec/eval3492 spec.clj: 62
expound.alpha/expound alpha.cljc: 541
expound.alpha/expound-str alpha.cljc: 527
clojure.string/join string.clj: 180
clojure.core/first core.clj: 55
...
expound.alpha/expound-str/iter/fn alpha.cljc: 528
...
expound.alpha/eval3273/fn alpha.cljc: 404
expound.alpha/value-in-context alpha.cljc: 201
expound.alpha/highlighted-form alpha.cljc: 188
clojure.string/replace string.clj: 101
java.lang.NullPointerException:
clojure.lang.Compiler$CompilerException: java.lang.NullPointerException, compiling:(/home/carocad/Proyectos/service.routing/src/service/routing/spec.clj:62:1)
Repro:
(require '[expound.alpha :as expound])
(require '[clojure.spec.alpha :as s])
(set! s/*explain-out* expound/printer)
(defn hello "hello world")
Actual:
-- Syntax error -------------------
(hello "hello world")
should have additional elements. The next element is named `:args` and satisfies
(clojure.spec.alpha/alt
:arity-1
:clojure.core.specs.alpha/args+body
:arity-n
(clojure.spec.alpha/cat
:bodies
(clojure.spec.alpha/+
(clojure.spec.alpha/spec :clojure.core.specs.alpha/args+body))
:attr
(clojure.spec.alpha/? map?)))
Expected:
The inner alt
should be displayed as a series of or
clauses
=> (ex/expound (s/coll-of integer?) #{:a})
java.lang.UnsupportedOperationException: nth not supported on this type: PersistentHashSet
As far as I know, s/coll-of
should be applicable to non-sequantial collections (including sets).
According to the stack trace, it looks like the error occurs here:
expound/src/expound/paths.cljc
Line 81 in 7853f0f
Is there a way to see the spec error generated when I type, say, (let (a 1) a)
in the REPL printed by expound
?
It'd be nice to be able to bind expound-str to s/*explain-out*
. With that stest/instrument should present expound formatted descriptions in exceptions.
(binding [s/*explain-out* expound/expound-printer]
(s/explain-str (s/coll-of integer?) {:A 1}))
The main difference to the current code is that *explain-out*
printers take explain-data as an arg.
Awesome awesome project!
For outputing to terminal, say for instrument called within clojure.test
, it would be awesome to have ansi color codes.
Just an idea, keep up the good work and thanks!
Compare:
(s/explain
(:args (s/get-spec `defn))
`(~'foo (~'arg1 ~'arg2) ~'arg3))
Output is:
In: [1 0] val: arg1 fails spec: :clojure.core.specs.alpha/arg-list at: [:bs :arity-n :bodies :args] predicate: vector?
In: [1] val: (arg1 arg2) fails spec: :clojure.core.specs.alpha/arg-list at: [:bs :arity-1 :args] predicate: vector?
With expound:
(expound/expound
(:args (s/get-spec `defn))
`(~'foo (~'arg1 ~'arg2) ~'arg3))
Output (in part)
-- Spec failed --------------------
(... (arg1 ...) ...)
^^^^
should satisfy
vector?
Note that the more interesting case is omitted - that the parent collection is a list instead of a vector.
I've written a function spec for my top-level ring handler, which is a function called app
that calls reitit.ring/ring-handler
. It looks like this:
(defn app
[]
(rr/ring-handler
... ))
(s/fdef app :ret :ring/handler)
The :ring/handler
spec is defined in ring-spec.
There's some kind of problem when I call (or s/explain
the result of) this function that makes Expound throw an exception. It looks like this:
AssertionError Assert failed: Internal Expound assertion failed. Please report this bug at https://github.com/bhb/expound/issues: All values should be the same, but they are [{:path [:sync+async :fn :sync :args :request], :pred map?, :via [:ring/handler :ring.sync+async/handler :ring.sync.handler/args :ring.sync.handler/args :ring/request :ring/request], :expound/form #object[clojure.lang.AFunction$1 0x7024b79d "clojure.lang.AFunction$1@7024b79d"], :val :sync, :spec :ring/handler, :expound/path [:sync+async :fn :sync :args :request], :expound/in [], :in [:args 0], :expound/via [:ring/handler :ring.sync+async/handler :ring.sync.handler/args :ring.sync.handler/args :ring/request :ring/request]} {:path [:sync+async :fn :sync :ret], :pred map?, :via [:ring/handler :ring.sync+async/handler :ring/response], :expound/form #object[clojure.lang.AFunction$1 0x7024b79d "clojure.lang.AFunction$1@7024b79d"], :val [:async {:status 405, :body ""}], :spec :ring/handler, :expound/path [:sync+async :fn :sync :ret], :expound/in [], :in [:ret], :expound/via [:ring/handler :ring.sync+async/handler :ring/response]} {:path [:sync+async :fn :async :args :request], :pred map?, :via [:ring/handler :ring.sync+async/handler :ring.async.handler/args :ring.async.handler/args :ring/request :ring/request], :expound/form #object[clojure.lang.AFunction$1 0x7024b79d "clojure.lang.AFunction$1@7024b79d"], :val :sync, :spec :ring/handler, :expound/path [:sync+async :fn :async :args :request], :expound/in [], :in [:args 0], :expound/via [:ring/handler :ring.sync+async/handler :ring.async.handler/args :ring.async.handler/args :ring/request :ring/request]}]
(apply = (map :val problems)) expound.alpha/eval6753/fn--6754 (alpha.cljc:577)
(this is how it's formatted)
When I run without expound, I get the following output:
ExceptionInfo Call to #'hanabi.web/app did not conform to spec:
In: [:args 0] val: :sync fails spec: :ring/request at: [:ret :sync+async :fn :sync :args :request] predicate: map?
In: [:args 0] val: :sync fails spec: :ring/request at: [:ret :sync+async :fn :async :args :request] predicate: map?
In: [:ret] val: [:async {:status 405, :body ""}] fails spec: :ring/response at: [:ret :sync+async :fn :sync :ret] predicate: map?
val: {:status 405, :body ""} fails spec: :ring/response at: [:ret :sync :ret] predicate: (contains? % :headers)
val: ({:server-port 1, :server-name "", :remote-addr "", :uri "/", :scheme :http, :protocol "HTTP/1.1", :headers {}, :request-method :a} #object[clojure.spec.alpha$fspec_impl$reify__2451$fn__2454 0x49704cb9 "clojure.spec.alpha$fspec_impl$reify__2451$fn__2454@49704cb9"] #object[clojure.spec.alpha$fspec_impl$reify__2451$fn__2454 0x6859d673 "clojure.spec.alpha$fspec_impl$reify__2451$fn__2454@6859d673"]) fails spec: :ring.async/handler at: [:ret :async] predicate: (apply fn), Assert failed: In: [0] val: {:status 405, :body ""} fails spec: :ring/response at: [:response] predicate: (contains? % :headers)
(pvalid? argspec args)
clojure.core/ex-info (core.clj:4739)
Now I have no idea what's up with the spec or why Expound throws an exception, but clearly the raw spec formatting is preferable.
I am using expound in development mode for my app, and due to the way my app's routes are implemented, expound's index.html
takes precedence over my app in dev mode.
It appears to be only useful for development of expound itself, therefore I think it should not be included in the release jars.
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.