January 13

On Saturday, 13 January 2024 at 06:46:54 UTC, Walter Bright wrote:

>

So you agree it is not simpler :-)

Let’s compare them side by side.

// DIP1027 + Sliding Template Arguments

// core.interpolation:

enum FormatString: string;
// Or, alternatively:
// struct FormatString { string s; }

// User-library code:

string generateSql(FormatString fmt) {
    import std.conv: text;

    string sql;
    int number;
    for (size_t i = 0; i < fmt.length; ++i) {
        char c = fmt[i];
        if (c == '%' && i + 1 < fmt.length && fmt[i + 1] == 's') {
            sql ~= text('?', ++number);
            ++i;
        } else if (c == '%' && i + 1 < fmt.length && fmt[i + 1] == '%')
            ++i;
        else
            sql ~= c;
    }
    return sql;
}

auto execi(FormatString fmt, Args...)(Sqlite db, Args args) {
    enum query = generateSql(fmt); // CTFE.
    auto statement = Statement(db, query);
    static foreach (i, arg; args)
        statement.bind(i + 1, arg);
    return statement.execute();
}

// User code:

db.execi(i"SELECT * FROM items WHERE id = $desiredId");
// Desugars to:
db.execi(cast(FormatString)"SELECT * FROM items WHERE id = %s", desiredId);

Vs.

// Steven's proposal + Sliding Template Arguments

// core.interpolation:

struct Interpolation {
    immutable(string)[ ] parts;
}

// User-library code:

string generateSql(Interpolation interp) {
    import std.conv: text;

    string sql;
    foreach (i, part; interp.parts)
        sql ~= i & 0x1 ? text('?', i / 2 + 1) : part;
    return sql;
}

// No changes here.
auto execi(Interpolation interp, Args...)(Sqlite db, Args args) {
    enum query = generateSql(interp); // CTFE.
    auto statement = Statement(db, query);
    static foreach (i, arg; args)
        statement.bind(i + 1, arg);
    return statement.execute();
}

// User code:

db.execi(i"SELECT * FROM items WHERE id = $desiredId");
// Desugars to:
db.execi(Interpolation(["SELECT * FROM items WHERE id = ", "desiredId", ""]), desiredId);

Which one is simpler?

(Note that neither of them will actually work due to the fact FormatString/Interpolation is not the first argument, but that’s not the point I’m arguing here.)

January 13

On Saturday, 13 January 2024 at 18:43:46 UTC, Nickolay Bukreyev wrote:

>

On Saturday, 13 January 2024 at 06:46:54 UTC, Walter Bright wrote:

>

So you agree it is not simpler :-)

Let’s compare them side by side.

Proposal 1:

>
// User-library code:

string generateSql(FormatString fmt) {
    import std.conv: text;

    string sql;
    int number;
    for (size_t i = 0; i < fmt.length; ++i) {
        char c = fmt[i];
        if (c == '%' && i + 1 < fmt.length && fmt[i + 1] == 's') {
            sql ~= text('?', ++number);
            ++i;
        } else if (c == '%' && i + 1 < fmt.length && fmt[i + 1] == '%')
            ++i;
        else
            sql ~= c;
    }
    return sql;
}

Note: the above code has a bug in it (I found it when testing), it's even easy for the language author to get these things wrong!

Proposal 2:

>
// User-library code:

string generateSql(Interpolation interp) {
    import std.conv: text;

    string sql;
    foreach (i, part; interp.parts)
        sql ~= i & 0x1 ? text('?', i / 2 + 1) : part;
    return sql;
}

Which one is simpler?

Thank you for showcasing!

I'll add also the code to generate a writef format string is even easier (if you really wanted to call writef):

string writefFormatString(Interpolation interp)
{
    import std.range : stride;
    import std.algorithm : filter;
    import std.array: join;
    return interp.parts.stride(2).join("%s");
}

And of course, sql dialects that use a static placeholder could just do:

string mysqlQueryString(Interpolation interp)
{
    import std.range : stride;
    import std.algorithm : filter;
    import std.array: join;
    return interp.parts.stride(2).join("?");
}

And in fact, we can provide this as a nice library function for users:

string blueprintWithPlaceholder(Interpolation interp, string placeholder)
{
    import std.range : stride;
    import std.algorithm : filter;
    import std.array: join;
    return interp.parts.stride(2).join(placeholder);
}
>

(Note that neither of them will actually work due to the fact FormatString/Interpolation is not the first argument, but that’s not the point I’m arguing here.)

Ugh, I hadn't thought of that. This is kind of a downer for this idea. It's... still saveable, but it's not as neat.

-Steve

