February 27, 2020
On Thursday, 27 February 2020 at 14:58:20 UTC, Adam D. Ruppe wrote:
> On Thursday, 27 February 2020 at 14:32:29 UTC, Petar Kirov [ZombineDev] wrote:
>> 2. Have the new type implicitly convert to printf-style args. I think this is what Adam is proposing. While nice to have, I don't think it's necessary.
>
> You can read my document for more detail
>
> https://github.com/dlang/DIPs/pull/186
>
> But basically
>
> writefln(i"hi $name, you are visitor ${%2d}(count)");
>
> gets turned into:
>
> writefln(
>    // the format string is represented by this type
>    new_type!("hi ", spec(null), ", you are visitor ", spec("%2d"))(),
>    // then the referenced arguments are passed as a tuple
>    name,
>    count
> )
>
>
> So very, very, very similar to Walter's proposal, just instead of the compiler generating the format string as a plain string, the format string is represented by a new type, defined by the spec and implemented by druntime. As a result, no more guess work - it is clear that this is meant to be interpreted as a format string. It is clear which parts are placeholders/specifiers for which arguments.

Perhaps my assumptions were based on an old version of your proposal.

What I want is for:

    auto s = i"hi $name, you are visitor ${%2d}(count)";

to lower to:

    auto s = new_type!(
        "hi ", spec(null), ", you are visitor ", spec("%2d")
    )(name, count);

I.e. the referenced arguments are passed to the constructor of new_type.
That way new_type can offer implicit conversion to string, while support for zero-allocation printf, write, writeln, writef, writefln and so on can be done via function overloading.

February 27, 2020
On Thursday, 27 February 2020 at 09:34:23 UTC, Walter Bright wrote:
> On 2/26/2020 7:41 AM, Arine wrote:
>> Yah, what's unwanted about that?
>
> 1. unwanted extra string allocation
> 2. poor performance
> 3. doesn't work with printf
> 4. doesn't work with writef
> 5. non-default formats require extra temp strings to be generated

Sometimes I wonder if you even bother to read posts to understand.

This is a problem with YOUR DIP. Where you said, "what's wrong with that, it's working as intended".


   void CreateWindow(string title, int w = -1, int h = -1);

   int a;
   CreateWindow(i"Title $a");
   // becomes
   CreateWindow("Title %s", a);

That's what your fine with, that's what the DIP your wrote would allow.

It's like you are just reading what you want to, instead of actually understanding what people are saying. I'd have more luck talking to a brick wall at the rate this thread is going, jesus.
February 27, 2020
On Thursday, 27 February 2020 at 17:41:12 UTC, Petar Kirov [ZombineDev] wrote:
>     auto s = new_type!(
>         "hi ", spec(null), ", you are visitor ", spec("%2d")
>     )(name, count);
>
> I.e. the referenced arguments are passed to the constructor of new_type.

Right, that actually is what my old proposal was (and I fought for it on the first few pages of the last thread), and this is very close to what C# does successfully, but I decided to leave it behind because:

1. It doesn't work very nicely in templates:

int a;
foo!i"test $a"; // error, a cannot be evaluated at compile time

Even if `foo` otherwise could handle it (e.g. an `alias` parameter), passing the data to an intermediate object prohibits this.

I personally think that is worth losing in exchange for the other benefits it brings, which you correctly identified at the end of your message, but there's more we lose too:


2. It brings complication with features like `ref` (cannot have a ref struct member so once we pass it, it is gone...) and potentially `scope`, etc., or even UDAs are lost with an intermediary (though UDAs affecting printing might be weird af, the door is totally closed with it and possibly open without).

But bottom line: this means it wouldn't work with non-copyable types.

---
// in theory printable, has a toString method
struct NoCopy { @disable this(this); void toString(dg) {...} }

NoCopy nc;

