Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.

First time visiting? Here are some places to start:

  1. Looking for a certain topic? Check out the categories filter or use Search (upper right).
  2. Need support? Ask a question to our Community Support category.
  3. Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
  4. Be respectful, on topic and if you see a problem, Flag it.

If you would like to contact our Community Manager personally, feel free to send a private message or an email.

Operator form queries

konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 899 ✭✭✭✭✭
edited November 2017 in FeatureScript
Would like to share my thoughts about compound query syntax. If you have to apply several query filters you getting coplex nested construction with arguments on the right side and subqueries on the left which is rather hard to read and edit like that:
qVertexAdjacent(qNthElement(qOwnedByBody(definition.testQuery,EntityType.VERTEX),1),EntityType.EDGE) 
and when you finnaly want to view a debug display of that query you need to assign it to separate variable.

And i decided to create an operator-form query syntax, which is based on operator overloading for (query*lambda) expression, lambda wrappers on built-in query functions, and wrapping built-in debug function in lambda that returns it's argument. Now you can use a simple, consistent syntax for compound query expressions like that:
qOwnedByBody(definition.testQuery) *
           qEntityFilter(EntityType.VERTEX) *
           qNthElement(1) *

debug(context)*
           qVertexAdjacent(EntityType.EDGE) *
           debug(context);
The evaluation order was inherited from * operator and goes from left to the right (from top to bottom for vertical formatting).
Each wrapper returns a lambda function of built-in query with a user-defined second parameter. That labmda processes the result of evaluation of prevous functions which always is of Query type. The first equation of this conveyor should allways be of Query type as well. And finally you can add specially overloaded debug operator at any stage of evaluation, and will not brake the evaluation because internally it is wrapper over built-in debug function that calls it and then returns the argument back to evaluation process.

Some examples of code that provides these capabilities:
* operator overloading
//Rule for query interaction with lambda's by "*" operator
export operator*(query is Query, queryFunction is function)
{
return queryFunction(query);
}

debug function wrapping:
//debug
export function debug(context is Context)
{
    return function(value)
        {
            debug(context, value);
            return value;
        };
}
lambda-wrappers for some commonly used built-in query functions:
//return  lambda for qBodyType(subquery, BodyType)
export function qBodyType(bodyType is BodyType)
{
return function(subquery)
{
return qBodyType(subquery, BodyType);
};
}

//return lambda for qBodyType(subquery, array of BodyType's)
export function qBodyType(bodyTypes is array)
{
return function(subquery)
{
return qBodyType(subquery, bodyTypes);
};
}

//return lambda for qEntityFilter(subquery, EntityType)
export function qEntityFilter(entityType is EntityType)
{
return function(subquery)
{
return qEntityFilter(subquery, entityType);
};
}

//return lambda for qNthElement (subquery is Query, n is number)
export function qNthElement(n is number)
{
return function(subquery)
{
return qNthElement(subquery, n);
};
}

//return lambda for qAttributeFilter (subquery is Query, attributePattern) returns Query
export function qAttributeFilter(attributePattern)
{
return function(subquery)
{
return qAttributeFilter(subquery, attributePattern);
};
}

//return lambda for qOwnedByBody (body is Query, entityType is EntityType) returns Query
export function qOwnedByBody(entityType is EntityType)
{
return function(body)
{
return qOwnedByBody(body, entityType);
};
}

// qVertexAdjacent (query is Query, entityType is EntityType) returns Query
export function qVertexAdjacent(entityType is EntityType)
{
return function(query)
{
return qVertexAdjacent(query, entityType);
};
}

// qEdgeAdjacent (query is Query, entityType is EntityType) returns Query
export function qEdgeAdjacent(entityType is EntityType)
{
return function(query)
{
return qEdgeAdjacent(query, entityType);
};
}

// qConstructionFilter (subquery is Query, constructionFilter is ConstructionObject) returns Query
export function qConstructionFilter(constructionFilter is ConstructionObject)
{
return function(subquery)
{
return qConstructionFilter(subquery, constructionFilter);
};
}

// qSketchFilter (subquery is Query, sketchObjectFilter is SketchObject) returns Query
export function qSketchFilter(sketchObjectFilter is SketchObject)
{
return function(subquery)
{
return qSketchFilter(subquery, sketchObjectFilter);
};
}

// qContainsPoint (subquery is Query, point is Vector) returns Query
export function qContainsPoint(point is Vector)
{
return function(subquery)
{
return qContainsPoint(subquery, point);
};
}





