www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Passing $ as a function argument

reply James Japherson <JJ goolooking.com> writes:
Would be nice to be able to pass $ as a function argument to be 
used in automatic path length traversing.


void foo(int loc)
{
    return bar[loc];
}

then foo($) would essentilly become

foo(&)

   becomes ==>

    return bar[$];


instead of having do to thinks like foo(bar.length).

The usefulness comes from the case when bar is local:

void foo(int loc)
{
    auto bar = double[RandomPInt+1];

    return bar[loc];
}


then foo($) always returns a value and the outside world does not 
need to know about foo. Since $ is a compile thing expression and 
not used anywhere else this can always be done(it is a symbolic 
substitution and has a direct translation in to standard D code 
except $ cannot be used as arguments like this the current D 
language grammar).
Oct 10 2018
next sibling parent bauss <jj_1337 live.dk> writes:
On Wednesday, 10 October 2018 at 08:46:42 UTC, James Japherson 
wrote:
 Would be nice to be able to pass $ as a function argument to be 
 used in automatic path length traversing.


 void foo(int loc)
 {
    return bar[loc];
 }

 then foo($) would essentilly become

 foo(&)

   becomes ==>

    return bar[$];


 instead of having do to thinks like foo(bar.length).

 The usefulness comes from the case when bar is local:

 void foo(int loc)
 {
    auto bar = double[RandomPInt+1];

    return bar[loc];
 }


 then foo($) always returns a value and the outside world does 
 not need to know about foo. Since $ is a compile thing 
 expression and not used anywhere else this can always be 
 done(it is a symbolic substitution and has a direct translation 
 in to standard D code except $ cannot be used as arguments like 
 this the current D language grammar).
I don't really get your example and what benefits this would have? And also what about the current behavior of the $ operator?
Oct 10 2018
prev sibling next sibling parent bachmeier <no spam.net> writes:
On Wednesday, 10 October 2018 at 08:46:42 UTC, James Japherson 
wrote:

 The usefulness comes from the case when bar is local:

 void foo(int loc)
 {
    auto bar = double[RandomPInt+1];

    return bar[loc];
 }


 then foo($) always returns a value and the outside world does 
 not need to know about foo. Since $ is a compile thing
No language change is necessary right now if you write one more character: foo!"$"
Oct 10 2018
prev sibling next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Wednesday, 10 October 2018 at 08:46:42 UTC, James Japherson 
wrote:
 Would be nice to be able to pass $ as a function argument to be 
 used in automatic path length traversing.


 void foo(int loc)
 {
    return bar[loc];
 }

 then foo($) would essentilly become

 foo(&)

   becomes ==>

    return bar[$];


 instead of having do to thinks like foo(bar.length).

 The usefulness comes from the case when bar is local:

 void foo(int loc)
 {
    auto bar = double[RandomPInt+1];

    return bar[loc];
 }


 then foo($) always returns a value and the outside world does 
 not need to know about foo. Since $ is a compile thing 
 expression and not used anywhere else this can always be 
 done(it is a symbolic substitution and has a direct translation 
 in to standard D code except $ cannot be used as arguments like 
 this the current D language grammar).
$ requires context (the array) for its value to be known - it's not a compile-time expression any more than rand() + currentWeather(getGpsCoordinates()) is. If $ were a valid identifier, you could do something like this: struct Sentinel {} Sentinel $; void foo(T)(T loc) { auto bar = double[RandomPInt+1]; static if (is(T == Sentinel)) { return bar[$]; } else { return bar[loc]; } } unittest { foo($); } Note that this would turn foo into a template, so that foo($) creates a separate function from foo(3). Since $ isn't a valid identifier, this is currently impossible, but bachmeier's suggestion of foo!"$" works: void foo(string s = "")(int loc = 0) if (s == "" || s == "$") { auto bar = double[RandomPInt+1]; static if (s == "$") { return bar[$]; } else { return bar[loc]; } } -- Simen
Oct 10 2018
next sibling parent reply James Japherson <JJ goolooking.com> writes:
On Wednesday, 10 October 2018 at 13:32:15 UTC, Simen Kjærås wrote:
 On Wednesday, 10 October 2018 at 08:46:42 UTC, James Japherson 
 wrote:
 Would be nice to be able to pass $ as a function argument to 
 be used in automatic path length traversing.


 void foo(int loc)
 {
    return bar[loc];
 }

 then foo($) would essentilly become

 foo(&)

   becomes ==>

    return bar[$];


 instead of having do to thinks like foo(bar.length).

 The usefulness comes from the case when bar is local:

 void foo(int loc)
 {
    auto bar = double[RandomPInt+1];

    return bar[loc];
 }


 then foo($) always returns a value and the outside world does 
 not need to know about foo. Since $ is a compile thing 
 expression and not used anywhere else this can always be 
 done(it is a symbolic substitution and has a direct 
 translation in to standard D code except $ cannot be used as 
 arguments like this the current D language grammar).
