klapaucius icon indicating copy to clipboard operation
klapaucius copied to clipboard

:predicate type

Open Vaguery opened this issue 7 years ago • 1 comments

The :predicate type is a tuple of

  1. a type (stack name here)
  2. 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 new Predicate(:X, #(= % [item]))
  • :X->equalitypredicate2 creates a new Predicate(:X, equal?)
  • :X->lessthanpredicate1 pops the top :X (call it A) and creates a new Predicate(:X, #(< % A))
  • :X-> lessthanpredicate2 creates a new Predicate(:X, lessthan?)
  • :X->greaterthanpredicate1 pops the top :X (call it A) and creates a new Predicate(:X, #(> % A))
  • :X-> greaterthanpredicate2 creates a new Predicate(:X, greaterthan?)
  • :X->contains1 pops the top :X (call it A) and creates a new Predicate(:X, #(contains? % A))
  • :X-> contains2 creates a new Predicate(:X, contains?)
  • :X->contained1 pops the top :X (call it A) and creates a new Predicate(:X, #(contained-in? % A))
  • :X-> contained2 creates a new Predicate(: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 new Predicate(: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 is true, it uses the item in the first position in each comparison, if false, it uses it in the second position
  • :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
  • :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]]
  • :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

(and so on, by extension)

Vaguery avatar Dec 09 '17 16:12 Vaguery

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.

Vaguery avatar Dec 09 '17 16:12 Vaguery