-
Notifications
You must be signed in to change notification settings - Fork 2
Description
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:
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
create-profile: Email Name -> Profile, whereProfileis{:email Email, :name Name, :id UUID}. This function creates a profile with given email and name. The behaviour to specify:- (B3) Profiles
emailandnameshould be equal to the provided one
- (B3) Profiles
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 :( )

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 wordspecifyingmay be confused withclojure.core.spec. Maybeinvariantwhould 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