Assertions in dynamic languages give you the benefits of static type safety, with less code and ceremony

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.

Interesting:

Well engineered non-trivial systems written in dynamic languages embrace runtime assertions especially near public interfaces. For example here’s a snippet of code from React.js that does exactly that:

  _renderValidatedComponent: function() {
    /* ... */
    invariant(
      renderedComponent === null || renderedComponent === false ||
      ReactElement.isValidElement(renderedComponent),
      '%s.render(): A valid ReactComponent must be returned. You may have ' +
        'returned undefined, an array or some other invalid object.',
      inst.constructor.displayName || 'ReactCompositeComponent'
    );
    return renderedComponent;
  },

Embracing assertions means a significantly more pleasant experience for newcomer and expert alike. While the learned can wax poetic for days about the utility of strong types for program design, the immediate benefit of stronger types is simply catching errors sooner and closer to the source of the problem! Well placed runtime assertions deliver precisely the same benefit.

As the above React.js assertion alludes, asserting function arguments and return values yields the most bang for the buck.

ClojureScript like Clojure has direct support for this pattern in the form of :pre and :post conditions. This is yet another old good idea.

Here’s an example from Om 0.8.0. The get-props helper validates that its argument is a valid Om component:

(defn get-props
  "Given an owning Pure node return the Om props. Analogous to React
   component props."
  ([x]
   {:pre [(component? x)]}
   (aget (.-props x) "__om_cursor"))
  ([x korks]
   {:pre [(component? x)]}
   (let [korks (if (sequential? korks) korks [korks])]
     (cond-> (aget (.-props x) "__om_cursor")
       (seq korks) (get-in korks)))))

:pre takes a vector of arbitrary predicate expressions, they must all return a truthy value otherwise the program will crash.

:post conditions are similarly useful. For example it’s a common pattern to declare a protocol that other users can extend. This permits the design of pluggable systems – the idea is little different from Java interfaces, Go interfaces, Objective-C protocols, Haskell typeclasses etc.

(defprotocol IReturnEven
  (-return-even [x]))

One useful pattern to pair with this – provide a common entry point:

(defn return-even [x]
  (-return-even x)

The benefit of the common entry point is that implementers can focus on their logic and the common entry point can provide shared assertion checking:

(defn return-even [x]
  {:pre  [(number? x)] 
   :post even?}
  (-return-even x)

Now people that want to extend your system will get early errors when their implementations fail to pass the assertion.

Post external references

  1. 1
    http://swannodette.github.io/2015/01/09/life-with-dynamic-typing/
Source