Skip to content

[discussion] Define and prove invariants of the function #20

@serzh

Description

@serzh

Basics

This is proposal and discussion on how to specify behavior of a function using refined types.

At first, let's consider an examples of functions and their behaviors you may want to specify:

  1. filter-by-type :: [TimelineItem] ItemType -> [TimelineItem], this function takes list of timeline items and filters them out by give type. The behavior you want to specify:
  • (B1) output is always a subset of the input list
  • (B2) all items in output must be only of given type
  1. create-profile: Email Name -> Profile, where Profile is {:email Email, :name Name, :id UUID}. This function creates a profile with given email and name. The behaviour to specify:
    • (B3) Profiles email and name should be equal to the provided one

It is clear that this behaviors are a key to the valid function specification. Without them type information doesn't capture the semantics of functions. You can write a function that will satisfy input and output types but won't behave correctly. The specifications of behaviors restrict possible implementation to more desirable.

If you try to formalize this behaviors it wil become clear that they are always functios of the input values:

  • (B1) is a function of input timeline items
  • (B2) is a function of the input type
  • (B3) is a function of input email and name

But it's even more specific: given the output type ([TimelineItem] or Profile), behavior are function from the inputs to the refinements of the output type:

  • (B1) output type = [TimelineItem]; behavior is a refinement of the output, that restricts all [TimelineItem] to only subsets of the input timeline items
  • same for other behaviors

(github doesn't have support for LaTEX, putting an image here :( )
screen shot 2018-06-30 at 22 05 48

Relying on the statement above, we can model behavior as a functions from inputs to predicates (refinements) that will later be applied to the Output type:

;; B1
(defn is-subset [{:keys [items]}]
 #(set/subset? (set items) %)
 
;; B2
(defn has-given-type [{:keys [type]}]
 (r/ForAll #(= (:type %) type))
 
;; B3
(defn has-given-email [{:keys [email name]}]
 (r/And (r/On :email (s/eq email))
        (r/On :name (s/eq name))))

The nice property of behaviors being refinements is that they are perfectly composable. Say you want B1 and B2 to be described as single behavior:

(defn fileter-by-type-behavior [args]
 (s/And (is-subset args)
        (has-given-type args)))

How to do it in schema-refined

We need to create API for specifying behavior for a functions. We should definitely stick with plumatic/schema way of defining typed functions (like here), but add optinal behaviors to that syntax:

(r/defn filter-by-type :- [TimelineItem] | is-subset has-given-type
        [items :- [TimelineItem]
         type  :- (s/enum "post" "photo")]
 ...)

Let's discuss it

TODO

  • Discuss term specifying behavior. I really don't like it, the word specifying may be confused with clojure.core.spec. Maybe invariant whould be much better.
  • Create API for add behavior to the function
  • Write documentation on
    • Rationale
    • API
    • How to plug it in to the REST, RPC, etc

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions