A typed filter API
makeProperty turns a selector into a named property handle. That is enough for a small query API: the compiler checks the property, the handle names it for display, and the accessor reads it.
API
where(_.year)("> 1950")(_ > 1950)
A filter keeps the property handle, a readable description and the test. inline passes the selector through to the macro.
scala
final case class Filter[T, V](property: PropertyWithAccessor[T, V], operator: String, test: V => Boolean) {
def apply(value: T): Boolean = test(property.get(value))
def describe: String = s"${property.name} $operator"
}
inline def where[T, V](inline selector: T => V)(operator: String)(test: V => Boolean): Filter[T, V] =
Filter(PropertySupport.makeProperty[T, V](selector), operator, test)
Live
Filter a list
Pick a filter. The label comes from the property handle, not from a string you typed twice.
Books
Three filters over one list.
- filter.describe
- year > 1950
- books.filter(filter).map(_.title)
- The Left Hand of Darkness, Foundation, The Dispossessed
Beyond lists
The same handle, other targets
property.name is what a database query, a URL parameter or a JSON Patch path needs. The test runs in memory; the name travels.
scala
def toQueryParameter(filter: Filter[?, ?], value: String): String =
s"${filter.property.name}=${java.net.URLEncoder.encode(value, "UTF-8")}"
json-mapper works the same way
EntitySchema.property(_.title) is this pattern: a selector in, a named property handle with an accessor out.