January 13
On Friday, January 12, 2024 3:35:54 PM MST Walter Bright via Digitalmars-d wrote:
> Given the interest in CTFE of istrings, I have been thinking about making a general use case out of it instead of one specific to istrings.

Honestly, this proposal just feels to me like it's making function overloading even more complicated for no obvious benefit. Of course, I don't like the idea of interpolated strings in the first place, so that's likely coloring my judgement on this sort of thing, but all I'm really seeing here is extra complexity being added to something that's already prety complex. And I'd really rather not have runtime arguments silently become compile-time arguments base on how a function signature is written (which could change when the code is refactored). I feel like function overloading is already seriously pushing it with how complicated it is (particularly when templates are involved). If anything, I wish that it were simpler, not more complex.

- Jonathan M Davis



January 13
On Saturday, January 13, 2024 10:12:14 AM MST Steven Schveighoffer via Digitalmars-d wrote:
> The compiler is *required* to parse out the parameters. It has, sitting in it's memory, the list of literals. Why would it reconstruct a string, with an arbitrarily decided placeholder, that you then have to deal with at runtime or CTFE? You are adding unnecessary work for the user, for the benefit of hooking `writef` -- a function *we control and can change to do whatever we want*.
>
> The SQL example *DOES NOT* generate a format string, I've told you this multiple times. It generates a string with placeholders. There is no formatting. In fact, the C function doesn't even accept the parameters, those happen later after you generate the prepared statement.
>
> But also, SQL requires you do it this way. And the C libraries being used require construction of a string (because that's the API C has). An sql library such as mysql-native, which is fully written in D, would not require building a string (and I intend to do this if string interpolation ever happens).
>
> Things other than SQL *do not require building a string*.

To be honest, this is the only part of the string interpolation proposals that seems even vaguely desirable to me. I absolutely hate their syntax in comparison to just calling format. IMHO, it's extremely hard to read strings with variables put into the middle of them, whereas format allows you to see the string's contents with minimal interference from what's going to be inserted into it (and when the interference is greater, it's because you're doing something fancier than %s, and you need to be doing something that you're not gonig to be doing with string interpolation anwyay). Obviously, that's a very subjective thing, but given that I hate the syntax, string interpolation would need to provide an actual technical benefit for it to have any value for me.

So, if the string interpolation produces information for the function being called in a manner that lets it see all of the individual pieces and what their types are such that it can do whatever is appropriate with that information rather than necessarily dealing with a format string that it has to parse, then there's value in that. I question that it's worth the terrible syntax, but at least it is providing some technical benefit at that point rather than just a different syntax to produce format strings. IMHO, if you're just going to create a format string out of the deal, then I see no value over calling format (though obviously, there's some disagreement on that or string interpolation wouldn't have even been proposed in the first place.

Of course, ultimately, I'm not sure that it matters much to me what the exact specification for interpolated strings is if they make it into the language, since I'd rather simply never have to deal with them, but if we're going to get them, I would hope that they would at least provide a technical benefit rather than just a different syntax that some folks think is an improvement.

- Jonathan M Davis



January 14

On Sunday, 14 January 2024 at 02:49:24 UTC, Jonathan M Davis wrote:

>

And I'd really rather not have runtime arguments silently become compile-time arguments base on how a function signature is written (which could change when the code is refactored).

Agreed.

Let me tell you a story. I once had to refactor a D codebase that exhibited monstrous memory usage during compile time. I quickly figured out the cause: it was instantiating tons of different templates with tons of different arguments, most notably value (i.e., non-type) arguments. Many of those template instantiations could be turned into CTFE computations, and some of those arguments could even become runtime arguments with little to no drawbacks.

So what I did was pretty straightforward: I went through the codebase looking for !(...) and considered whether I could move those template arguments to runtime (or CTFE, which is syntactically the same). If a snippet had no !(...), I was confident it didn’t pass value arguments to templates and thus wasn’t related to the problem I was fighting.

If something like Sliding Template Arguments existed at that time, my task would be much harder. For a call foo(arg), I would have to check whether arg was compile-time-known, and if so, look at the signature of foo to find out how it was receiving it. I would have to do that for every argument of every function call.

Now you probably understand why I’m not keen on the idea of magically turning what syntactically looks like a runtime argument into a template argument. I’d really prefer it the other way around: if you need a separate instantiation per value (as opposed to type) of a parameter, put an exclamation point. !(...) is what turns DMD from one of the fastest compilers into the most memory-hungry one; I think it should remain explicit.

January 14

On Friday, 12 January 2024 at 23:10:08 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

I started working on an alternative mechanism to templates for 1036e yesterday. Which I alluded to.

