Hacker News new | ask | show | jobs
by aria 4664 days ago
Author here. Happy to answer any questions or comments.
6 comments

This is a very good idea. And the reason I say that is because in our code base we have developed something similar, feeling the need for it :-). Our stuff is half-baked, though and I never got around to releasing it.

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).

Thanks for the feedback!

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 pairs
Interesting! Is the enum automatically a vector/sequence, or is that just an omission in the example?

Looks like I'll be using this library sooner than I thought, thanks!

Nope, I missed that in the example -- the enum should be in square brackets.
You mention generating core.typed annotations from schemas, to allow for some compile-time checking.

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?

core.typed will have to be extended to understand schemas. core.typed already plays nicely with assertions and branches (via occurrence typing), so we just need information on what type a schema "casts" to. This will not be difficult, it might not even have to live in core.typed's implementation and be extended via multimethods.

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.

Hey, I like what you're doing here, I think I'll use this... it gives you a lot of the benefits of a deep type system while being completely orthogonal to the design of the program and without the cognitive overhead of a complex type system.

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.

Thanks! Yeah I think a traditional type system wouldn't make sense for Clojure, you need something that can describe data structures more succinctly, which is what we were shooting for
Just so readers don't deduce you're talking about core.typed, core.typed is plenty expressive enough to represent many idiomatic Clojure data structures, and very succinctly.

eg. Heterogeneous keyword maps https://github.com/frenchy64/core.typed-example/blob/master/...

Thank you for this. I might want to start using Clojure in production now :)
You're welcome :) Please let us know if you have ideas for improving it
I posted on the wrong level, my suggestions are here https://news.ycombinator.com/item?id=6339708