Then you can assert that `user` is a User, with type hints, preconditions, 'spec' (a Clojure library), tests, docstrings, or some ad-hoc convention ("this kind of namespace does this kind of data transformation").
Then again, some Clojure users might prefer to use plain hashmaps (Rich tends to encourage this; or at least that's the common interpretation from his talks), and none of the mentioned mechanisms.
The mechanisms are there. Up to each to use them or not, depending on your scenario (and sincerely on one's competence too)
If I run this:
(defn bar [x y]
(println x y))
(defn foo [v]
(if v (bar 12 42 79)))
(foo false)
The program prints nothing and Clojure reports no error.But when I run this:
(foo true)
Then Clojure reports an error: Exception in thread "main" clojure.lang.ArityException: Wrong number of args (3) passed to: user/bar, compiling:(.../test.clj:6:5) (defn foo [a b] :hi)
(defn bar [] (foo 1 2 3))
WARNING: Wrong number of args (3) passed to prepost.core/foo at line 25 src/cljs/prepost/core.cljs
WARNING: foo at line 24 is being replaced at line 48 src/cljs/prepost/core.cljs
It's a compile-time warning even when the app is not running, but ClojureScript goes through a different compilation step than JVM Clojure. Perhaps I was mistaken in the JVM case, or someone else can chime in.Because these are not actual static types, you can get a lot of control and say things like "this function accepts three arguments, all integers between 5 and 50, but in increasing order, and the output must be a float between 0 and 1", and then have that checked on the function before the program ever runs. At run time, you can then verify that you are passing in valid arguments.
Spec is relatively new, and there are experiments in the community to do things more like traditional static type checking also. The future is an interesting place.
To statically check constraints like "all integers between 5 and 50", you need dependent types, and Clojure doesn't support this.
Considering this, I'm not sure what you mean by "before the program ever runs". Do you mean a check thanks to automated property-based testing (which is a way of running a part of your program)?
I think the main advantage of a type system (e.g. Java's) is that it avoids a lot of stupid mistakes; and therefore does help refactoring because most "stupid" changes will simply not compile (rename a function but forget it's used in 3 other places? boom)
Static analysis with Spec might be a great booster because it gets you the expressiveness of Spec to static code reasoning.
I've used static and dynamic languages. When using a dynamic language, I usually don't miss static language features, except when refactoring and/or maintaining a large code base. It's the reason why I'm interested by Flow and TypeScript (in the JS ecosystem), mypy (in the Python ecosystem) and even static languages like Go. It's also the reason why I'm a bit reluctant to invest in Clojure, despite being interested by the language.
I think dynamic typing is a lot more problematic in imperative/OO languages. One problem is that the data is mutable, and you pass things around by reference. Even if you knew the shape of the data originally, there's no way to tell whether it's been changed elsewhere via side effects. The other problem is that OO encourages proliferation of types in your code. Keeping track of that quickly gets out of hand.
Clojure embraces immutability, and any changes to the data happen explicitly in a known context. This makes it much easier to know exactly what the shape of the data is at any one place in your application.
Meanwhile, all the data is structured using a set of common data structures. Any iterator function such as `map`, `filter`, or `reduce` can iterate any data structure, and it's completely agnostic regarding the concrete types. The code that cares about the types is passed in as a parameter. Pretty much any data transformations in Clojure are accomplished by chaining functions from the standard library together, with domain specific code bubbling up to a shallow layer at the top.
There's also the culture of using Spec and Schema in Clojure, so you often have assertions at the interface level. For example, there's ring-spec https://github.com/ring-clojure/ring-spec/blob/master/src/ri... providing the specification for Ring.
(defn parse-seq [parse-el form] (when (sequential? form) (reduce #(if-let [parsed (parse-el %2)] (conj %1 parsed) (reduced nil)) [] form)))
without knowing really clojure i take it "[parse-el form]" are the arguments of this function. How is it possible with minimal effort to know what am I supposed to pass as parse-el and form? what are they?
https://github.com/tonsky/datascript/blob/master/src/datascr...
"parse-el" is used as a function, which takes one argument, the first argument when invoked from reduce (the accumulator), which is first [] then another vector (because of conj).
So "parse-seq" parses a sequence (named "form") and produces a vector, or nil. It applies "parse-el" to each form using reduce. When the result of any form gives a nil value, the whole result is also nil (see https://clojuredocs.org/clojure.core/reduced). The result is also nil if form is not sequential.
There is no other contract about "parse-el", it just should be able to "parse" whatever occurs in "form". You could use it on your own with any kind of data that has a corresponding parse function.
A documentation string would have been nice, or different names ("parse-el"?)
Now, if you look at the usage of "parse-seq", you can see for example:
(let [vars* (parse-seq parse-variable vars)
....
And it looks like "parse-variable" parses a form and returns an instance of Variable. (defn parse-variable [form]
(when (and (symbol? form)
(= (first (name form)) \?))
(Variable. form)))An interesting corollary to this problem though is the wandering type-path, where when consuming (again, poorly documented) a Java library, I know the type of the function I want to call, but not how to get that type. Usually it's:
"I have B value [byte-array, int, whatever] and I need Z from this library so I can call `Z.foo`... Z's constructor takes Y, which takes X, which takes..."
Types help there, but they also hinder quite a bit if I have the correct underlying data type, but can't get Java to accept it.
OK, there aren't any. Pull requests accepted?
Well, javascript does not have some of the features that other dynamic programming languages have, that help you manage complexity and create successful projects of large size.