From 9bf5dc93d5d46f64b0da298e03ba3233539af6ef Mon Sep 17 00:00:00 2001 From: Yong He Date: Fri, 16 Aug 2024 10:23:14 -0700 Subject: [PATCH] Design proposal: IFunc interface. (#4851) * Design proposal: IFunc interface. * fix code error. --- docs/proposals/009-ifunc.md | 133 ++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/proposals/009-ifunc.md diff --git a/docs/proposals/009-ifunc.md b/docs/proposals/009-ifunc.md new file mode 100644 index 0000000000..646f5f55bd --- /dev/null +++ b/docs/proposals/009-ifunc.md @@ -0,0 +1,133 @@ +SP #009 - IFunc interface +============== + +Now that we have variadic generics in the language following [SP #007], we should now be able to define a builtin `IFunc` interface that represent +things that can be called with the `()` operator. This will allow users to write generic functions that takes a callback object and adopt more +functional programming idioms. + +Status +------ + +Author: Yong He + +Implementation: Planned. + +Reviewed by: N/A + +Background +---------- + +Callback is an idiom that frequently show up in complex codebases. Currently, Slang users can implement this idiom with +interfaces: + +``` +interface ICondition +{ + bool test(int x); +} + +int countElement(int data[100], ICondition condition) +{ + int count = 0; + for (int i = 0; i < data.getCount(); i++) + if (condition.test(data[i])) + count++; + return count; +} + +int myCondition(int x) { return x%2 == 0; } // select all even numbers. + +struct MyConditionWrapper : ICondition +{ + bool test(int x) { return myCondition(x); } +}; + +void test() +{ + int data[100] = ...; + int count = countElement(data, MyConditionWrapper()); +} +``` + +As can be seen, this is a lot of boilerplate. With a builtin `IFunc` interface, we can +allow the compiler to automatically make ordinary functions conform to the interface, +eliminating the need for defining interfaces and wrapper types. + + +Proposed Approach +----------------- + +We propose `IFunc` and `IMutatingFunc` that is defined as follows: + +``` +// Function objects that does not have a mutating state. +interface IFunc +{ + TResult __call(expand each TParams params); +} + +// Function objects with a mutating state. +interface IMutatingFunc +{ + [mutating] + TResult __call(expand each TParams params); +} +``` + +Ordinary functions are treated as conforming to `IFunc` and `IMutatingFunc` automatically, +so the following code is valid: + +``` +int countElement(int data[100], IFunc condition) +{ + int count = 0; + for (int i = 0; i < data.getCount(); i++) + if (condition(data[i])) + count++; + return count; +} + +int myCondition(int x) { return x%2 == 0; } // select all even numbers. + +void test() +{ + int data[100] = ...; + int count = countElement(data, myCondition); +} +``` + +An ordinary function or static function with type `(T0, T1, ... Tn)->TR` is coerceable to `IFunc` if +`TI0, TI1, ... TIn` are coerceable to `T0, T1, ... Tn` and `TR` is coerceable to `TIR`. +To achieve this, the compiler will synthesize a wrapper struct type conforms to the `IFunc` interface, and calls the original function +in its `__call` method. + +The `IMutatingFunc` interface is for defining functors that has a mutable state. The following example demonstrates its use: + +``` +void forEach(int data[100], inout IMutatingFunc f) +{ + for (int i = 0; i < data.getCount(); i++) + f(data[i]); +} + +struct CounterFunc : IMutatingFunc +{ + int count; + + [mutating] + void __call(int data) + { + if (data % 2 == 0) + count++; + } +}; + +void test() +{ + int data[100] = ...; + CounterFunc f; + f.count = 0; + forEach(data, f); + printf("%d", f.count); +} +```