Being able to pass a UDA at a call site and be able to get access to it via __traits(getAttributes, parameter) within the (templated) function.

i"$ident$(expr)$(ident:format)${1:format}"

becomes

@IExpression("ident")
ident,
@IExpression("expr")
expr,
@IExpression("ident")
@IFormat("format")
ident,
@IFormat("format")
@IPosition(1)
IPosition.init

This would be a general purpose language feature.

Your idea looks attracting to me, but to be honest, I haven’t fully grasped it. Could you show an example implementation of execi & generateSql please? Particularly interested in how you would pass and process literal text chunks.

January 14
On 14/01/2024 8:56 PM, Nickolay Bukreyev wrote:
> Your idea looks attracting to me, but to be honest, I haven’t fully grasped it. Could you show an example implementation of `execi` & `generateSql` please? Particularly interested in how you would pass and process literal text chunks.

I'm working off of:

https://forum.dlang.org/post/cbwqnanabefqjvxrzszx@forum.dlang.org

I have not thought this use case through, but the basic premise should be sound.

For instance suffix strings, positional arguments are not handled here and if something wasn't an interpolated string.

```d
db.execi(i"SELECT * FROM items WHERE id = $desiredId");
```

Becomes:

```d
db.execi(@IPrefix("SELECT * FROM items WHERE id = ") desiredId);
```

```d
auto execi(FormatString fmt, Args...)(Sqlite db, Args args) {
	enum query = () {
		string ret;

		static foreach(i; 0 .. Args.length) {
			foreach(prefix; __traits(getAttributes, args[i], IPrefix)) {
				ret ~= prefix.value;
			}
			ret ~= "?";
			ret ~= i.text;
		}

		return ret;
	}();

	auto statement = Statement(db, query);
	static foreach (i, arg; args)
		statement.bind(i + 1, arg);
	return statement.execute();
}
```
January 14
On 14/01/2024 9:23 PM, Richard (Rikki) Andrew Cattermole wrote:
> auto execi(FormatString fmt, Args...)(Sqlite db, Args args) { enum query = () { string ret; static foreach(i; 0 .. Args.length) { foreach(prefix; __traits(getAttributes, args[i], IPrefix)) { ret ~= prefix.value; } ret ~= "?"; ret ~= i.text; } return ret; }(); auto statement = Statement(db, query); static foreach (i, arg; args) statement.bind(i + 1, arg); return statement.execute(); }

Ugh oops, left in the FormatString template parameter.

Should be:

```d
auto execi(Args...)(Sqlite db, Args args) {
    enum query = () {
        string ret;

        static foreach(i; 0 .. Args.length) {
            foreach(prefix; __traits(getAttributes, args[i], IPrefix)) {
                ret ~= prefix.value;
            }
            ret ~= "?";
            ret ~= i.text;
        }

        return ret;
    }();

    auto statement = Statement(db, query);
    static foreach (i, arg; args)
        statement.bind(i + 1, arg);
    return statement.execute();
}
```
January 14
On Sunday, 14 January 2024 at 03:09:23 UTC, Jonathan M Davis wrote:
>
> To be honest, this is the only part of the string interpolation proposals that seems even vaguely desirable to me. I absolutely hate their syntax in comparison to just calling format. IMHO, it's extremely hard to read strings with variables put into the middle of them, whereas format allows you to see the string's contents with minimal interference from what's going to be inserted into it (and when the interference is greater, it's because you're doing something fancier than %s, and you need to be doing something that you're not gonig to be doing with string interpolation anwyay). Obviously, that's a very subjective thing, but given that I hate the syntax, string interpolation would need to provide an actual technical benefit for it to have any value for me.
>

I kinda agree, although I consider $variable to be more readable than format.

1) "$a $b $c" (DIP1027)
2) "%s %s %s".format(a, b, c)
3) "${a} ${b} ${c}" (DIP1036) Too much punctuation noise in the common case.

When you are forced to add {} for simple variables, the entire feature loses its point, even worse when you add expressions with function calls and what not.
1) "%s %s %s".format(a+1, b+1, c+1)
2) "${a+1} ${b+1} ${c+1}" (DIP1036)

Funnily enough, alias parameters can already handle runtime variables...

January 14
On Sunday, 14 January 2024 at 09:41:16 UTC, Daniel N wrote:
>
> When you are forced to add {} for simple variables, the entire feature loses its point, even worse when you add expressions

Just let's use groovy syntax for 1036:
1. $myVar - for variable refs.
2. ${1 + myVar} - for expressions and vars.
3. \$myVar - for string "$myVar".

Then all lazyness will be satisfied.

Best regards,
Alexandru.