May 07, 2021
On Friday, 7 May 2021 at 17:16:06 UTC, Steven Schveighoffer wrote:
> On 5/7/21 1:05 PM, Steven Schveighoffer wrote:
>> I think the problem here is that the language doesn't give you a good way to express that. So we rely on template constraints that both can't exactly express that intention, and where the approximations create various template instantiations that cause strange problems (i.e. if you accept an enum that converts to string, it's still an enum inside the template). Whereas the language
>
> I forgot to finish this thought, got interrupted.
>
> Whereas the language (with non-template parameters) does the matching and conversion simultaneously without needing special cases.
>
> -Steve

What's wrong with this?

void fun(T : string)(T t)
May 07, 2021
On Friday, 7 May 2021 at 17:27:18 UTC, Daniel N wrote:
> What's wrong with this?
>
> void fun(T : string)(T t)

That doesn't convert to string. It allows it to compile because T *can* be converted to string and thus it is the closest specialization it can get, but it does NOT actually convert it.

----
import std.stdio;

enum Test : string {
        a = "foo"
}

void test2(T:string)(T t) {
        pragma(msg, T); // Test, not string!
        writeln(t);
}

void main() {
        test2(Test.a);
}
-----
May 07, 2021
On 5/7/21 1:27 PM, Daniel N wrote:
> On Friday, 7 May 2021 at 17:16:06 UTC, Steven Schveighoffer wrote:
>> On 5/7/21 1:05 PM, Steven Schveighoffer wrote:
>>> I think the problem here is that the language doesn't give you a good way to express that. So we rely on template constraints that both can't exactly express that intention, and where the approximations create various template instantiations that cause strange problems (i.e. if you accept an enum that converts to string, it's still an enum inside the template). Whereas the language
>>
>> I forgot to finish this thought, got interrupted.
>>
>> Whereas the language (with non-template parameters) does the matching and conversion simultaneously without needing special cases.
>>
> 
> What's wrong with this?
> 
> void fun(T : string)(T t)

Because T is not a string.

e.g. for an string-based enum, t.popFront won't work.

-Steve
May 07, 2021
On Friday, 7 May 2021 at 17:02:17 UTC, Paul Backus wrote:
> We can already *almost* express this in the language. This code works:

eeeeh that's a compile time argument and it still isn't actually a string.

What I'm talking about is like in the normal function:

void test(string s) {
        writeln(s);
}

enum Test : string {
        a = "foo"
}

test(Test.a);


The conversion to string happens outside `test`. So caller instead of callee, whereas with a template - any template - the exact type is passed, what T:string is saying is that the callee *can* do the conversion if it wants to inside, but the compiler won't actually do it for you.

This is very useful in a lot of cases. Like if you do

void foo(T : SomeBase)(T t) {}

and pass foo(new Derived()), you can still see the whole Dervied type and thus do some reflection and such over it, with the compiler promising that it can be converted to SomeBase if you want to.

Of course, in this case, it is not really different than a template constraint. You could do

void foo(T)(T t) if(is(T : SomeBase)) {}

and get that same rejection behavior. But of course what's nice about specialization is you can then add an overload

void foo(T : SomeBase)(T t) {}
void foo(T : Derived)(T t) {}

And if you get like

class Derived : SomeBase {}
class OtherBranch : SomeBase{}

and call

foo(new Derived()); // goes to second overload as it is a more specific match
foo(new OtherBranch()); // goes to first overload as it is the best option available, but it still can see it is OtherBranch inside there, unlike a normal interface cast where you'd only have that detail at runtime.


May 07, 2021
On Friday, 7 May 2021 at 17:11:32 UTC, Steven Schveighoffer wrote:
> How do you say "I want to accept something that's a string, but I want it as a string please"

Well, one way we can do that today is to have the template forward to a normal function, or a normal function forward to a template.

void format(T...)(const char[] s, T args) {
      format(asRangeOfDchar(s), args);
}

void format(Range T...)(Range r, T args) if(isAppopriateRange!Range) {
       // actual impl based on the range interface
       // and actually tbh I'd personally take another step
       // and collapse all these down even more.
}

Then a whole bunch of conversions are done to match `const char[]` and the template is then working with that entry point instead of the whole plate.

This of course assumes isAppropriateRange is false for anything that isn't actually already a range. And I'm assuming string is not already a range. Otherwise you enter back into the hell of not only saying what you accept, but having to exclude things too.


So let me rant.

I think it was actually a mistake for Phobos to UFCS shoe-horn in range functions on arrays too - this includes strings as well as int[] and such as well. Lots of new users ask why they can't do the same thing. And like Phobos took this opportunity to do silly things like autodecoding when we all hate now, but I don't think the freestanding ufcs range functions should exist at all.

Just have the user fetch a range out of the container. Then they get in that habit with other containers too and it moves a bunch of ugly code out of every consuming function.

Heck the `asRange` thing itself might have a variety of overloads it forwards to.


MyRange asRangeHelper(const char[] s) { return MyRange(s); }

auto asRange(T)(T t) { /* generic stuff */ }
auto asRange(T : const char[])(T t) { return asRangeHelper(t); } // let the language convert it in these specializations

and so on and so forth.



This is a half-baked rant im sure you can destroy at will. But like I'm pretty sure if we did develop this it would be nicer overall than what we have now.


> The answer is because there isn't a good way to do it.

And it is possible the language could insert some magic to make it easier if we really put our thinking caps on.
May 07, 2021
On Friday, 7 May 2021 at 18:17:31 UTC, Adam D. Ruppe wrote:
> void format(T...)(const char[] s, T args) {
>       format(asRangeOfDchar(s), args);
> }

oh i should have added of course you can do the wchar and dchar overloads here too. yeah yeah i know "DRY" but like it is a trivial forwarder, get over it.
May 07, 2021
On 2021-05-07 17:24, Andrei Alexandrescu wrote:

> Compare all that with:
> 
> 0. We put a String type in the standard library. It uses UTF8 inside and supports iteration by either bytes, UTF8, UTF16, or UTF32. It manages its own memory so no need for the GC. It disallows remote coupling across callers/callees. Case closed.

You can have enums with the base type being a struct or a class. How does putting a String type in the standard library help with the enum problem you're describing?

-- 
/Jacob Carlborg
May 07, 2021
On 2021-05-07 17:24, Andrei Alexandrescu wrote:

> 0. We put a String type in the standard library.

If you're going to make strings a user defined type, how are you planning to support things like switch statements with strings? It's not currently possible to have switch statements with user defined types.

-- 
/Jacob Carlborg
May 07, 2021
On 5/7/21 2:17 PM, Adam D. Ruppe wrote:
> I think it was actually a mistake for Phobos to UFCS shoe-horn in range functions on arrays too - this includes strings as well as int[] and such as well.

The most common range BY FAR in all of D code is an array.

The end result of something like you allude to would result in nearly all of phobos NOT working with arrays.

Just a taste:

int[] arr = genArray;
arr.sort(); // fail.

I don't want to go to that place, ever.

-Steve
May 07, 2021
On Friday, 7 May 2021 at 18:25:57 UTC, Jacob Carlborg wrote:
> On 2021-05-07 17:24, Andrei Alexandrescu wrote:
>
>> 0. We put a String type in the standard library.
>
> If you're going to make strings a user defined type, how are you planning to support things like switch statements with strings? It's not currently possible to have switch statements with user defined types.

It really, really should be. Pattern matching and destructuring are two of my most wanted features in D.