link to the document:
https://cad.onshape.com/documents/b2bef3c68edf40c9d2bff9cd/w/b5d571e7d6216fbb6e7b56b8/e/4e50b5b6d035682501ff1fde

Comments

  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 921
    Nice!

    Originally I resisted the urge to do this type of thing because it is a bit of an abuse of notation, but it's hard to argue with your example -- it is certainly way more readable with the overloads.  I'm going to think about putting something like that in std.

    Things I would probably do a little differently / in addition:
    1. I would add a QueryFilter type and tag the lambdas with it -- that way, you don't accidentally multiply with some arbitrary function.
    2. I would (maybe -- I'm not certain) flip the order to be consistent with function composition -- that would require an overload of multiplying two QueryFilters
    3. I would probably add + for qUnion and - for qSubtraction.  qIntersection would be left without an operator, but it is much more rare (currently 26 std occurrences vs. 63 qSubtraction and 267 qUnion).  The algebra doesn't quite work out (i.e., a + b - c is not the same as a - c + b) but it's close.
    Ilya Baran \ Director, Architecture and FeatureScript \ Onshape Inc
  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 899 ✭✭✭✭✭
    edited November 2017
    Thanks @ilya_baran
    Initially I thought about making a function
    compoundQuery(subquery is Query, filterSequence is map) returns Query
    where filter sequence has a structure like {"queryFilterName1": filterCondition1,...}. but without autocomplete of filterSequence fields this is not very usefull.
    I agree with your first point. About evaluation order - it is so because i didn't ever thought that I can affect on it by overloading functions interaction, though i can find some convenience in it now.
    About other query functions that combine two or more arguments of query type I don't see much use of overloading them, because it is not a big deal to have some finishing short line of code.
    But appending array by += operator would be handy.
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 921
    @kevin_o_toole_1 has also proposed an alternative approach: we could add
    a -> methodCall(b, c)
    as syntactic sugar for
    methodCall(a, b, c)
    And I think maybe something like
    b -> methodCall(a, ^, c)
    for the same thing (except b would be evaluated first), to make debug work.  With this, your example with no library changes would read:
    qOwnedByBody(definition.testQuery)->
               qEntityFilter(EntityType.VERTEX)->
               qNthElement(1)->

    debug(context, ^)->
               qVertexAdjacent(EntityType.EDGE)->
               debug(context, ^);
    Which is about as readable as the original example and doesn't suffer from the weirdness of overloaded multiplication and left-to-right vs. right-to-left questions.  At the same time, the downside is that it's suggestive of an actual method defined on the class (or worse, a lambda in some languages), rather than just a function call.
    Ilya Baran \ Director, Architecture and FeatureScript \ Onshape Inc
  • lemon1324lemon1324 Member, Developers Posts: 164 EDU
    edited November 2017
    I'm a fan of some sort of syntactic sugar approach like this.  When @konstantin_shiriazdanov first suggested standardized notation in a response to one of my posts, my first thought was something more like the method chaining you get in JavaScript, where one defines the query functions as methods of Query objects returning Query, and then further chained method calls further filter the query (or perform a debug and return the same query).  With the same example, this would look like:
    qOwnedByBody(definition.testQuery)
               .qEntityFilter(EntityType.VERTEX)
               .qNthElement(1)

    .debug(context)
               .qVertexAdjacent(EntityType.EDGE)
               .debug(context);
    Where context doesn't need the caret since it's operating on the current Query at that point; similarly, qUnion would take one parameter or an array, and add to the query, etc.  I prefer this notation a bit over the arrow notation, but that's just me.

    Does Onshape currently evaluate a non-null Query as true, or throw an error?  If currently an error, overloading Query & Query for intersection and Query ^ Query for exclusive-or/symmetric difference along with + and - for union and subtraction would seem to make sense. I don't see overloading the non-commutative operations as a huge problem.
    Arul Suresh
    PhD Candidate at Stanford University
  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 899 ✭✭✭✭✭
    edited November 2017
    Since those functions on subqueries behave like filters i would be happy to use any symbol to qlue them in one conveyor, will it be dot or arrow or &, but implementing object-oriented approach seem to be more usefull in other applications, for not only queries.
    By the way Kevin's approach reminds me multy-argument function curring from Haskell. Looks like there going to begin "OOP vs Functional" battle :D
Sign In or Register to comment.