$ requires context (the array) for its value to be known - it's not a compile-time expression any more than rand() + currentWeather(getGpsCoordinates()) is. If $ were a valid identifier, you could do something like this: struct Sentinel {} Sentinel $; void foo(T)(T loc) { auto bar = double[RandomPInt+1]; static if (is(T == Sentinel)) { return bar[$]; } else { return bar[loc]; } } unittest { foo($); } Note that this would turn foo into a template, so that foo($) creates a separate function from foo(3). Since $ isn't a valid identifier, this is currently impossible, but bachmeier's suggestion of foo!"$" works: void foo(string s = "")(int loc = 0) if (s == "" || s == "$") { auto bar = double[RandomPInt+1]; static if (s == "$") { return bar[$]; } else { return bar[loc]; } } -- Simen
The whole point is not to use $ as an identifier but to specify to the compiler of that it can rewrite it. You seem to think that what the compiler does is absolute authority. This is why I said "It would be nice".... meaning that if we had some a feature(which is entirely doable, not some mathematical impossibility), it would allow one to express the limit of an index in a concise way. Your templated version is not concise. All you really proved is that the compiler can be given a rewrite rule and handle this nicely. $ is not an used for identifiers, it is used to specify that the maximum length of the array it is used in is to be used. It is short hand for doing hacks such as specifying -1 for maximum length, etc. You seem to to have not understood the problem. I mean, don't you understand that the entire point of $ in the first place is just syntactic sugar? It also has no context in and of itself. The compiler knows what to do with it... The same can be done with function arguments. You just haven't thought about the problem enough.
Oct 10 2018
next sibling parent reply Dennis <dkorpel gmail.com> writes:
Can you give a real-world, non-foo/bar example where you want to 
use it? I have trouble understanding what you want to accomplish.

On Wednesday, 10 October 2018 at 23:04:46 UTC, James Japherson 
wrote:
 It also has no context in and of itself. The compiler knows 
 what to do with it... The same can be done with function 
 arguments. You just haven't thought about the problem enough.
 The usefulness comes from the case when bar is local:
 
 void foo(int loc)
 {
   auto bar = double[RandomPInt+1];

   return bar[loc];
 }
That also brings some difficulties. What kind of code do you expect the compiler to generate when the declaration is unknown? ``` int getFromArray(int loc); // implemented in another file, compiled separately void main() { getFromArray($); // what integer is passed? } ``` Finally I want to note that accessing element $ is a range violation, $-1 is the index of the last element in an array. $ can be used as an endpoint for intervals (where the endpoint is excluded from the range): ``` auto popped = arr[1..$]; //pop the front element auto elem = popped[$-1]; //okay, last element auto err = popped[$]; //range violation ```
Oct 10 2018
parent reply James Japherson <JJ goolooking.com> writes:
On Wednesday, 10 October 2018 at 23:26:38 UTC, Dennis wrote:
 Can you give a real-world, non-foo/bar example where you want 
 to use it? I have trouble understanding what you want to 
 accomplish.
I don't understand why you need to be convinced that this is relevant. Do you not realize that there are cases where one wants to select the last element of a list without having to explicitly know it? After all, the whole point of $ is exactly to specify this "last element". arr[$-1] is EXACTLY shorthand for arr[arr.length - 1] So, you are already drinking the cool aid. All I'm proposing is to to allow one to escape that syntax to function calls. foo(int index) { return arr[index]; } and D can support foo($-1); which simply gets translated in to arr[arr.length - 1] All D does is look at the argument, when parsing, see's the $ and say's "Ah ah, they they want to access the last element of the array where index is used. It then simply sets index to arr.length - 1. It is NO different than what it already does except, because we can use it in functions, explicitly(it only works at compile time), it is just more sugar... again, $ is pure sugar. There are many applications, I shouldn't have to justify why it would be useful... it is, because it is as useful as it is. If you claim it is not useful then you also must make that claim about the current semantics of $. But since you seem to need prodding, I will prod, // returns elements from graph GetNormalizedGraph(int index) { auto Graph[1000]; for(i = 0; i < Graph.length; i++) Graph[i] = i^2/100; return normalized(Graph[index]); } then GetNormalizedGraph($-1) always returns the last element. The point is, the caller doesn't have to know the size of Graph inside... it can change without breaking the program. It beats having to use hacks like using negative -1 to represent the length, etc. The only problem is that we might use index for multiple arrays all having different lengths, which would be a violation since it would make the index multi valued. This can be solved by using lengths for the arrays but setting the index to max value or an compiler error. // returns elements from graph GetNormalizedGraph(int index = -1) { auto Graph[1000]; for(i = 0; i < Graph.length; i++) Graph[i] = i^2/100; return normalized(Graph[index == -1 ? Graph.length : index]); } The problem is when we do GetNormalizedGraph() it is tells us nothing GetNormalizedGraph(-1) is confusing, are we indexing at -1? GetNormalizedGraph(int.max) again confusing, the array isn't going to to be int.max long, is it? but GetNormalizedGraph($-1) makes perfect sense for what $ does. The compiler just has to do a little magic, that is all... all sugar is magic anyways. I don't know why some people want to have their sugar in their coffee but the scoff at people who drink cokes.
Oct 10 2018
next sibling parent Neia Neutuladh <neia ikeran.org> writes:
On 10/10/2018 05:01 PM, James Japherson wrote:
 All I'm proposing is to to allow one to escape that syntax to function 
 calls.
 
 foo(int index)
 {
     return arr[index];
 }
 
 and D can support
 
 foo($-1);
 
 which simply gets translated in to
 
 arr[arr.length - 1]
I think you might have a misunderstanding about how $ works. $ is a variable of type size_t. It's an integer. It is syntactic sugar. You can't pass $ as a special value; it's expanded to refer to a specific array at compile time, and it's an alias to the .length property of that array. In order to pass it to a function, you need an array as context. Right now, the compiler looks at the enclosing index expression and uses it to determine the value to pass. You want it to look into the function you're calling to determine the value to pass. It's obvious what you want it to do in this particular case -- the compiler should track where that function parameter is used, find the relevant array, and use it to get the length to pass. How about: module a; extern(C) int foo(int index) { return someGlobalArray[index]; } module b; extern(C) int foo(int index); void main() { foo($); } The compiler doesn't have access to the function body to determine what array you're talking about. Or: int foo(int index) { if (someCondition) return someGlobalArray[index]; else return someOtherArray[index]; } foo($); There are two arrays you could be talking about, potentially of different lengths, and the compiler can't tell which you're going to access. Or: int foo(int index) { int something = index; return someGlobalArray[something]; } The compiler can't just track how the `index` variable is used; it has to track how every variable is used and where it can get its value. This gets complicated fast. Or: int foo(int index) { return std.process.environment["PATH"].split(":")[index]; } The compiler has to execute the bulk of this function at runtime in order to figure out what value to pass to it. Your proposal only works in the most trivial cases. Because of that, if we made that change, you'd try using it at call sites, then the function definition would change slightly and your code would break. It's generally not good for a programming language to have brittle features like that.
Oct 10 2018
prev sibling parent bachmeier <no spam.net> writes:
On Thursday, 11 October 2018 at 00:01:27 UTC, James Japherson 
wrote:

 I don't understand why you need to be convinced that this is 
 relevant.

 Do you not realize that there are cases where one wants to 
 select the last element of a list without having to explicitly 
 know it?
It's fine if you want to have a discussion about this feature, but if you're serious about changing the language, you will not only have to show that it is relevant (and important), but also that you can't do it with existing language features. I'm not being dismissive, just trying to save you some time by avoiding the same pointless discussion I've seen over and over again.
Oct 11 2018
prev sibling next sibling parent crimaniak <crimaniak gmail.com> writes:
On Wednesday, 10 October 2018 at 23:04:46 UTC, James Japherson 
wrote:

 The whole point is not to use $ as an identifier but to specify 
 to the compiler of that it can rewrite it.
It's called 'alias'. // compile time int foo(alias index)(int[] a) { return a[index(a.length)]; } // run time int barr(int[] a, size_t function(size_t) index) { return a[index(a.length)]; } int main() { import std.range: iota; import std.array: array; import std.stdio: writeln; int[100] a = iota(0,100).array; a.foo!(l => l-3).writeln; a.barr(l => l-3).writeln; return 0; }
Oct 10 2018
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Wednesday, 10 October 2018 at 23:04:46 UTC, James Japherson 
wrote:
 The whole point is not to use $ as an identifier but to specify 
 to the compiler of that it can rewrite it.
I know. I'm pointing out that as syntactic sugar, it can't be passed as an int.
 You seem to think that what the compiler does is absolute 
 authority. This is why I said "It would be nice".... meaning 
 that if we had some a feature(which is entirely doable, not 
 some mathematical impossibility), it would allow one to express 
 the limit of an index in a concise way. Your templated version 
 is not concise. All you really proved is that the compiler can 
 be given a rewrite rule and handle this nicely.

 $ is not an used for identifiers, it is used to specify that 
 the maximum length of the array it is used in is to be used. It 
 is short hand for doing hacks such as specifying -1 for maximum 
 length, etc.

 You seem to to have not understood the problem.

 I mean, don't you understand that the entire point of $ in the 
 first place is just syntactic sugar?
I do. Do you understand what syntactic sugar even is? Do you understand what an int is? Do you understand what a compiler is and does? Do you have any idea what separate compilation is? Now, to spell it out: $ is syntactic sugar that is replaced with the .length or .opDollar property of the array/range used in the surrounding expression. It is valid in arr[$-1] exactly because arr is an array that provides the necessary context. The result of an expression using $ is generally a size_t, but can be any type when the range overloads opDollar. The behavior of $ as something you can pass around in other contexts is not easy to pin down. Consider: struct MyRange { string opDollar() { return "foo!"; } bool opIndex(size_t idx) { return false; } bool opIndex(string idx) { return true; } } bool fun(size_t idx) { MyRange mr; return mr[idx]; } unittest { auto x = fun($); // What does it even mean? }
 It also has no context in and of itself. The compiler knows 
 what to do with it... The same can be done with function 
 arguments. You just haven't thought about the problem enough.
Then please, show me how it works in my and others' examples. The compiler only know what we tell it. Your description of this feature is woefully inadequate at providing the information necessary to tell the compiler how to do this. Now, I and others may have come off as slightly... dismissive. It's not that we don't like your idea, we just see a lot of problems with it. There may be solutions to these, and if you have said solutions, we'd love to see them. You'll need to give a more technical description than you have thus far, though. -- Simen
Oct 10 2018
parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
On Thursday, 11 October 2018 at 06:58:08 UTC, Simen Kjærås wrote:
 unittest {
     auto x = fun($); // What does it even mean?
 }
After some reading through the whole thread I think his "$ idea" can only be applied to a RandomAccessRange (and similar) where the size can be known...
Oct 11 2018
parent Neia Neutuladh <neia ikeran.org> writes:
On 10/11/2018 04:36 AM, Dejan Lekic wrote:
 On Thursday, 11 October 2018 at 06:58:08 UTC, Simen Kjærås wrote:
 unittest {
     auto x = fun($); // What does it even mean?
 }
After some reading through the whole thread I think his "$ idea" can only be applied to a RandomAccessRange (and similar) where the size can be known...
It already works with a RandomAccessRange: void main() { auto a = [1, 2, 3, 4]; auto b = a.map!(x => x + 1); writeln(b[$-1]); } It's just that the compiler can't reach into the body of another function to determine what variable you're talking about to find the correct value to pass.
Oct 11 2018
prev sibling parent reply lngns <contact lngnslnvsk.net> writes:
On Wednesday, 10 October 2018 at 13:32:15 UTC, Simen Kjærås wrote:
 struct Sentinel {}
 Sentinel $;

 void foo(T)(T loc) {
     auto bar = double[RandomPInt+1];
     static if (is(T == Sentinel)) {
         return bar[$];
     } else {
         return bar[loc];
     }
 }

 unittest {
     foo($);
 }
It looks to me OP is proposing exactly this, but wants the subscript expression to support it. That would require introducing a new type or changing size_t, so that both integer indices and the sentinel can be passed around. Present-day size_t is unsigned, so we can't use negative values to represent it. I guess it could be useful when you are manipulating multiple arrays at the same time in a functional fashion and need the upper bound of all of them at one point, which could be different.
Oct 14 2018
parent reply lngns <contact lngnslnvsk.net> writes:
On Sunday, 14 October 2018 at 13:18:37 UTC, lngns wrote:
 That would require introducing a new type
Or just use int with a negative number... That's how it's done in some dynamic languages. But my point is that it should be compatible with pre-existing code using unsigned indices somehow. I don't think that is possible.
Oct 14 2018
parent reply Michael Coulombe <kirsybuu gmail.com> writes:
On Sunday, 14 October 2018 at 14:35:36 UTC, lngns wrote:
 On Sunday, 14 October 2018 at 13:18:37 UTC, lngns wrote:
 That would require introducing a new type
Or just use int with a negative number... That's how it's done in some dynamic languages. But my point is that it should be compatible with pre-existing code using unsigned indices somehow. I don't think that is possible.
Another way to do this with UFCS: // write-once library wrapper struct Indexer(R) { R r; auto opDollar() { return r.length; } auto opIndex(size_t i) { return r[i]; } } auto indexer(R)(R r) { return Indexer(r); } // rewrite index parameter => return wrapped range auto foo() { auto arr = (...); return indexer(arr); } // easy-to-use result auto end = foo[$-1];
Oct 14 2018
parent Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Sunday, 14 October 2018 at 15:27:07 UTC, Michael Coulombe 
wrote:
 On Sunday, 14 October 2018 at 14:35:36 UTC, lngns wrote:
 On Sunday, 14 October 2018 at 13:18:37 UTC, lngns wrote:
 That would require introducing a new type
Or just use int with a negative number... That's how it's done in some dynamic languages. But my point is that it should be compatible with pre-existing code using unsigned indices somehow. I don't think that is possible.
Another way to do this with UFCS: // write-once library wrapper struct Indexer(R) { R r; auto opDollar() { return r.length; } auto opIndex(size_t i) { return r[i]; } } auto indexer(R)(R r) { return Indexer(r); } // rewrite index parameter => return wrapped range auto foo() { auto arr = (...); return indexer(arr); } // easy-to-use result auto end = foo[$-1];
Didn't feel like making a full expression tree thing, so feel free to extend this: struct Index { struct Op { string op; int value; void apply(ref size_t val) { switch (op) { case "+": val += value; break; case "-": val -= value; break; case "*": val *= value; break; case "/": val /= value; break; case "%": val %= value; break; default: assert(false); } } } Op[] ops; bool fromEnd; this(bool b) { fromEnd = b; } this(size_t i) { ops ~= Op("+", i); } static Index opDollar() { return Index(true); } static Index opIndex(Index idx) { return idx; } static Index opIndex(size_t idx) { return Index(idx); } auto opBinary(string op)(int rhs) { Index result = this; result.ops ~= Op(op, rhs); return result; } auto value(size_t length) { size_t result = fromEnd ? length : 0; foreach (e; ops) e.apply(result); return result; } } struct Indexer(R) { R r; alias r this; Index opDollar() { return Index(true); } auto opIndex(Index idx) { return r[idx.value(r.length)]; } auto opIndex(size_t idx) { return r[idx]; } } auto indexer(R)(R r) { return Indexer!R(r); } unittest { auto a = Index[$-2]; // Will always point to next-to-last element. auto arr = [1,2,3,4,5,6,7,8,9].indexer; // Wrap access. assert(arr[0] == 1); // Regular access still works. assert(arr[a] == 8); // Look ma, I'm using $! assert(arr[Index[$-1]] == 9); // Look ma, I'm using $! } -- Simen
Oct 16 2018
prev sibling next sibling parent Neia Neutuladh <neia ikeran.org> writes:
On 10/10/2018 01:46 AM, James Japherson wrote:
 Would be nice to be able to pass $ as a function argument to be used in 
 automatic path length traversing.
$ only works in indexing operations because that's required to figure out what it refers to. However, you can mostly use it as a readonly variable there, with the caveat that you can't refer to it directly from function literals.
Oct 10 2018
prev sibling parent Vladimir Panteleev <thecybershadow.lists gmail.com> writes:
On Wednesday, 10 October 2018 at 08:46:42 UTC, James Japherson 
wrote:
 Would be nice to be able to pass $ as a function argument to be 
 used in automatic path length traversing.
You can already do this, by returning a custom type from opDollar: /// Define RealNumbers so that, given `RealNumbers r`, `r[x] == x` but `r[$] == real.infinity`. struct RealNumbers { private struct Dollar {} Dollar opDollar() { return Dollar.init; } real opIndex(size_t index) { return index; } real opIndex(Dollar dollar) { return real.infinity; } } unittest { RealNumbers r; assert(r[5] == 5); assert(r[$] == real.infinity); }
Oct 10 2018