qt4cg / qtspecs

QT4 specifications
https://qt4cg.org/
Other
27 stars 15 forks source link

Defining names for parameters on typed function tests #1136

Open rhdunn opened 3 months ago

rhdunn commented 3 months ago

When defining the type of a higher-order function parameter, you cannot currently specify the names of the parameters of that higher-order function.

Allowing this can be useful for variou reasons:

  1. documenting the parameter names in the function signature -- this makes it clear looking at the function in an IDE, etc. what the parameters are;
  2. making the specs clearer by referring to the parameters by name;
  3. allowing a processor to provide better error messages by referring to the parameters names, e.g. when there is a type conversion error;
  4. allowing a user to reference the parameter by name if we enable this to resolve named keyword argments (which is currently being discussed in #1114).

Thus, you could declare e.g. index-where like this:

declare function fn:index-where(
  $input as item()*,
  $predicate as function(
    $item as item(),
    $position as xs:integer
  ) as xs:boolean
) as xs:integer* {
  (: ... :)
};
ChristianGruen commented 3 months ago

Thanks for the separate proposal.

I’m not 100% sure I understand how 4. could look like in practice. Could you add a little example for it?

rhdunn commented 3 months ago
declare function fn:index-where(
  $input as item()*,
  $predicate as function(
    $item as item(),
    $position as xs:integer
  ) as xs:boolean
) as xs:integer* {
  for $it at $pos in $input
  where $predicate(item := $it, position := $pos)
  return $pos
};

Here, the processor resolves $predicate in the function body to the parameter declaration. That has a static type which defines the parameter names of the function bound to that parameter. That static type is used to resolve the named keywords.

ChristianGruen commented 3 months ago

I wonder how that would go hand in hand with the existing semantics:

michaelhkay commented 3 months ago

This proposal introduces some static type inferencing into the language, something which we have previously avoided (except for the limited purpose of streamability analysis in XSLT). If we keep it very tightly constrained -- associating the explicitly declared type of a variable or parameter with references to that variable or parameter -- then this might be manageable. But I worry that it could easily feature-creep into something more complex.

Consider

declare function fn:index-where(
  $input as item()*,
  $predicate as (function(
    $item as item(),
    $position as xs:integer
  ) as xs:boolean)?
) as xs:integer* {
  let $actual-predicate := $predicate otherwise true#0
  for $it at $pos in $input
  where $actual-predicate(item := $it, position := $pos)
  return $pos
};

This isn't going to work because $actual-predicate doesn't have a type declaration providing the parameter names.

He could try:

let $actual-predicate as function(
    $item as item(),
    $position as xs:integer
  ) as xs:boolean := $predicate otherwise true#0

but is this actually usable?

To make it more usable, we have to start introducing more complex type inferencing rules.

Even without this, we're making life tougher for implementations -- for example you can't now inline a variable reference without retaining information from its type declaration. To what benefit?

rhdunn commented 3 months ago

It would make sense for fn:count#1, etc. to keep the parameter names as part of the typed function test. -- This would allow the use of keyword arguments in that case as I discussed in the other thread.

I think it makes sense for function coercion rules, subsumption rules, etc. to allow different names -- otherwise there would be incompatibilities with 3.1 -- and use the names in the nearest scope.

That is, given:

let $f := fn:count#1 return $f(input := (1, 2))

the fn:count#1 expression resolves to the static type function($input as item()*) as xs:integer as you note. The variable $f inherits that inferred static type. And the dynamic call to $f(...) makes use of that static type.

And given:

let $f as function($values as item()*) as xs:integer := fn:count#1
return $f(values := (1, 2))

the variable $f provides a specific static type. And the dynamic call to $f(...) makes use of that specific static type.

rhdunn commented 3 months ago

@michaelhkay I'm happy to keep this constrained, such that your $actual-predicate example would not be able to use named parameters without the dynamic proposal from @ChristianGruen. -- This goes along the lines of your approach of introducing small, constrained features rather than trying to do everything at once.

There are two parts to this proposal:

  1. supporting naming the parameters on a function test as a purely documentational feature -- i.e. where it does not affect the semantics of the function test;
  2. extending the use of named keywords to the case where the static type of a variable/parameter is a function test with parameter names, or via a named function reference.

I don't expect this specific proposal to go any/much farther than that. Doing so would require the dynamic proposal that @ChristianGruen is putting forward.

ChristianGruen commented 3 months ago

@ChristianGruen. -- This goes along the lines of your approach of introducing small, constrained features rather than trying to do everything at once.

An even smaller step (hopefully not too small) would be to introduce optional parameter names, but to utilize them only for documentation and error messages. This would address reason 1-3 of this proposal.

Closely related: #981.

rhdunn commented 3 months ago

I'm happy to treat 1-3 and 4 as two separate proposals.

michaelhkay commented 3 months ago

I do think we should avoid the "thin end of the wedge" effect of static type inferencing. I don't think it would be acceptable to allow parameter names on $param(....) but not on $param[3](....) or $param()(....) or $record?callback(....).

michaelhkay commented 3 months ago

I agree that allowing names purely for documentation purposes seems a sensible idea.

It's hard to separate this, however, from the fact that according to the data model, a function item has parameters with names. Currently there is no way of determining what the names are, and I don't think there is anything in the language that would be any different if they didn't have names, but we should make a decision whether to retain the names and make use of them, or to drop them.

ChristianGruen commented 3 months ago

I agree that allowing names purely for documentation purposes seems a sensible idea.

It's hard to separate this, however, from the fact that > we should make a decision whether to retain the names and make use of them, or to drop them.

Now that we introduce keyword parameters with version 4, I think it would be just consistent to keep the names as well.