Let's take a look.
> Validation
Validation typically means that given a set of shapes of data from a large class we should determine whether a certain instance fits within a smaller class. For instance, parsing selects out valid strings amongst all strings.
Haskell has a number of libraries like this. Let's use JSON for example. We create instances of the typeclass
class FromJSON a where
parseJSON :: JSON -> Maybe a -- simlplified
and now each type which instantiates that class gets an automatic validation as a JSON-like type. Similar patterns are available for any other kind of "validation".> Coercion
Coercion in the core.spec sense is a similar game to the kind of enhanced validation I used above. If validation picks out a subset of valid instances, coercion, conformance, lets more than just the subset stand for itself—other instances can "conform" to the same thing.
Again, type systems are very handy for this. The JSON example above is already taking care of this in a way more robust than core.spec. The desired result type `a` drives all possible "parses" and conforms them all to its value.
> Error reporting
Let's take a closer look at a more real type of the method within FromJSON
class FromJSON a where
fromJSON :: JSON -> Validation ParseError a
what a `Validation` does is collect errors which occur in the parsing of a value `a`. All of the errors which arise throughout parsing are collected.Now, core.spec takes advantage of Clojure's core data language (EDN or whatever) which has the property of having concrete "paths" from the root of any value down to any sub-value within it. This is a nice property in that core.spec can pinpoint the location of an error within a type.
But it's a property of the shapes of Clojure data, not a property of Spec. We can write a type in, e.g., Haskell, which has the same properties. If we replaced JSON with that type then we'd have the exact same property.
> Automated test generation
This one makes me laugh a little bit. Automatic test generation was invented for use in Haskell's type system. For this we have the typeclass
class Arbitrary a where
arbitrary :: Gen a
where `Gen` denotes a randomly chosen value which can be "shrunk" to find a more minimal example. Create a collection of Arbitrary instances for the data of your system and you'll immediately be able to generate "property tests".Reid Draper, original author of test.check, actually was cribbing the design precisely from Haskell's QuickCheck library.
---
So we might wish our types did these things... But we'd be mistaken to do so. Our types already do these things and have for longer than Clojure has been around!
Furthermore, I really can't let it pass that Clojure's core.spec is not a form of static typing at all. It enables no guarantees at compile time, only at runtime.
Core.typed is a static type system and is outside of the compiler as you note, but there's nothing special here. People put type systems in their compilers for (a) convenience and (b) to guarantee the things fed to the compiler are well-typed so that the compiler can generate more efficient artifacts. Both of those reasons are auxiliary, optional. There has never been a reason why a language couldn't just build an external type-checker. Hell, Flow and TypeScript are more or less exactly that.
Sorry to come on strong, but I think it's quite far from the truth to take these things as benefits of dynamism. They have always been there. Dynamic languages just make them a little harder to access.