May 11, 2021
On 5/11/21 12:57 PM, Andrei Alexandrescu wrote:
> 
> template FloatingPointTypeOf(T) {
>      static if (isIntegral!T) {
>          alias FloatingPointTypeOf = T;
>      } else ...
> }

Correx:


template FloatingPointTypeOf(T) {
    static if (isIntegral!T) {
        alias FloatingPointTypeOf = double;
    } else ...
}

May 11, 2021
On Tuesday, 11 May 2021 at 16:44:03 UTC, Andrei Alexandrescu wrote:
>> Again with moving the goalposts.
>
> To clarify: you can't make up your own definitions as you go so as to support the point you're making at the moment. You can't go "oh, call it something else than a type, my point stays". No. Your point doesn't stay.
>
> By the same token you can't make up your own definition of what subtyping is and isn't. Value types and reference types are well-trodden ground. You can't just claim new terminology and then prove your own point by using it.

I apologize for injecting myself into this conversation, but with all due respect, what the hell are you talking about? Everything Deadalnix is saying makes perfect sense - it's basic type theory, and yet you're accusing him of moving goalposts and making up definitions, etc. The problem is that `isSomeString` doesn't respect the LSP and the template constraints on the relevant stdlib functions for enums are a hack to work around that. End of story. if `isSomeString` was defined sensibly, these template constraint hacks would not have to exist.

All the bluster about `popFront` on enum strings, etc. is completely irrelevant, and is a red herring anyway (as was already explained).

I'm sorry for being so blunt, but this conversation is painful to read.




May 11, 2021
On Tuesday, 11 May 2021 at 16:44:03 UTC, Andrei Alexandrescu wrote:
> By the same token you can't make up your own definition of what subtyping is and isn't. Value types and reference types are well-trodden ground. You can't just claim new terminology and then prove your own point by using it.

I simply removed an assumption that isn't relevant to the case I'm making, namely wether you consider ref string to be a type or not, because it doesn't affect the conclusion and therefore isn't a debate worth getting into.

You made the point that SomeEnumString cannot be considered a subtype of string because things start breaking when it is passed by ref, and I retort that the exact same things break in the exact same way for subtypes, making your argument moot.

You say "B is not a subtype of A because it exhibit behavior X when passed by ref"
I say "D is a known subtype of C, and it also exibhit behavior X when passed by ref, therefore X cannot be used as a justification that B isn't a subtype of A"

We can argue to no end about what is the right definition that should be used for X, but it really doesn't change the overall point that is being made.
May 11, 2021
On Tuesday, 11 May 2021 at 16:57:13 UTC, Andrei Alexandrescu wrote:
> Reasonable, though again a matter of API definition. Would you expect this to work?
>
> float sin(float x);
> double sin(double x);
> real sin(real x);
> ...
> auto x = sin(1);
>
> Shouldn't that work? Not that int is a full fledged floating point number or anything, simply that you can pass it down to phobos, or anything else for that matter, in place where a floating point number is expected.
>


It's debatable. There are many languages out there where it doesn't.

I think your case here is disingenuous, because an int is not a special kind of float. We are explicitly outside of the scope of the argument being made to begin with. Whatever conclusion we reach using int and float would have no bearing on what should happen for string and SomeEnumString.

However, in D, it is possible to do:

enum SomeEnumInt : int;

This is for instance used in std.encoding. UI'm not sure if this works with float or not, but assuming that it does, then this absolutely and unambiguously work:

enum SomeEnumFloat : float;
SomeEnumFloat f = ...;
auto x = sin(f);

Here, x would have type float, based on `float sin(float x)`.

> Well an argument can be made that it should work, or the API designer can wisely choose to NOT yield true from isFloatingPoint!int.
>

An argument could be made, however, this is not the argument I am making, so I don't really see the point of bringing this up.

> And if we explore this madness further, we get to an enormity just as awful as StringTypeOf:
>
> template FloatingPointTypeOf(T) {
>     static if (isIntegral!T) {
>         alias FloatingPointTypeOf = T;
>     } else ...
> }
>
> And then whenever we need a floating point type we use is(FloatingPointTypeOf!T) like a bunch of dimwits.
>
> What use case does that helps? Who is helped by that? Someone who can't bring themselves to convert whatever they have to double prior to using the standard library.
>
> Arguably not a good design.

This is indeed not a good design, but also isn't really required if the places requiring a float can consistently accept SomeEnumFloat, because in this case, it turtles transparently all the way down.

May 11, 2021
On 5/11/2021 12:14 PM, deadalnix wrote:
> I think your case here is disingenuous, because an int is not a special kind of float.
D has no notion of a "special kind of type". It only has a notion of "implicitly convertible".

* An int is implicitly convertible to a float.

* An enum is implicitly convertible to its base type.

The two *must* behave the same way, or the language falls apart with hackish special cases that will never work in a predictable manner.

One could design a language with two kinds of conversions:

1. is-a-special-case-of
2. is-implicitly-convertible-to

but D isn't it.
May 11, 2021

On Friday, 7 May 2021 at 11:55:53 UTC, Andrei Alexandrescu wrote:

>

On 5/7/21 2:03 AM, evilrat wrote:

>

On Friday, 7 May 2021 at 03:48:47 UTC, Andrei Alexandrescu wrote:

>

We should remove all that rot from phobos pronto.

https://github.com/dlang/phobos/pull/8029

Just a commoner here, can you explain for stupid what makes enum string a no go and why it should begone?

Heavy toll on the infra for a very niche use case with trivial workarounds on the user side.

To try to put some focus on the user perspective, here's a sample program:

import std.stdio;
import std.array;
import std.range;

void main()
{
    writefln!"%d"(0);

    immutable string f1 = "%d";
    writefln!f1(1);

    enum f2 = "%d";
    writefln!f2(2);

    enum string f3 = "%d";
    writefln!f3(3);

    enum { f4 = "%d" }
    writefln!f4(4);

    enum : string { f5 = "%d" }
    writefln!f5(5);

    enum X { f6 = "%d" }
    writefln!(X.f6)(6);   // Compilation error

    enum Y : string { f7 = "%d" }
    writefln!(Y.f7)(7);   // Compilation error
}

All but the named enums (last two) are fine. These fail with similar compilation errors:

Error: template std.stdio.writefln cannot deduce function from argument types !("%d")(int), candidates are:
dmd-2.095.1/osx/bin/../../src/phobos/std/stdio.d(4258):        writefln(alias fmt, A...)(A args)
  with fmt = f6,
       A = (int)
  must satisfy the following constraint:
       isSomeString!(typeof(fmt))
dmd-2.095.1/osx/bin/../../src/phobos/std/stdio.d(4269):        writefln(Char, A...)(in Char[] fmt, A args

This is at least a potentially confusing situation for users. The error message indicates that f6 should be a "string" of some kind, and it looks like one. One needs to be very familiar with the details to understand why it does not satisfy isSomeString. Similarly with understanding why anonymous enums are fine but named enums are not.

The error message is also not particularly helpful in determining what the available workarounds are. They may be trivial once understood, but there's non-trivial learning to get there. Note that slicing ([]) and .representation() do not work for the template argument. Casting does. e.g. The following is fine:

    writefln!(cast(string)X.f6)(6);

It can be argued that this case is rare enough in user code that the ROI from either making the case work or improving the compiler error message is too low to devote time to this now. But maybe there are other cheap options that could help users. A documentation note perhaps. A FAQ somewhere on the D site that would surface in searches.

May 11, 2021
On Tuesday, 11 May 2021 at 19:37:55 UTC, Walter Bright wrote:
> On 5/11/2021 12:14 PM, deadalnix wrote:
>> I think your case here is disingenuous, because an int is not a special kind of float.
> D has no notion of a "special kind of type". It only has a notion of "implicitly convertible".
>
> * An int is implicitly convertible to a float.
>
> * An enum is implicitly convertible to its base type.
>
> The two *must* behave the same way, or the language falls apart with hackish special cases that will never work in a predictable manner.
>
> One could design a language with two kinds of conversions:
>
> 1. is-a-special-case-of
> 2. is-implicitly-convertible-to
>
> but D isn't it.

Except, it is.

D has numerous instance of both already and pretending it doesn't really isn't going to lead anywhere useful.

And this very thread is indeed proof that "the language falls apart with hackish special cases that will never work in a predictable manner."

The fact is that you can't get rid of 1. and support OOP, because polymorphism is a key ingredient of OOP. And we even go as far as to talk about some of the metaprogramming techniques in D as being compile time polymorphism, so so this is clearly a road we want to embark on.

The alternative is to go full functional on these things, and, as Andrei explain with the tail example, this is an option that works as well, but you have to write everything in functional style, which makes some code harder to write.

Personally, I'm not interested in D going full functional, because I appreciate that different ideas are better expressed in different paradigms. But I understand that it means that we must have 1.

Now, do we need 2. ? Strictly speaking, we do not. We could just say that string float conversion and vice versa must be explicit. We can remove alias this, and whatever other feature of the language does implicit conversion. I'm actually confident that in some cases, that would be a win, but also that we are too far gone to realistically be able to remove 2.

So we have both, we need to live with both, and make sensible decisions based on that. Pretending that we don't have both only leads to the guarantee that we'll make more bad decisions on that front in the future.

May 11, 2021
On Tuesday, 11 May 2021 at 19:56:05 UTC, deadalnix wrote:
> On Tuesday, 11 May 2021 at 19:37:55 UTC, Walter Bright wrote:
>> [...]
>
> Except, it is.
>
> D has numerous instance of both already and pretending it doesn't really isn't going to lead anywhere useful.
>
> [...]
Remove alias this support for classes and replace it with compile time default interface methods.

-Alex
May 11, 2021
On 5/11/21 2:37 PM, Meta wrote:
> On Tuesday, 11 May 2021 at 16:44:03 UTC, Andrei Alexandrescu wrote:
>>> Again with moving the goalposts.
>>
>> To clarify: you can't make up your own definitions as you go so as to support the point you're making at the moment. You can't go "oh, call it something else than a type, my point stays". No. Your point doesn't stay.
>>
>> By the same token you can't make up your own definition of what subtyping is and isn't. Value types and reference types are well-trodden ground. You can't just claim new terminology and then prove your own point by using it.
> 
> I apologize for injecting myself into this conversation, but with all due respect, what the hell are you talking about? Everything Deadalnix is saying makes perfect sense - it's basic type theory, and yet you're accusing him of moving goalposts and making up definitions, etc. The problem is that `isSomeString` doesn't respect the LSP and the template constraints on the relevant stdlib functions for enums are a hack to work around that. End of story. if `isSomeString` was defined sensibly, these template constraint hacks would not have to exist.
> 
> All the bluster about `popFront` on enum strings, etc. is completely irrelevant, and is a red herring anyway (as was already explained).
> 
> I'm sorry for being so blunt, but this conversation is painful to read.

Being blunt is totally cool, but that doesn't make you right.

There's no true subtyping or polymorphism with value semantics. This has been common knowledge in C++ - inheriting a value type is an antipattern for many reasons, and conversion operators are to be used carefully (and not as a substitute to subtyping) for many other reasons.

With value types, it's all static typing, no polymorphism, no LSP beyond what's called ad-hoc polymorphism in the classic Caderlli et al paper (http://poincare.matf.bg.ac.rs/~smalkov/files/old/fp.r344.2016/public/predavanja/FP.cas.2016.07%20-%20p471-cardelli.pdf).

What can be aimed for with values is called "parametric polymorphism" (which is NOT subtyping) by the same paper: "Parametric polymorphism is obtained when a function works uniformly on a range of types; these types normally exhibit some common structure."

That works if and only if you can reasonably supplant the same primitives across said range of types. With enums that's onerous; as soon as you "derive" an enum from int you figure that ++x can't reasonably be implemented. Same goes for enum strings - you can't implement the expected string primitives so substitutability is out the window.

Values are monomorphic. Years ago I found a bug in a large C++ system that went like this:

class Widget : BaseWidget {
    ...
    Widget* clone() {
        assert(typeid(this) == typeid(Widget*));
        return new Widget(*this);
    }
};

The assert was a _monomorphism test_, i.e. it made sure that the current object is actually a Widget and not something derived from it, who forgot to override clone() once again.

The problem was the code was doing exactly what it shouldn't have, yet the assert was puzzlingly passing. Since everyone here is great at teaching basic type theory, it's an obvious problem - the fix is:

        assert(typeid(*this) == typeid(Widget));

Then the assertion started failing as expected. Following that, I've used that example for years in teaching and to invariably there are eyes going wide when they hear that C++ pointers are monomorphic, it's the pointed-to values that are polymorphic, and that's an essential distinction. (In D, just like in Java, classes take care of that indirection automatically, which can get some confused.)
May 11, 2021
On Tuesday, 11 May 2021 at 21:36:46 UTC, Andrei Alexandrescu wrote:
> There's no true subtyping or polymorphism with value semantics.

I think you guys need to agree on what you mean by "type" and "subtype".

Mathematically a type would be a set of states and a set of operators that can take you between the states.  A subtype is just a reduced set of states/operators where operators keep you within the set of states.

In OO a type is an abstraction (reduced set) of the states that the entity you model in The Real World has. A subclass in OO is increasing the number of states/operators, but decreasing the number of Real World entities covered.

So these twi notions of "subtype" are opposite.

> primitives across said range of types. With enums that's onerous; as soon as you "derive" an enum from int you figure that ++x can't reasonably be implemented. Same goes for enum

In C enums are subtypes of int. You reduce the number of states.

C enums are not sound, because operators can take you out of the allowed set of states in a heartbeat.

Anyway, I've given up following this discussion.

Just define the desirable outcome (practical design) and forget about the theoretical aspects... then others might be able to understand where the viewpoints differ.