qt4cg / qtspecs

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

fn:identity: accept 2 arguments, ignore second #858

Closed ChristianGruen closed 9 months ago

ChristianGruen commented 9 months ago

Apart from id, Haskell has const, which accepts 2 arguments, but only returns the first. Thanks to the introduction of default arguments, it’s straightforward to extend fn:identity to be able to accept 2 arguments:

fn:identity(
  $input    as item()*,
  $ignored  as item()*  := ()
) as item()*
michaelhkay commented 9 months ago

Could you explain why this is useful?

ChristianGruen commented 9 months ago

If a function with two parameters is expected, and if you only want to ignore the second argument, you could currently supply a function like fn($a, $b) { $a }. For example, you can have a function that produces a result along with debugging information:

declare function local:process(
  $debug as function(item()*, xs:string)
) as item()* {
  let $result := (123 (: etc :))
  return $debug($result, 'number')
};
local:process(trace#2)

If you do not need debugging, you could pass identity#2 to this function.

I wouldn’t want to have a dedicated function for this in the spec (it would seem esoteric enough to most users), but it seems straightforward to support this with default parameters.

dnovatchev commented 9 months ago

Yes, and I would propose to have function fn:constant that can have any number of arguments after the first arguments, and that are simply ignored:

fn:constant as function($valueToReturn as item()*, $... as item()*

I forgot what syntax we were supposed to use for unbounded number of arguments, so, please use the right syntax and correct the above function signature.

Use cases:

We already have a math:pi function that returns the constant π. In the future we could add a number of other useful constants, such as the speed of light c, the golden ratio, Euler's constant e, Plank's constant, Avogadro's constant, the gravitational constant, Boltzmann;s constant, ... - just to name a few.

We could dedicate a separate function for each of these constants and end up with N such functions.

Or, we can have just a single fn:constant which allows us to express all of the mentioned constants, and the user can easily specify their own constants, too. Thus , with this proposal we effectively decrease the number of needed functions from O(N) to 1.

Any such constant may need to be returned when having unspecified number of arguments at hand, therefore it is best to use the new feature of variadic functions with unbounded positional arguments.

michaelhkay commented 9 months ago

I'm sorry if I'm being stupid, but I simply don't understand the use case.

If I want a function that returns the value of e I can write

let $e := function(){2.71828e0}

Why would I want to use a function with additional arguments that are ignored?

dnovatchev commented 9 months ago

I'm sorry if I'm being stupid, but I simply don't understand the use case.

If I want a function that returns the value of e I can write

let $e := function(){2.71828e0}

Why would I want to use a function with additional arguments that are ignored?

The idea is to have just one function that gives us any wanted (famous or not) constant value - instead of having N different functions for N different constants and still not having any functions at all for even the top 10% of all users' favorite constants.

As for the many arguments, imagine that a higher-order function expects as argument a function with 2 (or N) arguments, and we want to pass to it (in this specific case) a function that returns a constant, regardless of how many arguments might formally be expected, then this is exactly the proposed function.

And certainly, we will have even one more function with infinite arity, besides fn:concat 😄

michaelhkay commented 9 months ago

As for the many arguments, imagine that a higher-order function expects as argument a function with 2 (or N) arguments, and we want to pass to it (in this specific case) a function that returns a constant, regardless of how many arguments might formally be expected, then this is exacty the proposed function.

But we can do that already. We've defined the coercion rules so that, for example, you can pass the zero-arity function true() to a function that expects an arity-1 predicate function.

ChristianGruen commented 9 months ago

Why would I want to use a function with additional arguments that are ignored?

Just imagine that local:process#1 from my example above is a function in a module that you cannot rewrite for your own purposes. If you have no intent to evaluate the second argument, you would currently call the function with fn($value, $ignore) { $value }. fn:identity#2 would just be another formal solution to do this.

Here are some more examples for applications of the equivalent Haskell function, maybe they are more intuitive than mine: https://stackoverflow.com/questions/7402528/whats-the-point-of-const-in-the-haskell-prelude

I forgot what syntax we were supposed to use for unbounded number of arguments, so, please use the right syntax and correct the above function signature.

@dnovatchev Variadic arguments are not part of the standard yet (see #161 for the proposal). If we add them, I think we don’t need fn:constant; we could simply enhance the signature to:

fn:identity(
  $input       as item()*,
  $ignored...  as item()*  := ()
) as item()*
dnovatchev commented 9 months ago

As for the many arguments, imagine that a higher-order function expects as argument a function with 2 (or N) arguments, and we want to pass to it (in this specific case) a function that returns a constant, regardless of how many arguments might formally be expected, then this is exacty the proposed function.

But we can do that already. We've defined the coercion rules so that, for example, you can pass the zero-arity function true() to a function that expects an arity-1 predicate function.

A constant() function of one argument is represented on the 2-dimensional coordinate system as a straight-line, that is parallel to the ->X axis. A constant() function of 2 arguments is represented in a 3-dimensional coordinate system as a plane that is perpendicular to the ->Z axis and is parallel to the X * Y plane. Etc., with N dimensions we can have a (n-1) - dimensional "plane" that is perpendicular to the Nth axis and parallel to the (N-1) - dimensional "plane" that is defined upon the first N - 1 axes.

These objects (constant planes) can be useful in many scenarios, and it is great to have a single function that can produce them all.

Thus, one important use-case for a multi-argument constant() function is when handling problem with multi-dimensional objects. For example, the value of such a function can serve as a limit (boundary) for any allowed results of computation.

dnovatchev commented 9 months ago

Why would I want to use a function with additional arguments that are ignored?

Just imagine that local:process#1 from my example above is a function in a module that you cannot rewrite for your own purposes. If you have no intent to evaluate the second argument, you would currently call the function with fn($value, $ignore) { $value }. fn:identity#2 would just be another formal solution to do this.

Here are some more examples for applications of the equivalent Haskell function, maybe they are more intuitive than mine: https://stackoverflow.com/questions/7402528/whats-the-point-of-const-in-the-haskell-prelude

I forgot what syntax we were supposed to use for unbounded number of arguments, so, please use the right syntax and correct the above function signature.

@dnovatchev Variadic arguments are not part of the standard yet (see #161 for the proposal). If we add them, I think we don’t need fn:constant; we could simply enhance the signature to:

fn:identity(
  $input       as item()*,
  $ignored...  as item()*  := ()
) as item()*

@ChristianGruen I am glad we agree on the function!

The only slight difference is our preference for the name of the function. I prefer the name constant as it stems from simpler math notions, whereas identity is a special case of transformation which is an additional process that is absent in the definition of constant.

ChristianGruen commented 9 months ago

I'm sorry if I'm being stupid, but I simply don't understand the use case.

@michaelhkay Your speculation about being stupid should have been a sign: As you say, my use case has indeed become insubstantial due to the new function coercion rules (in my example, one can simply pass identity#1) – and I can’t think of another, so I’m closing this. Thanks for your time.

@dnovatchev If you think there’s still a use case for fn:constant, despite function coercion, feel free to create a new issue.

michaelhkay commented 9 months ago

Here are some more examples for applications of the equivalent Haskell function, maybe they are more intuitive

Sorry, but for some of us Haskell will never be intuitive. Please show me a simple use case: what is the input, what is the required output, how would I write this today, how could I write it better with this new function?

ChristianGruen commented 9 months ago

Sorry, but for some of us Haskell will never be intuitive. Please show me a simple use case: what is the input, what is the required output, how would I write this today, how could I write it better with this new function?

…me included. One note to add to my initial example is that it will indeed fail if the parameter type is omitted (i.e., if function coercion is not triggered):

declare function local:process($debug) {
  let $result := (123 (: etc :))
  return $debug($result, 'number')
};
local:process(identity#1)

But as we have function coercion, it’s at least possible to enforce it, also without identity#2.

Another example: When inserting a key into a map, $f decides how to combine the new value with a possibly existing old one. With fn($a, $b) { $a }, the old value is ignored. identity#1 cannot be supplied here unless the parameter $f is replaced with $f as function(item()*, item()*) as item()*). If we had identity#2, the exact type could be omitted:

let $insert-with := fn($f, $map, $k, $v) {
  let $old := $map($k)
  let $new := if($old) then $f($v, $old) else $v
  return map:merge(($map, map:entry($k, $new)))
}
let $map := map { 'foo': 1 }
let $add := $insert-with(fn($a, $b) { $a + $b }, ?, ?, ?)
let $ins := $insert-with(fn($a, $b) { $a }, ?, ?, ?)
return (
  $add($map, 'foo', 2)('foo'),
  $ins($map, 'foo', 42)('foo')
)
dnovatchev commented 9 months ago

@michaelhkay , @ChristianGruen :

The use case of being able to define many (an indefinite number of) constants dynamically with a single, dedicated function is still on.

If we don't want to have fn:constant then let us think of a system-provided map(xs:string, xs:anyAtomicType) with keys that are the names of the most popular and widely needed constants, and values - these constants' values.

I have been thinking for a long time that providing such "system variables" would be something very convenient and useful.

michaelhkay commented 9 months ago

The use case of being able to define

That's not a use case. A use case describes your problem, not your proposed solution.

Arithmeticus commented 8 months ago

@ChristianGruen When editing the specs, I found that all four examples at fn:identity left me asking, huh? Why do you need this? They don't illustrate a case where "a function must be supplied, but no processing is required." The minutes from Sept. 20, 2022 do little to illumine. To reiterate the theme of "use case" in this thread, could you supply an example that highlights the utility of the function? It would be nice to have that as the first or second example.

ChristianGruen commented 8 months ago

@ChristianGruen When editing the specs, I found that all four examples at fn:identity left me asking, huh? Why do you need this?

@Arithmeticus I assume that Michael contributed this part of the spec. I assume it’s tricky to find intuitive examples for this function for people who don't understand it anyway. In general, it’s a replacement for function($x) { $x }, and the name is derived from the mathematical identity function. As you may have seen, it is already used in some function signatures of the spec; maybe those will help you to get a feeling of its usefulness?

dnovatchev commented 8 months ago

@ChristianGruen When editing the specs, I found that all four examples at fn:identity left me asking, huh? Why do you need this? They don't illustrate a case where "a function must be supplied, but no processing is required." The minutes from Sept. 20, 2022 do little to illumine. To reiterate the theme of "use case" in this thread, could you supply an example that highlights the utility of the function? It would be nice to have that as the first or second example.

@Arithmeticus Sometimes we need convenient default values for functions and in some of these cases a function that is essentially a "NoOp" comes handy.

This could be so in the Visitor and Strategy pattern, if we want to "project" the visited structure as-is (not modified). Such a function can also come handy as one (maybe often forgotten) edge case in testing scenarios.