read me

Clojure’s :pre and :post

Dec 21, 2009

One of the more exciting features of the upcoming Clojure 1.1 release is the inclusion of pre and post assertion functions. Some preliminary details can be found in the Clojure documentation, but I thought I’d quickly cover a simple example with potentially far-reaching implications; the case of making :post assertions relative to the input argument(s).

Let’s re-write the simple function constrained-sqr as found in the Clojure documentation linked above and re-cast it as constrained-fn:

(defn constrained-fn [f x]
  {:pre  [(pos? x)]
   :post [(= % (* 2 x))]}
  (f x))

(constrained-fn #(* 2 %) 2)
;=> 4
(constrained-fn #(float (* 2 %)) 2)
;=> 4.0
(constrained-fn #(* 3 %) 2)
;=> java.lang.Exception: Assert failed: (= % (* 2 x))

So what have I done? By specifying a :post assertion of (= % (* 2 x)) I’ve constrained the passed function f to be (effectively) a doubling function. I’ve done this by constraining the result of constrained-fn relative to its input. There is nothing entirely ground-breaking about pre and post conditions, but they are surprisingly powerful. By stripping out the assertions into a wrapper function I’ve detached some hairy logic from a potentially globally useful function and isolated it in an aspect (if you will) named constrained-fn. Now we could wrap the function f using a different constraining function depending on the context in which it is intended to be used; for example jimmys-fn, logged-result-fn, only-ints-fn, payroll-dept-fn, etc… In fact, I like that word aspect; it has a nice ring to it. I wonder if anyone has thought of that before?

-m

Related posts:

  1. Clojure Golf episode 1 This is an attempt to have some fun with Clojure....
  2. Understanding the Clojure -> macro On page 109 of Paul Graham’s ANSI Common Lisp he...
  3. De-chunkifying Sequences in Clojure At the first CAP CLUG meetup I gave a presentation...
  4. Clojure Golf – Episode 2: Largest Prime Factor In the last episode of Clojure Golf we saw some...
  5. Comparing Lines of Code: Scala and Clojure (FUD version) A completely tongue in cheek comparison between Scala and Clojure,...

Related posts brought to you by Yet Another Related Posts Plugin.

1 Comment 15 Tweets 1 Comment

32 Comments, Comment or Ping

  1. Clojure’s :pre and :post: http://bit.ly/5EK0Or Comments: http://bit.ly/7Nweg2

    This comment was originally posted on Twitter

  2. Clojure’s :pre and :post: Comments http://url4.eu/xrMt

    This comment was originally posted on Twitter

  3. I love this idea.

    This comment was originally posted on FriendFeed

  4. Reactions to Clojure :pre and :post conditions: 1. Cool. 2. Dude, contracts, kinda. 3. Unnecessary with strong typing? http://bit.ly/4HmHux

    This comment was originally posted on Twitter

  5. RT @fogus: #Clojure :pre and :post http://blog.fogus.me/2009/12/21/clojures-pre-and-post/

    This comment was originally posted on Twitter

  6. RT:@al3x:Reactions to Clojure :pre and:post conditions:1.Cool.2. Dude,contracts,kinda.3.Unnecessary with strong typing? http://bit.ly/4HmHux

    This comment was originally posted on Twitter

  7. Your post condition (= % (* 2 x)) constrains your function to be a “doubling” function, not a “squaring” function.

    I think you wanted this post condition: (= % (* x x))

  8. Clojure’s :pre and :post http://bit.ly/8JYW4X

    This comment was originally posted on Twitter

  9. You are correct… the sad part is that I do not recall which one I actually wanted. I guess I’ll flip a coin. :-)

    Thank you -m

  10. RT @fogus #Clojure :pre and :post http://tr.im/Iiep

    This comment was originally posted on Twitter

  11. Easy pre and postconditions in clojure. http://blog.fogus.me/2009/12/21/clojures-pre-and-post/

    This comment was originally posted on Twitter

  12. Sean Devlin

    Hey, really cool post. I’d make a change to your wrapping fn

    (defn constrained-fn [f & args]
      {:pre  [(pos? (first args))]
       :post [(= % (* 2 (first args)))]}
      (apply f args))

    This way you can pass fns with multiple arguments and have them work correctly.

    Sean

  13. Is it just me, or is the fact they’re throwing java.lang.Exception’s rather than say AssertionException a little smelly.

  14. #Clojure’s :pre and :post => http://blog.fogus.me/2009/12/21/clojures-pre-and-post/

    This comment was originally posted on Twitter

  15. HNews: Clojure’s :pre and :post http://bit.ly/6fAwsM

    This comment was originally posted on Twitter

  16. Yes, your apply version is good in that it moves further in the direction that I’d like to see :pre and :post evolve — that is, as elements of a more generic function overlay system. There is a current disparity in that the `:pre` and `:post` are specific to a particular “shape” of `f` while `(apply f args)` is generic.

    Thanks for reading.

    -m

  17. Do you mean java.lang.AssertionError? Good question. I can only speculate, but my guess is that throwing anything derived from java.lang.Error would be overkill. Seems like a fair question for http://groups.google.com/group/clojure.

    -m

  18. RT @fogus: #Clojure :pre and :post http://blog.fogus.me/2009/12/21/clojures-pre-and-post/

    This comment was originally posted on Twitter

  19. You might enjoy reading about PLT Scheme’s DbC system:

    http://docs.plt-scheme.org/guide/contract-boundaries.html

  20. You mentioned Gregor and Aspects but not Bertrand and DbC/Eiffel?

    Boo!

  21. mikhailfranco

    Have a look at this if you want to generalize ’shape’

    http://conal.net/blog/posts/semantic-editor-combinators/

    Mik

  22. Upcoming Clojure’s :pre and :post. #in http://bit.ly/8z5Yli

    This comment was originally posted on Twitter

  23. Clojure gets (Eiffel-like?) pre- and post-conditions http://blog.fogus.me/2009/12/21/clojures-pre-and-post/

    This comment was originally posted on Twitter

  24. Schön.. Clojure bekommt Pre und Post Condition. http://bit.ly/55joMP

    This comment was originally posted on Twitter

  25. Ah, at first I thought these were going to be similar to CLOS’s method combination (:before :after :around).

    This comment was originally posted on Hacker News

  26. RE: Bertrand and Eiffel

    The omission was not intentional, but I didn’t want to mention other systems/langs that I couldn’t claim to know at least a little bit about. As for Eiffel — it keeps popping up during these :pre and :post discussions, so I suppose there is no way to postpone some deeper research. Thanks for the pointers.

    -m

  27. RE: Semantic Editors.

    Thanks for the link; I flagged this post a few days ago in my Zeitgeist-feed but haven’t had the chance to dig into it.

    -m

  28. @Rettke,

    Have I ever mentioned that PLT Scheme never ceases to amaze me? Well, I am now.

    -m

  29. Ivan Toshkov

    How is that better than what we have today?

    (defn constrained-fn [f x] (assert (pos? x)) (let [r (f x)] (assert (= r (* 2 x))) r))

    One can easily write a simple macro to handle the boilerplate.

  30. @Toshkov

    One advantage that :pre and :post have over assert is that the former allows the “assertions” to potentially come from a different source than the body of the function. This is what my example implies, even if I didn’t elucidate that exact point. :pre and :post also make it relatively easy to do some fancy automatic test generate and as a bonus provide a form of documentation for the function. Chris Houser added another interesting point that I was not aware of, “using assert instead of :pre is usually pretty straightforward, if you had to do it. Using assert instead of :post is a pain.” Sounds reasonable. Along that point, Chas Emerick’s assessment that :pre and :post cover 99.5% of assert uses, plus provide simple, and consistent hooks.

    Hope that helps. Thanks for reading.

    -m

  31. Alexander

    One thing that is always missed when people try to re-implement Eiffel’s design by contract is that the real strength of design by contract is how it interacts with polymorphism.

    In Clojure, :pre and :post should have been defined on generic functions where they shine.

    You don’t want simple :pre and :post, you want abstractions such as constrained-fn in the example above.

    In design by contract, constrained-fn should be a multi-method. The contract for constrained-fn should be that:

    Precondition: The argument should be positive, BUT CAN BE RELAXED.

    Postcondition: The result should be doubled, BUT CAN BE STRENGTHENED.

    Thus, preconditions should be combined by disjunction. Postconditions should be combined by conjunction.

    This should be done by the multi-method dispatch system. All preconditions for applicable functions should be ORed together and evaluated. The postconditions for all applicable functions should be ANDed together and evaluated.

    Why is this essential feature missed by everyone?

  32. @ Alexander

    Thanks for the great comment. There is much to ponder and there no longer and excuse for me to avoid Eifel.

    Thanks for reading.

    -m

Reply to “Clojure’s :pre and :post”

Additional comments powered by BackType