-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unified Call Syntax #148
Comments
It's not clear how this would fit into Zig. Zig does not have overloading (and almost certainly won't in the future), so a function named In another file, to import this, assuming the file was called "math.zig" you would do: const math = @import("math.zig");
fn f() {
const v = TwoNumbers {.x = 1, .y = 2};
math.sub(v);
} With your UFCS proposal you could perhaps do: const math = @import("math.zig");
const sub = math.sub;
fn f() {
const v = TwoNumbers {.x = 1, .y = 2};
v.sub();
} Or maybe using the use @import("math.zig");
// now `sub` is in the current namespace But then you wouldn't be able to import use @import("vector.zig");
// might cause error because `sub` is defined twice. Versus if you had this: struct Vector {
x: i32,
y: i32,
pub fn sub(self: Vector, other: Vector) -> Vector {
Vector {.x = self.x - other.x, .y = self.y - other.y}
}
}
struct TwoNumbers {
x: i32,
y: i32,
pub fn sub(tn: TwoNumbers) -> i32 {
tn.x - tn.y
}
} Here we have two implementations of Further, putting the function in the struct works conveniently for generic programming: struct Vector(T: type) {
x: T,
y: T,
pub fn sub(self: Vector(T), other: Vector(T)) -> Vector(T) {
Vector(T) {.x = self.x - other.x, .y = self.y - other.y}
}
} It's not clear how this would work without being in the scope of the struct. Side note about the |
I'm not convinced that extension methods are worthwhile. There are two main arguments I can find in favor of extension methods: chaining extension methods like I'm not sure how useful chaining extension methods will be in Zig for a number of reasons like: no garbage collector, and no OOP (at least yet). (Remember that Zig already has method syntax for methods declared inside the struct; this proposal allows method syntax for "third-party" methods.) I feel like where extension methods really shine in C# is when you extend Regarding IDE dot completion, I'm generally in favor of catering to IDE's (even if they don't exist yet), but I'm not sure this is really the right approach to that problem. Extension method syntax will enable you to discover a certain class of method, which is cool I guess. But the cost seems pretty high. I can't get past the idea that UFC would allow you to write So is this feature worth this cost? I'm not convinced. |
Responding to @thejoshwolfe here instead of #427 since it's more related to UFCS. For reference I'm providing an example where "chaining extension methods" helps readability:
With UFCS you could write this as:
I'm including the proposed
Note that when an argument is marked as
Since this is an error, it means there is only one way to call the function, so josh's example
|
How would memory management work for the Note that therer's no "default allocator" in Zig, so no I'm raising this point, because your code example that does map/filter operations on lists looks like it depends on a garbage collector, and Zig doesn't have that. I think it's important to make big design decisions motivated by more concrete than theoretical usecases, so I'm trying to take your code example pretty seriously here. |
Ranges have nothing to do with memory allocation, it's just a set of methods you implement that allows code to know how to work together on a set of data. There are different kinds of ranges but the most basic is the input range which requires the for(; !range.empty(); range.popFront())
{
auto front = range.front();
// use front
} You could use memory allocation in your range but typically all the data is computed lazily and kept on the stack. I think an example implementation will be better than trying to explain. I don't have a working zig compiler right now so I've included a C++ implementation that makes use of templates, you could also create one that uses virtual functions and pointer casting, but this one is more efficient since it can inline the entire thing and end up behaving as if you wrote for loops for everything. I've also included the equivalent in D, which showcases what "template type deduction" gives you and also what it would look like with UFCS. #include <stdio.h>
template<typename Range>
void printNumbers(Range range)
{
for(; !range.empty(); range.popFront())
{
printf("%d\n", range.front());
}
}
template<typename T>
struct arrayRange
{
T* next;
T* limit;
arrayRange(T* start, int count) : next(start), limit(start + count)
{
}
bool empty()
{
return next >= limit;
}
T front()
{
return *next;
}
void popFront()
{
next++;
}
};
template<typename Range>
struct plus
{
Range inputRange;
int valueToAdd;
plus(Range inputRange, int valueToAdd) : inputRange(inputRange), valueToAdd(valueToAdd)
{
}
bool empty()
{
return inputRange.empty();
}
int front()
{
return inputRange.front() + valueToAdd;
}
void popFront()
{
inputRange.popFront();
}
};
template<typename Range>
struct filterIfGreaterThan
{
Range inputRange;
int limit;
filterIfGreaterThan(Range inputRange, int limit) : inputRange(inputRange), limit(limit)
{
}
bool empty()
{
for(; !inputRange.empty(); inputRange.popFront())
{
if(inputRange.front() <= limit)
{
return false;
}
}
return true;
}
int front()
{
return inputRange.front();
}
void popFront()
{
inputRange.popFront();
}
};
int main(int argc, char* argv[])
{
int integerArray[] = {2, 1, 5, 8, 3};
printf("Print Numbers:\n");
printNumbers<arrayRange<int>>(
arrayRange<int>(integerArray, 5));
printf("Add 10 to each number and print:\n");
printNumbers<plus<arrayRange<int>>>(
plus<arrayRange<int>>(
arrayRange<int>(integerArray, 5), 10));
printf("Print original numbers to show they haven't changed:\n");
printNumbers<arrayRange<int>>(
arrayRange<int>(integerArray, 5));
printf("Add 10 to each number then filter any that are greater than 13, then print them\n");
printNumbers<filterIfGreaterThan<plus<arrayRange<int>>>>(
filterIfGreaterThan<plus<arrayRange<int>>>(
plus<arrayRange<int>>(
arrayRange<int>(integerArray, 5), 10), 13));
return 0;
} And here's the D version import std.stdio;
void printNumbers(Range)(Range range)
{
// Note: D's foreach loop understand ranges
foreach(value; range)
{
writeln(value);
}
}
// D already has methods that treat arrays like ranges, but I'm including this to make
// it close to the C++ version.
auto arrayRange(T)(T[] array)
{
struct _
{
T* next;
T* limit;
this(T* start, T* limit)
{
this.next = start;
this.limit = limit;
}
bool empty()
{
return next >= limit;
}
T front()
{
return *next;
}
void popFront()
{
next++;
}
}
return _(array.ptr, array.ptr + array.length);
};
auto plus(Range)(Range inputRange, int valueToAdd)
{
struct _
{
Range inputRange;
int valueToAdd;
this(Range inputRange, int valueToAdd)
{
this.inputRange = inputRange;
this.valueToAdd = valueToAdd;
}
bool empty()
{
return inputRange.empty();
}
int front()
{
return inputRange.front() + valueToAdd;
}
void popFront()
{
inputRange.popFront();
}
}
return _(inputRange, valueToAdd);
};
auto filterIfGreaterThan(Range)(Range inputRange, int limit)
{
struct _
{
Range inputRange;
int limit;
this(Range inputRange, int limit)
{
this.inputRange = inputRange;
this.limit = limit;
}
bool empty()
{
for(; !inputRange.empty(); inputRange.popFront())
{
if(inputRange.front() <= limit)
{
return false;
}
}
return true;
}
int front()
{
return inputRange.front();
}
void popFront()
{
inputRange.popFront();
}
}
return _(inputRange, limit);
};
int main(string[] args)
{
auto integerArray = [2, 1, 5, 8, 3];
//
// No UFCS, but with template parameter deduction
//
writeln("Print Numbers:");
printNumbers(arrayRange(integerArray));
writeln("Add 10 to each number and print:");
printNumbers(
plus(
arrayRange(integerArray), 10));
writeln("Print original numbers to show they haven't changed:");
printNumbers(
arrayRange(integerArray));
writeln("Add 10 to each number then filter any that are greater than 13, then print them");
printNumbers(
filterIfGreaterThan(
plus(
arrayRange(integerArray), 10), 13));
//
// Now use template parameter deduction AND UFCS
//
writeln("Print Numbers:");
integerArray.arrayRange.printNumbers;
writeln("Add 10 to each number and print:");
integerArray.arrayRange.plus(10).printNumbers;
writeln("Print original numbers to show they haven't changed:");
integerArray.arrayRange.printNumbers;
writeln("Add 10 to each number then filter any that are greater than 13, then print them");
integerArray.arrayRange.plus(10).filterIfGreaterThan(13).printNumbers;
return 0;
} |
Great example! I'll convert all that to zig to see how it looks with status quo. Standby... |
This was in the context of the infix function proposal, which is why it is in terms of Vecs - they are not the best structures to demonstrate the concept. I will expand on this later, meanwhile, here are my thoughts. Keep in mind some of this is already possible with the generic syntax. I've been thinking of how I'd pass arbitrarily typed self parameters to a function, They keyword
This led to a sort of trait/impl construct idea, that uses the compile-time duck typing concept to provide a generic way to access an abitrary struct.
And an idea about explicit interfaces, where you basically require that
|
To follow up my earlier comment, I determined that the Can we come up with an actual real example where UFCS would be good? I see lots of discussion about vectors, but that's an example of where you can use status-quo method call syntax, so UFCS is not relevant there. |
I was actually under the impression status quo was UFCS. I was using it as an example of how status quo was good. Perhaps I should annotate my comments to make that clear. If someone could describe how status quo differs from UFCS, that might make things clearer |
I didn't read the entire pdf in the OP, and the poster seems to have lost interest, so I'll state these definitions for the sake of discussion:
|
You can see it here. I've seen it in some new languages, and I think it fits perfectly into Zig. Also, it allows adding functions that operate on any type after declaring all their members: just put it as the first argument.
The text was updated successfully, but these errors were encountered: