Skip to content

Instantly share code, notes, and snippets.

@fogus
Last active January 10, 2017 14:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fogus/6c10c2111182abad11d51d03a171b159 to your computer and use it in GitHub Desktop.
Save fogus/6c10c2111182abad11d51d03a171b159 to your computer and use it in GitHub Desktop.

If you've looked into the relatively new Clojure library clojure.spec you might have come across something curious. Observe the use of core.spec/or:

(require '[clojure.spec :as s])
    
(s/def ::num (s/or :float float? 
                   :int   int? 
                   :ratio ratio?))
    
(s/conform (s/coll-of ::num) [0.25 1/2 1])
;;=> [[:float 0.25] [:ratio 1/2] [:int 1]]

The result of the call to s/conform is quite descriptive in the way that it mirrors the vector [0.25 1/2 1] provided. Indeed, the form of the return looks interesting like a naive version of a parse-tree, but why should such a thing happen? What's the purpose of the s/conform function in light of the fact that clojure.spec already provides functions for validation and rich descriptions of their failures:1

(s/valid? (s/coll-of ::num) [0.25 1/2 1])
;;=> true
    
(s/valid? (s/coll-of ::num) [0.25 1/2 :blarg])
;;=> false
    
(s/explain (s/coll-of ::num) [0.25 1/2 :blarg])
    
;; In: [2] val: :blarg fails spec: :user/num at: [:float] predicate: float?
;; In: [2] val: :blarg fails spec: :user/num at: [:int] predicate: int?
;; In: [2] val: :blarg fails spec: :user/num at: [:ratio] predicate: ratio?

It certainly seems that if clojure.spec were limited to validation and explanation then it would be fairly useful in its own right. However, clojure.spec provides capability beyond validation and explanation precisely because it's not design to necessarily solve those problems. Instead, they are components of a solution for solving a much more pernicious "language problem".

(this transition is awkward)

While functional languages in general and Clojure specifically fosters the use of aggregations of simple data to represent complex domain information, the ugly truth is that each and every data aggregation necessarily constitutes its own mini-language. Of course, the nature of languages is such that their meaning and interpretation is encoded in custom parsing code. That is, prior to the introduction to clojure.spec Clojure programmers had to create ad hoc parsers for walking their domain structures and identifying and reporting any errors or inconsistencies. Tools like the useful Schema library helped to alleviate the complexities around the problem of the data mini-language, but it focused solely on the problem of validation. The clojure.spec library on the other hand recognizes that there is a fundamental synergy between specification, parsing, combination, validation, explanation, and generation and provides an extremely powerful tool for managing the complexities inherent in data mini-languages.

I'm going to take some time over the next few weeks to write about clojure.spec and explore some of its uses and advantages, specifically as they pertain to the problem of mini-languages brought on by the use of domain data encoding. Stay tuned.

:F


Post meta:

  • This is intentionally meant to be short and sweet and provide a lead-in to a larger "series"
  • Eliminate the awkward transition
  • Is clojure.spec the prefered name?
  • Link in David's "Tools for Thought" post
  • Link to Schema
  • "synergy" the right word?
  • "language problem" not precise anough

Footnotes

  1. A more interesting option is to use explain-data to receive a data structure the error explanation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment