Closed ChristianGruen closed 9 months ago
Could you explain why this is useful?
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.
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.
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?
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
😄
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.
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()*
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.
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 withfn($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
.
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.
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?
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')
)
@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.
The use case of being able to define
That's not a use case. A use case describes your problem, not your proposed solution.
@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 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?
@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.
Apart from
id
, Haskell hasconst
, which accepts 2 arguments, but only returns the first. Thanks to the introduction of default arguments, it’s straightforward to extendfn:identity
to be able to accept 2 arguments: