From c6992bbb201cb9846fbdc9fc975cc79f1646722a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Tue, 23 Jan 2024 18:05:18 +0100 Subject: [PATCH 1/4] RFC: 1026 function overloading --- text/1026-function-overload.rst | 184 ++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 text/1026-function-overload.rst diff --git a/text/1026-function-overload.rst b/text/1026-function-overload.rst new file mode 100644 index 0000000..9eb7743 --- /dev/null +++ b/text/1026-function-overload.rst @@ -0,0 +1,184 @@ +.. code:: + + Status: Draft + Type: Feature + Created: 2023-01-25 + Authors: Aljaž Mur Eržen + +####################################### + RFC 1026: Remove function overloading +####################################### + +********** + Abstract +********** + +Current behavior of function and operator overloading requires a +complicated call resolution algorithm. This RFC proposes a new syntax +for defining multiple function and operator implementations and removing +implicit function overloading. + +************ + Motivation +************ + +Currently, EdgeQL allows overloading function definitions. For example, +there are 11 functions defined under the name ``std::contains``: + +.. code:: + + CREATE FUNCTION std::contains( + haystack: std::str, needle: std::str + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: std::bytes, needle: std::bytes + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: array, needle: anytype + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: json, needle: json + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: range, needle: range + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: range, needle: anypoint + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: multirange, needle: multirange + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: multirange, needle: range + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: multirange, needle: anypoint + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: range, needle: cal::local_date + ) -> std::bool using (...); + CREATE FUNCTION std::contains( + haystack: multirange, needle: cal::local_date + ) -> std::bool using (...); + +This behavior is desired, as it is useful to implement a function for +multiple types with slight variations. However, it complicates resolving +which definition should be used for a given call. + +As functions can be defined at any location, these definitions do not +have any order. When a function is called, all definitions that match in +name are compared with the function call signature. First, all +definitions whose parameters are not super types of call's arguments are +discarded. Then, we compute a "call distance" between types of arguments +and parameters and pick the function definition with the lowest +distance. + +It might happen that multiple definitions have an equal call distance, +in which case an error saying "please disambiguate" is raised. + +Similar behavior is used for operators. + +This behavior is mostly used in combination with pseudo-types: + + create function foo(a: anyscalar) -> str using ("matched anyscalar"); + create function foo(a: str) -> str using ("matched str"); + + select foo("hello"); # {"matched str"} select foo(12); # {"matched + anyscalar"} + +Note that in user-defined functions, it is not allowed to use +pseudo-types or overload functions that take object types. Overloading +of functions that take scalar types does not work correctly. This means +that this overloading behavior cannot used outside of EdgeDB-defined +functions. + + .. + create scalar type A extending str; + create scalar type B extending A; + create function foo(a: str) -> str using ("matched str"); + create function foo(a: A) -> str using ("matched A"); + select foo("hello"); # error: please disambiguate between the two function + select foo("hello"); # matched A + select foo("hello"); # internal server error: function edgedbpub."..." is not unique + +********** + Proposal +********** + +#. Add the following syntax: + + .. code:: + + CREATE FUNCTION std::contains + (haystack: std::str, needle: std::str) -> std::bool using (...) OR + (haystack: std::bytes, needle: std::bytes) -> std::bool using (...) OR + (haystack: array, needle: anytype) -> std::bool using (...); + + Here, all implementations must be defined in a single declaration. + Since they have an inherent order, this order can be used during call resolution. + To resolve a call, we first find the single definition matching in name. + Then we traverse the list of implementations from first to last and compare their signature to the call. + First signature that matches is resolved to, regardless of potential match in the following implementations. + + Individual implementations in the list would be allowed to define any number of parameters with arbitrary types. + +#. Add similar syntax for operators. + +#. Forbid implicit overloading of functions and operators. + +*************** + Justification +*************** + +This change would vastly simplify call resolution code. + +It would also make it much more predictable. This is currently +problematic, as there is 36 implementations of ``std::`=```, including +implementations for ``anytype``, ``anyscalar``, ``anynumber``, each of +the numeric types and their combinations. + +The simplified behavior would be easier to implement dynamically (i.e. +for dynamic dispatch), as it has to match a linear list, as opposed to +an inheritance tree. Currently, dynamic dispatch is supported only for +pseudo-types, using appropriate PostgreSQL pseudo-types. + +****************** + Future expansion +****************** + +Function overloading is needed when an extension defines or extends a +type. Specifically, there is a need to implement ``std::`=```. + +As extensions would not be able to alter the original function +definition, they do require overloading. That could be accomplished via +explicit an OVERLOADED keyword. Implementations created this way would +be inject in front of the list, before all other implementations. To +disambiguate between two OVERLOADED implementations, explicit references +between the implementations could be used. + +************************ + Backward compatibility +************************ + +Because function overloading is currently useless in user-code, we can +consider it not part of current EdgeQL spec. This means that this change +is fully backwards compatible, if the proposed change can resolve all +calls to EdgeDB-defined functions and operators as they are resolved +currently. + +That *should* be possible, although I am not certain. + +************************* + Backwards compatibility +************************* + +The proposal is fully backwards compatible. + +********************* + Implementation plan +********************* + +The proposal can be implemented in stages. E.g. EdgeDB version 3.0 will +have the basic ``*`` and ``**`` operators supported in shapes, while +EdgeDB 4.0 or later can have the proposed type language extensions +implemented. From 49da8ced28ec3828aeae43443d712285affe3ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Tue, 23 Jan 2024 18:06:52 +0100 Subject: [PATCH 2/4] fix --- text/1026-function-overload.rst | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/text/1026-function-overload.rst b/text/1026-function-overload.rst index 9eb7743..751500d 100644 --- a/text/1026-function-overload.rst +++ b/text/1026-function-overload.rst @@ -92,15 +92,6 @@ of functions that take scalar types does not work correctly. This means that this overloading behavior cannot used outside of EdgeDB-defined functions. - .. - create scalar type A extending str; - create scalar type B extending A; - create function foo(a: str) -> str using ("matched str"); - create function foo(a: A) -> str using ("matched A"); - select foo("hello"); # error: please disambiguate between the two function - select foo("hello"); # matched A - select foo("hello"); # internal server error: function edgedbpub."..." is not unique - ********** Proposal ********** @@ -109,21 +100,23 @@ functions. .. code:: - CREATE FUNCTION std::contains - (haystack: std::str, needle: std::str) -> std::bool using (...) OR - (haystack: std::bytes, needle: std::bytes) -> std::bool using (...) OR - (haystack: array, needle: anytype) -> std::bool using (...); + CREATE FUNCTION std::contains + (haystack: std::str, needle: std::str) -> std::bool using (...) OR + (haystack: std::bytes, needle: std::bytes) -> std::bool using (...) OR + (haystack: array, needle: anytype) -> std::bool using (...); - Here, all implementations must be defined in a single declaration. - Since they have an inherent order, this order can be used during call resolution. - To resolve a call, we first find the single definition matching in name. - Then we traverse the list of implementations from first to last and compare their signature to the call. - First signature that matches is resolved to, regardless of potential match in the following implementations. + Here, all implementations must be defined in a single declaration. + Since they have an inherent order, this order can be used during call + resolution. To resolve a call, we first find the single definition + matching in name. Then we traverse the list of implementations from + first to last and compare their signature to the call. First + signature that matches is resolved to, regardless of potential match + in the following implementations. - Individual implementations in the list would be allowed to define any number of parameters with arbitrary types. + Individual implementations in the list would be allowed to define any + number of parameters with arbitrary types. #. Add similar syntax for operators. - #. Forbid implicit overloading of functions and operators. *************** From 687d0d454413cb84cb3b7474a6e80fe337d4ea7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Tue, 23 Jan 2024 18:10:18 +0100 Subject: [PATCH 3/4] fix --- text/1026-function-overload.rst | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/text/1026-function-overload.rst b/text/1026-function-overload.rst index 751500d..a4bad16 100644 --- a/text/1026-function-overload.rst +++ b/text/1026-function-overload.rst @@ -160,18 +160,3 @@ calls to EdgeDB-defined functions and operators as they are resolved currently. That *should* be possible, although I am not certain. - -************************* - Backwards compatibility -************************* - -The proposal is fully backwards compatible. - -********************* - Implementation plan -********************* - -The proposal can be implemented in stages. E.g. EdgeDB version 3.0 will -have the basic ``*`` and ``**`` operators supported in shapes, while -EdgeDB 4.0 or later can have the proposed type language extensions -implemented. From 43e240783a63b8a9519263259fccef818a777a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alja=C5=BE=20Mur=20Er=C5=BEen?= Date: Tue, 23 Jan 2024 18:11:09 +0100 Subject: [PATCH 4/4] fix --- text/1026-function-overload.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/1026-function-overload.rst b/text/1026-function-overload.rst index a4bad16..a822bc3 100644 --- a/text/1026-function-overload.rst +++ b/text/1026-function-overload.rst @@ -2,7 +2,7 @@ Status: Draft Type: Feature - Created: 2023-01-25 + Created: 2024-01-23 Authors: Aljaž Mur Eržen #######################################