What I'd like to see is something similar to what we did — checking more than just types. Here's an example from our code:
(conforms example-object [map :from string? :to [map :required-keys {"products" [seq string?] "type" [seq #{"type1" "type2"}]}]])
This describes a map containing mappings from strings to maps of a certain kind (where "products" and "type" are required, "products" must map to a sequence of strings and "type" must map to a sequence of strings from a specific set).
From our "conforms" docstring:
Returns true if value conforms to typespec. typespec can be either: - a string, which value must match literally; - a predicate, which value must satisfy; - [seq sub-typespec], which means that value must be a collection of elements, each of which must conform to sub-typespec; - [map specifiers...], where optional specifiers may include: :from sub-typespec -- keys in the map must match sub-typespec; :to sub-typespec -- likewise for values; :required-keys [key1 typespec1 key2 typespec2...] -- map must include the required keys and their values must match the typespecs.
We don't use that for function contracts and it might be overkill for that, but I'm pretty certain I'd like to be able to specify more than just types for map keys. A list of possible values would be tremendously useful.
This could provide you with some ideas. I can of course contribute the "conforms" function (although it isn't that difficult to write).
Actually, schema can express arbitrary constraints. Your example translates to schema as:
{String {(s/required-key "product") [String]
(s/required-key "type") (s/enum "type1" "type2")
s/Any s/Any}} ;; allow any other k-v pairsLooks like I'll be using this library sooner than I thought, thanks!
I would have thought that this would be extremly hard to automate for non-trivial schemas, or at least that core.typed would have a hard time proving that return values match a schema. Is this only for a subset?
One of the problems with gradually annotating an untyped namespace with core.typed is we immediately have to be very accurate with our type annotations.
However if the namespace is completely "schema'ed", we can assume functions simply take `Any` as arguments and rely on Schema to regain type information.
This could be a very nice way to work.
Also, functional programming is heavily focused on data transformations, which in practice means lots of deeply nested heterogenous data structures... these types of structure are usually tedious to put into a static type system, but your system appears to make it easy.
eg. Heterogeneous keyword maps https://github.com/frenchy64/core.typed-example/blob/master/...