1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
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
Status: Implemented.
Implementation: [PR 4905](https://github.com/shader-slang/slang/pull/4905) [PR 4926](https://github.com/shader-slang/slang/pull/4926)
Reviewed by: Kai Zhang, Jay Kwak
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 should support overloading of `operator()`, and use the function call syntax to call the `operator()` member, similar to C++:
```
struct Functor
{
int operator()(float p) {}
}
void test()
{
Functor f;
f(1.0f);
}
```
We propose `IFunc`, `IMutatingFunc`, `IDifferentiableFunc` and `IDiffernetiableMutatingFunc` that is defined as follows:
```
// Function objects that does not have a mutating state.
interface IMutatingFunc<TR, each TP>
{
[mutating]
TR operator()(expand each TP p);
}
// Function objects with a mutating state.
interface IFunc<TR, each TP> : IMutatingFunc<TR, expand each TP>
{
TR operator()(expand each TP p);
}
// Differentiable functions
interface IDifferentiableMutatingFunc<TR : IDifferentiable, each TP : IDifferentiable> : IMutatingFunc<TR, expand each TP>
{
[Differentiable]
[mutating]
TR operator()(expand each TP p);
}
interface IDifferentiableFunc<TR : IDifferentiable, each TP : IDifferentiable> : IFunc<TR, expand each TP>, IDifferentiableMutatingFunc<TR, expand each TP>
{
[Differentiable]
TR operator()(expand each TP p);
}
```
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<void, int> f)
{
for (int i = 0; i < data.getCount(); i++)
f(data[i]);
}
struct CounterFunc : IMutatingFunc<void, int>
{
int count;
[mutating]
void operator()(int data)
{
if (data % 2 == 0)
count++;
}
};
void test()
{
int data[100] = ...;
CounterFunc f;
f.count = 0;
forEach(data, f);
printf("%d", f.count);
}
```
# Coercion of ordinary functions
Eventually, we should allow ordinary functions to be automatically coerceable to `IFunc` interfaces. But this is scoped out
for the initial `IFunc` work, because we believe the implementation can be simpler if we support lambda function first, then
implement ordinary function coercion as a special case of lambda expressions.
|