klapaucius
klapaucius copied to clipboard
:predicate type
The :predicate
type is a tuple of
- a type (stack name here)
- a predicate function with one (or two) arguments of that type, returning a
:boolean
For example, here are some one-argument :predicate
definitions:
-
Predicate(:scalar, #(% > 9.3))
-
Predicate(:string, #(contains? % "foo"))
-
Predicate(:string, #(= % "bar"))
-
Predicate(:scalars, #(contains-permutation-pattern? % [2,1,3,5,4])
-
Predicate(:matrix, #(multiplication-compatible? % Matrix[[1 2 3],[4 5 6]])
-
Predicate(:set, #(nonempty-intersection? % #{7, "a", \g, [4 5]))
-
Predicate(:complex, #(dominates? % Complex(8,2))
-
Predicate(:vector, mixed-type?)
Here are some two-argument :predicates
:
-
Predicate(:scalar, <=)
-
Predicate(:string, contains-with-gaps?)
-
Predicate(:interval, overlaps?)
-
Predicate(:code, contains?)
Making :predicate
objects
-
:predicate-fromtemplate
pops top:predicate
and makes a new one of the same type (and function) from the top item on the appropriate stack
For "predicatable" types:
-
:X->equalitypredicate1
pops the top:X
and creates a newPredicate(:X, #(= % [item]))
-
:X->equalitypredicate2
creates a newPredicate(:X, equal?)
-
:X->lessthanpredicate1
pops the top:X
(call it A) and creates a newPredicate(:X, #(< % A))
-
:X-> lessthanpredicate2
creates a newPredicate(:X, lessthan?)
-
:X->greaterthanpredicate1
pops the top:X
(call it A) and creates a newPredicate(:X, #(> % A))
-
:X-> greaterthanpredicate2
creates a newPredicate(:X, greaterthan?)
-
:X->contains1
pops the top:X
(call it A) and creates a newPredicate(:X, #(contains? % A))
-
:X-> contains2
creates a newPredicate(:X, contains?)
-
:X->contained1
pops the top:X
(call it A) and creates a newPredicate(:X, #(contained-in? % A))
-
:X-> contained2
creates a newPredicate(:X, contained-in?)
Some special instructions for particular types are feasible (but not included), as well, such as:
-
:string->close?
pops a:string
A and:scalar
B and creates a newPredicate(:string, #(< (edit-distance % A) B)
What they do
Whenever these instructions fire, the top predicate may want one or two arguments. By convention a :boolean
will be used to determine whether a constant will be in the first position or second if there are two arguments in the predicate.
-
:predicate-apply
pops the one or two items of the predicate's type, and returns the:boolean
result -
:predicate-map
- if it's a 1-arg
:predicate
, applies the predicate to every item on the top item of the appropriate vectorized type, returning a:booleans
vector of the same length - if it's a 2-arg
:predicate
, pops one item from vector, one from items, one from:boolean
; if:boolean
istrue
, it uses the item in the first position in each comparison, iffalse
, it uses it in the second position
- if it's a 1-arg
-
:predicate-split
- if it's a 1-arg
:predicate
, takes the top item from the appropriate vectorized stack, and returns two vectors of items that match and those that don't - if it's a 2-arg
:predicate
, pops one item from vector, one from items, one from:boolean
; based on:boolean
it puts the item in the first or second positions
- if it's a 1-arg
-
:predicate-first
takes the top vector of that type, returns the first item matching the predicate (if any); for 2-arg version, see above -
:predicate-next
takes the top vector of that type, returns the first item matching the predicate (if any) AND the reduced vector; for 2-arg version, see above -
:predicate-hoist
takes the top vector of that type, pushes the same vector with the first matching item brought to the front; for 2-arg version, see above -
:predicate-remove
takes top vector, returns new vector with only failing items; for 2-arg version, see above -
:predicate-keep
takes top vector, returns new vector with only passing items; for 2-arg version, see above
Fancier stuff:
-
:predicate-applyeach
pops a:predicate
- if it's 1-arg, works as
:predicate-apply
- if it's 2-arg, pops two vectors, applying each item in turn;
- interpreter mode
:wrap-vectors? true
works like R, cycling the items of the shorter vector to get enough items to compare; for example if these are:scalars
, and args are[1 2 3 4 5]
&[6 7]
, we produce a result[[1 v 6] [2 v 7] [3 v 6] [4 v 7] [5 v 6]]
- interpreter mode
:wrap-vectors? false
terminates when the shorter vector runs out of items; for example if these are:scalars
, and args are[1 2 3 4 5]
&[6 7]
, we produce a result[[1 v 6] [2 v 7]]
- interpreter mode
- if it's 1-arg, works as
-
:predicate-mapeach
pops a:predicate
- if it's 1-arg, works as
:predicate-map
- if it's 2-arg, pops two vectors, applying each item in turn;
- interpreter mode
:wrap-vectors? true
works like R, cycling the items of the shorter vector to get enough items to compare - interpreter mode
:wrap-vectors? false
terminates when the shorter vector runs out of items
- interpreter mode
- if it's 1-arg, works as
(and so on, by extension)
This will probably lead to some refactoring of the DSL, which is a good thing.
Consider bringing predicate functions up into a collection associated with each type, rather than being hard-coded inside the DSL definition. For instance, :scalar-≤
should probably invoke the :scalar
type's ≤
predicate in its DSL definition. That way, the :predicate
object has access to it as well. Also faster.
The comparable
aspect might insert these predicates into the type.