Stream: fhirpath
Topic: Context for `.union`
Josh Mandel (Mar 21 2022 at 17:53):
From #fhirpath.js discussion...
Is it expected that
name.select(use | given)
should return different results from
name.select(use.union(given))
? I would have expected these to be equivalent but in fhirpath.js, union
appears to be operating at the root level even when it occurs inside a select
.
Yury Sedinkin (Mar 21 2022 at 19:28):
I think that the right replacement for:
name.select(use | given)
using union() is:
name.select(use.union($this.given))
It does not work for fhirpath.js right now, but I can fix that if there are no objections.
Josh Mandel (Mar 21 2022 at 21:09):
That doesn't quite sound correct to me; the spec says:
a.union(b)
is synonymous with
a | b
... and so I expect this holds true anywhere the expression occurs (including within a select
expression).
Paul Lynch (Mar 21 2022 at 21:26):
The spec is referring to two collections, "A" and "B" (uppercase), and then gives the example in lowercase. I would interpret that as referring to two collections, not two properties of a parent node.
Josh Mandel (Mar 22 2022 at 01:08):
@Ewout Kramer can you shed any light on the intention here?
Bryn Rhodes (Mar 22 2022 at 20:55):
I don't think we intended to imply any difference in using uppercase versus lower case for those examples. I also don't think that there's any difference between whether the union operates on two collections or on two properties of a parent node (assuming those properties are accessible within the current scope), they would both be interpreted as collection arguments to the union operator.
Bryn Rhodes (Mar 22 2022 at 20:58):
And in the example above:
name.select(use.union($this.given))
This is equivalent to:
name.select(use.union(given))
In other words, the $this
is still referring to the iterator introduced by the .select()
, union does not introduce an iteration scope. So although the use.union($this.given)
should still work, it's not necessary to use $this
.
Paul Lynch (Mar 22 2022 at 21:01):
union does not introduce an iteration scope, but in that example, what is the scope for the collection in "union(...)"?
Bryn Rhodes (Mar 22 2022 at 21:06):
I'm not sure I understand the question.
Bryn Rhodes (Mar 22 2022 at 21:09):
The expression use.union(given)
is the same as saying use | given
, both are just invoking the .union()
function, which takes two arguments, and both those arguments are evaluated in the same scope, which in this case is the one introduced by the select()
function.
Bryn Rhodes (Mar 22 2022 at 21:09):
Is that what you're asking?
Paul Lynch (Mar 22 2022 at 21:11):
Yes. That is clear answer. I think the spec could use some clarification on that point, because it introduces "A" and "B" without any context of where they are coming from. This example from Patient would be better.
Bryn Rhodes (Mar 22 2022 at 21:27):
That's fair, submit a tracker?
Paul Lynch (Mar 22 2022 at 21:36):
Brian Postlethwaite (Mar 23 2022 at 22:48):
Which is the version that is a concatenation of collections that doesn't remove duplicates?
(I know that | does remove duplicates)
Paul Lynch (Mar 24 2022 at 00:42):
"combine"? https://hl7.org/fhirpath/#combineother-collection-collection
Grahame Grieve (Mar 31 2022 at 01:27):
@Bryn Rhodes the difference between name.select(use.union($this.given))
and name.select(use.union(given))
is catching me out.
Grahame Grieve (Mar 31 2022 at 01:44):
you say that
In other words, the $this is still referring to the iterator introduced by the .select(), union does not introduce an iteration scope. So although the use.union($this.given) should still work, it's not necessary to use $this.
but it's more than that. You're saying that .union() doesn't switch context on it's parameters.
Grahame Grieve (Mar 31 2022 at 01:45):
I don't get this. a.func(b), a is the scope for b. but not for union?
Josh Mandel (Mar 31 2022 at 02:26):
I don't get this. a.func(b), a is the scope for b. but not for union?
That is how I read the definitions, yes. How else can a | b
and a.union(b)
be equivalent? It's obvious in the former expression that the context for b
is the same as the context for a
.
Bryn Rhodes (Mar 31 2022 at 02:44):
I don’t see a as the scope for b, a is just an argument, just like b, they are independent of eachother.
Paul Lynch (Mar 31 2022 at 14:26):
Grahame Grieve said:
I don't get this. a.func(b), a is the scope for b. but not for union?
Maybe in a.func(b)
a only becomes the scope for b (one at a time) when func takes and expression
(as in repeat
) and not a collection
(as in union
)?
Paul Lynch (Mar 31 2022 at 14:29):
union
not the only case of this, right? Should Patient.telecom.use.subsetOf(use)
return true?
Josh Mandel (Mar 31 2022 at 15:11):
That's an interesting framing, re: expressions vs collections as arguments. I think it makes sense, though I have not thought through all of the implications.
(I think your specific subset example is challenging though: if you thought the context for evaluating the argument to the subsetOf function was use
, you'd then be evaluating use.use
, which wouldn't make sense.)
Paul Lynch (Mar 31 2022 at 15:25):
Josh Mandel said:
(I think your specific subset example is challenging though: if you thought the context for evaluating the argument to the subsetOf function was
use
, you'd then be evaluatinguse.use
, which wouldn't make sense.)
Yes, my question is whether Patient.telecom.use.subsetOf(use)
should work, just like (I think) you and Bryn are saying Patient.telecom.use.union(use)
should work.
Josh Mandel (Mar 31 2022 at 16:57):
I am aware of two possible interpretations:
a.b.someFunc(c)
evaluatesc
in the same context that it evaluateda
(this is how.union
presumably works, and perhaps all functions that take a collection for their argument)a.b.someFunc(c)
evaluatesb
in the context of elements of thea.b
collection (this is how.where
presumably works, and perhaps all functions that take an expression for their argument)
... but I don't see how Patient.telecom.use.subsetOf(use)
makes sense in either of these intepretations. (Under (1), the "use" argument would be looking for Patient.use
, and under (2), the "use" argument would be looking for Patient.telecom.use.use
-- and neither of these exists.)
Paul Lynch (Mar 31 2022 at 19:00):
Using the union example, wouldn't you say a.b.union(c)
is equivalent to a.b | a.c
? I did drop the select() call from the original example, which might make a difference.
Josh Mandel (Mar 31 2022 at 19:08):
Wow, good question. I was assuming a.b | c
. What do others expect?
Paul Lynch (Mar 31 2022 at 19:09):
After some more thought about the effect of select(), I think if one wants the union of a.b and a.c one could write a.select(b | c)
, or a.select(b.union(c))
, or a.b.union(a.c)
, but not a.b.union(c)
.
Grahame Grieve (Mar 31 2022 at 20:50):
@Bryn Rhodes I'm having trouble with this:
I don’t see a as the scope for b, a is just an argument, just like b, they are independent of each other.
Patient.name.exists(use = 'nickname')
- the scope of use is name
name.select(use.union(given))
- the scope of given is name, not use
I don't have any magic that can make this happen in my code unless I make a special rule for union. Or unless I'm just missing the point here
Paul Lynch (Mar 31 2022 at 20:57):
The only difference I can see is that union takes a "collection" while exists takes an "expression".
Bryn Rhodes (Mar 31 2022 at 21:18):
Consider a similar example:
name.select(use.contains(given))
The scope of given
is still name
(and that scope was introduced by the select
)
Bryn Rhodes (Mar 31 2022 at 21:19):
@Grahame Grieve , does your engine have the same issue with this example?
Grahame Grieve (Mar 31 2022 at 22:28):
yes it does. For the same reason. Because scope works consistently for me
Josh Mandel (Mar 31 2022 at 22:52):
To be clear, Grahame -- are you saying your engine's behavior reflects the behavior you would like the standard to specify?
Paul Lynch (Mar 31 2022 at 23:48):
I think if we are going to have some functions setting the scope while others do not, there needs to be a clearly stated explanation of why that is.
Bryn Rhodes (Apr 01 2022 at 15:17):
The Functions section specifies that if an argument is typed as an expression
, then the function will evaluate the input expression with respect to each item in the input collection, and that these expressions are allowed to refer to the $this
and $index
variables. We don't explicitly call that a scope
, but that is what I've been calling a scope
in this discussion. In the CQL engine this is implemented with a runtime stack, and functions that introduce a scope push a $this variable onto that stack, and that variable is allowed to resolve implicitly.
Bryn Rhodes (Apr 01 2022 at 15:25):
To me, the implication of that language is that attempting to access $this
outside the context of an expression-type argument is an error. Grahame, are you saying that in your engine, I could say: Patient.name.$this
and the reference to $this
would be allowed, and would return the name?
Grahame Grieve (Apr 04 2022 at 11:49):
no I don't have an issue with expressions and $this, I have an issue that you've described a particular behavior that I don't understand around scope.
Grahame Grieve (Apr 04 2022 at 11:50):
as far I understand, you're saying that scope works differently for these:
Patient.name.exists(use = 'nickname')
name.select(use.union(given))
but I don't know why you think that they're different. My engine can't differentiate them
Bryn Rhodes (Apr 04 2022 at 21:42):
They are different because union()
doesn't have arguments of type expression
. In other words, the arguments to union can both be evaluated prior to invoking the union. Whereas the expression
arguments to exists()
and select()
are evaluated iteratively within the functions.
Grahame Grieve (Apr 04 2022 at 21:54):
I don't follow. to me that's not relevant to my question. In the first case, "use" as parameter to exists() has a scope of name - the thing on which .exists is called. But the in the second case, the parameter to union() does not have the scope of 'use' but of name. Why is that different? I don't see that the .select() bit makes any difference to the evaluation there
Bryn Rhodes (Apr 04 2022 at 23:35):
use
isn't a parameter to the exists()
in this example, the expression use = 'nickname'
is the parameter. And the reason the select()
bit makes a difference is because it's a function with an expression
type parameter. Functions that don't have expression
type parameters don't introduce an iteration scope.
Grahame Grieve (Apr 05 2022 at 04:16):
I had missed that about exists() in my head and in my code
Last updated: Apr 12 2022 at 19:14 UTC