// but this works with a tuple... not with an intermediary, unless
// the intermediary allocates a string upon construction, of course, but
// that gets us into the hidden GC worries again
print(i"i can print it without copying it $nc");
---

`print` there might take its arguments by `auto ref` and work just fine, but an intermediate object wouldn't have that option.

(ref also means you could do an interpolated `readf` string too, though I kinda think that is a little weird anyway personally, but some people in the other thread did have uses where it could be useful, so if we can avoid breaking it, we should. I think the non-copy print is more compelling though.)


3. As Walter pointed out with GC too, it is easy to go from tuple to object (just do `.whatever` to pass it to an object constructor/helper function), but object to tuple is not so simple. (of course there is `.tupleof`, but the details lost through the intermediate object can never be recovered.) So if there's other use cases we miss, the tuple has fewer potential regrets.


4. Just want to point out that some people feel strongly that the implicit conversion to fully interpolated string is altogether a bad idea because it would be a hidden GC allocation. I don't agree - I don't think it is that hidden since `alias this` only triggers if you name `string` and if you want to avoid GC allocs there's plenty of other ways to do it - but still a few people said it last thread so I wanna mention it here too.


If you like, we could copy/paste this into my DIP too, might be useful for future reference. I didn't spend much time on this since I was focused on just advocating for my amendment to Walter's and assumed the point was moot anyway (Walter's proposal shares these strengths since it also uses the tuple at its core).

But yeah, it is a good idea... just I think my new DIP as written, with a typed format string and a tuple of arguments, is a better idea since it works with more of D's unique features in addition to doing most of what C# can do - just sans the implicit conversion.
February 27, 2020
On Thursday, 27 February 2020 at 18:07:19 UTC, Arine wrote:
> On Thursday, 27 February 2020 at 09:34:23 UTC, Walter Bright wrote:
>> On 2/26/2020 7:41 AM, Arine wrote:
>>> Yah, what's unwanted about that?
>>
>> 1. unwanted extra string allocation
>> 2. poor performance
>> 3. doesn't work with printf
>> 4. doesn't work with writef
>> 5. non-default formats require extra temp strings to be generated
>
> Sometimes I wonder if you even bother to read posts to understand.
>
> This is a problem with YOUR DIP. Where you said, "what's wrong with that, it's working as intended".
>
>
>    void CreateWindow(string title, int w = -1, int h = -1);
>
>    int a;
>    CreateWindow(i"Title $a");
>    // becomes
>    CreateWindow("Title %s", a);
>
> That's what your fine with, that's what the DIP your wrote would allow.
>
> It's like you are just reading what you want to, instead of actually understanding what people are saying. I'd have more luck talking to a brick wall at the rate this thread is going, jesus.

Come on, this isn't Reddit. Be more civil.
February 27, 2020
On Thu, Feb 27, 2020 at 10:11:07AM -0500, Steven Schveighoffer via Digitalmars-d-announce wrote: [...]
> Large hidden invisible types are not the problem (look at normal dynamic arrays, the large hidden type built into the runtime is a huge success I think). The problem is that the compiler gives special features to such types.
> 
> In the case of AA, the ONLY missing piece that cannot be implemented by user types is this:
> 
> int[string][string] aalist;
> 
> aalist["hello"]["world"] = 5;
> 
> In essence, the compiler knows how to make this work. The operators available do not allow this expression to be captured properly by user code (i.e. we don't have opIndexIndexAssign or opIndexIndexIndexAssign, nor does that scale).

It's not that hard:

	https://issues.dlang.org/show_bug.cgi?id=7753

Somebody just has to do it, that's all.


> I believe the last person to try and implement a full template type that could replace AAs is H. S. Teoh. He would have a better explanation (and possibly contradict me, I don't know).

Actually, I may have been the *first* one to try to do this, but I don't think I was the last.  Over the years various pieces of the AA implementation has been templatized and moved to druntime, though the core implementation is still in aaA.d.  I think, on the basis of this other work, that we're in a far better position now to fully move AA's into a template implementation.  I haven't been keeping track, though, so I don't know what issues might remain that hinder this migration.


> Other than that, we've ripped out all other "magic" into templates in the language. If we could get that one feature (not sure how to do this in a scalable way), I think we have a full library type that can be further factored out of the compiler. We might even be able to avoid using TypeInfo, and make CTFE AAs compatible with runtime ones.
[...]

Yeah, most of the work on removing AA magic from the compiler has been done by someone else, IIRC Martin Nowak among them, and probably others.

Making CTFE AAs usable at runtime is somewhat of a different beast, though.  The main problem is that you need to be able to instantiate the binary representation of a runtime AA (the main hash table, and each of the buckets) at compile-time, and do so in a way that the compiler can turn into data in the data segment of the executable.  Regardless of what the CTFE representation is, it needs an explicit transformation step to turn it into something the runtime code can decipher.

Basically, you have to create an .init value for the final object that doesn't require calling a runtime memory allocation function, but nonetheless still points to legit instances of AA buckets and their contents.  This cannot be directly cast from the CTFE AA, because CTFE AA buckets won't have legit runtime addresses that can be assigned to the hash table's pointers.

I think this *might* be possible to do via a string mixin that creates explicit variables for each AA bucket then the main hash table by taking their addresses.  Of course, some hackish casts will probably be required to make it all work with the current runtime AA implementation.


T

-- 
Why can't you just be a nonconformist like everyone else? -- YHL
February 27, 2020
On 2/27/2020 1:45 AM, Rainer Schuetze wrote:
> The string buffer could also be stack allocated or manually managed with
> malloc/free by the string interpolation type.

It's quite a big deal to make that work, and does not address the inherent inefficiency of it.

printf, for all its faults, is very efficient, and one reason is it does not require arbitrary temporary storage. Another is it does not require exception handlers. I, for one, simply would not use such when printf is available.

People often overlook how *expensive* RAII is. Turn exception handling on and have a look at the generated code.

It's a minor syntactic convenience with an unexpected large and hidden cost. That's not what D is about.

Leave this at the user's discretion with:

    f(format("hello %betty"));

where the user chooses via the format function which method of handling temporaries he finds appropriate.

---

Note that the reason outbuffer.d has a printf member function is because it is fast and efficient to format data directly into the output buffer rather than going through a series of temporary strings first.
February 27, 2020
On 2/27/20 1:42 PM, H. S. Teoh wrote:
> Making CTFE AAs usable at runtime is somewhat of a different beast,
> though.  The main problem is that you need to be able to instantiate the
> binary representation of a runtime AA (the main hash table, and each of
> the buckets) at compile-time, and do so in a way that the compiler can
> turn into data in the data segment of the executable.  Regardless of
> what the CTFE representation is, it needs an explicit transformation
> step to turn it into something the runtime code can decipher.

I think this is not too difficult. This works, and it's not much different:

static immutable rbt = new RedBlackTree!int(1,2, 3, 4);

In other words, I have pretty much faith that if the AA becomes a template, then whatever call is made for ["hello": 1, "world": 2] can be callable at compile time, and generate a compatible runtime AA.

The CTFE AA can be whatever CTFE likes, just when it moves to runtime land, it gets translated to an AA literal.

-Steve
February 27, 2020
On Thursday, 27 February 2020 at 18:19:03 UTC, Adam D. Ruppe wrote:
> On Thursday, 27 February 2020 at 17:41:12 UTC, Petar Kirov [ZombineDev] wrote:
>> [...]
>
> Right, that actually is what my old proposal was (and I fought for it on the first few pages of the last thread), and this is very close to what C# does successfully, but I decided to leave it behind because:
>
> [...]

Should this maybe be called tuple interpolation instead of string interpolation? It is unique to D it seems, this feature that is. And then it might help with the “wait why can I not assign an interpolated string to a string”?? And on top maybe change the prefix to t instead of i?
February 27, 2020
On 2/27/2020 6:32 AM, Petar Kirov [ZombineDev] wrote:
> I'm not sure where exactly you draw the line, but I would say that C# follows C's syntax about as much as D does.

C# is very different from C. D is not so different, and close enough that DasBetterC is very viable. Hindsight being 20/20, if I was doing a do-over with D I might even make it able to compile unmodified C code.


> Yet it doesn't import some of the broken C semantics like implicit narrowing conversions (luckily, neither does D) and allowing mixed sign comparisons (the oldest open D issue :( [0]).

I don't like C#'s solution to mixed sign comparisons for various reasons. Or Java's "solution", which is to not have unsigned types.


> My point is that if D didn't follow the usual arithmetic conversions, much fewer newcomers would even notice compared to extremely large backlash that we may get if go with the string interpolation -> raw tuple approach.

As opposed to backlash from another gc-required feature and low performance and not usable with printf nor writef nor any user-built functions like OutBuffer.printf and all the ones used by dmd.


> 1. Have a simple pragma(inline, true) wrapper function that will convert the distinct type to printf-style args. This wrapper function can even be named printf as it would work by virtue of function overloading. This is O(1) additional code that once written no one will need to bother with.
>
> 2. Have the new type implicitly convert to printf-style args. I think this is what Adam is proposing. While nice to have, I don't think it's necessary.

It's better to build things from simple components than try to break up a complex feature into components.


> As for std.stdio.write(f)(ln), there's no reason why any garbage would need to be created, as again, a simple overload that accepts the distinct type will be able to handle it just like it would (performance-wise) with DIP1027. However, with DIP1027, only writef and writefln can be used, while write and writeln will produce wrong results (wrong according to people that have used string interpolation in other languages).

Magic types are not simple and inevitably lead to unexpected corners and unresolvable problems. *cough* associative arrays *cough*

You can have everything you want with DIP1027 interpolated strings by wrapping it in a function call to a function you specify. And so can everyone else.

DIP1027 will also likely lead to a number of unexpected treasures, as has happened repeatedly when simple building blocks were added, while complex ones just caused trouble.
February 27, 2020
On Thu, Feb 27, 2020 at 02:20:14PM -0500, Steven Schveighoffer via Digitalmars-d-announce wrote:
> On 2/27/20 1:42 PM, H. S. Teoh wrote:
> > Making CTFE AAs usable at runtime is somewhat of a different beast,
> > though.  The main problem is that you need to be able to instantiate the
> > binary representation of a runtime AA (the main hash table, and each
> > of the buckets) at compile-time, and do so in a way that the
> > compiler can turn into data in the data segment of the executable.
> > Regardless of what the CTFE representation is, it needs an explicit
> > transformation step to turn it into something the runtime code can
> > decipher.
> 
> I think this is not too difficult. This works, and it's not much different:
> 
> static immutable rbt = new RedBlackTree!int(1,2, 3, 4);
> 
> In other words, I have pretty much faith that if the AA becomes a template, then whatever call is made for ["hello": 1, "world": 2] can be callable at compile time, and generate a compatible runtime AA.
> 
> The CTFE AA can be whatever CTFE likes, just when it moves to runtime land, it gets translated to an AA literal.
[...]

Interesting. So it looks like the problem is "merely" that the AA implementation is opaque to the compiler (the current implementation is PIMPL), so it doesn't know how to turn it into static data.  What if we explicitly cast it into the implementation type, or into a parallel declaration of the implementation type(s), then forcibly cast it back to V[K]? Something like this:

	struct AANode { /* implementation here */ }

	static immutable AANode* aaImpl = /* CTFE AA here */;
	static immutable aa = cast(V[K]) aaImpl;

Not 100% sure about that last line, you probably have to force it into void* or something at some point, I dunno.


T

-- 
MASM = Mana Ada Sistem, Man!