May 12, 2021
On Wednesday, 12 May 2021 at 16:46:40 UTC, Ola Fosheim Grøstad wrote:
> On Wednesday, 12 May 2021 at 16:38:10 UTC, Ola Fosheim Grøstad wrote:
>> typeid(a) is A*, because that is the type of the pointer. However, the relationship between B* and A* is polymorphic, because you can use B* in the context where you expect A*? E.g. you can call a function that expects paramater A* with a pointer B*. So that makes the relationship polymorphic?
>
>
> To be more precise. B* is a subtype of A* if you can use B* in contexts where A* is expected, which is polymorphic in nature.
>

I would say it is a sybtype, yes, but polymorphism imply that there are several ways to see the same thing, which, as Andrei points out, imply that you go through a reference somewhere.

May 12, 2021
On Wednesday, 12 May 2021 at 16:49:12 UTC, deadalnix wrote:
> On Wednesday, 12 May 2021 at 16:38:10 UTC, Ola Fosheim Grøstad wrote:
>> I have to admit I never use the terminology monomorphic and polymorphic, so my understanding could be wrong. If so, I am probably not alone in the thread, so for the sake of other readers, maybe someone can provide a definition for monomorphic?
>
> It's quite simple.
>
> *a is polymorphic, because it it an object of type A as far as the user of *a is concerned, but it is actually an object of type B (or any other subtype of a).
>
> a itself isn't polymorphic, because it is a pointer to an A no matter what. It is not a pointer to a B that is observed as if it was a pointer to an A. There is nothing more in it to be discovered at run time, it's just a pointer.

I think I understand what you mean, but the terminology used is confusing me. A monomorphic function/operator works on only one type, but a polymorphic function/operators works on many types.

Seems to me that A* can work on many types, but B* can only work on one type (if has no subclasses. So wouldn't that make A* be polymorphic in nature, but B* be monomorphic in nature?

I've recently found it better (less baggage) to think in terms of protocols than classes. Then A* would be a pointer to something that provides the A-protocols. So when an A* pointer points to a B instance then we can think of it as if it points to the A-protocols that B provides. Maybe then you could claim that it is monomorphic as it only binds to A-protocols.

But that is not actually the case, as you have the ability to cast A* to B*. So then it would be polymorphic...? I dunno. Seems it is a matter of perspective, if "monomorphic" means "of one form".

May 12, 2021
On 5/12/21 11:30 AM, deadalnix wrote:
> Last time I we had a discussion on the matter, it went in a loop that is best summarized as this:
> enum E : int { A, B, C }
> 
> while (true) {
>    Me: A | B ought to be an int, not an E.
>    W&A: But you need it to be an enum, so that you can do things like combining flags and stay. As in:
>      enum Mode { Read, Write }
>      openFile(file, Mode.Read | Mode.Write);
>    Me: Wl then, you can't have final switch, because you don't have the guarantee it rely on.
>    W&A: final switch is very much needed, from X, Y Z reason.
> }

I know this is Walter's take, but please don't ascribe it to me as well. I could at the very best give a nod to practicality, but I very much think that typing binary "or" on enums as the enum is a kludge.

> This is extremely tiresome and kinda looks like the current discussion (or another one would be the in contract needing to be statically bound, where Timon and Myself had to fish for Bertrand Meyer because nothing short of an argument from authority could do the trick).
> 
> So if we get nothing else out of that discussion, fixing enum so that they don't go out of the allowed set of value would be nice. It's just unfortunate that it takes literally 5 years+ to get to a point where this is even acknowledged as being an issue.

This reach for credit here does not seem very well deserved.

> I hope we can somehow shorten that process, because it's not workable as it is. You have people around like Timon and myself who have an eye for this. It's free brainpower you are leaving not leveraging.

I will say what follows with the utmost respect. I think Timon is way better at these things (like in, incomparably better) than you and me combined. He most certainly is less skilled than you at other things, but as far as PL theory in this group goes, he and Paul are the only game in town.
May 12, 2021
On Wednesday, 12 May 2021 at 18:35:49 UTC, Andrei Alexandrescu wrote:
> I will say what follows with the utmost respect. I think Timon is way better at these things (like in, incomparably better) than you and me combined. He most certainly is less skilled than you at other things, but as far as PL theory in this group goes, he and Paul are the only game in town.

You are so wonderful at being inclusive... :-P Never seen anyone in these forums that haven' said things about PL theory that is either wrong or lacks nuance. Applies to Andreis, Timons, Pauls alike...

However, since most here does not have comp.sci. background it would be nice if we stop hiding behind terminology (which people will perceive differently even if they have comp.sci. background which is why papers use references).

deadalnix is explaining how he uses the terms which makes the thread more inclusive for all.

Your dismissal is not helpful.


May 12, 2021

On Wednesday, 12 May 2021 at 02:30:50 UTC, deadalnix wrote:

>

On Wednesday, 12 May 2021 at 02:21:06 UTC, 12345swordy wrote:

>

Yes. A is being replace with the new instance of A that happens to have the same value here. There is no guarantee that they will share the same address.

  • Alex

You might want to reconsider how sure of yourself you are. For instance by opening https://replit.com/languages/csharp and running the following code in there:

using System;

class A {
int i;
public A(int i_) { i = i_; }
public int getI() { return i; }
}

class Program {
static void Main(string[] args) {
A a = new A(15);
Console.WriteLine(a.getI());
foo(a);
Console.WriteLine(a.getI());
}

static void foo(A ainfoo) {
ainfoo = new A(23);
Console.WriteLine(ainfoo.getI());
}
}
You are conflicting passing an argument by value/reference with the concept of value/reference types. They are not the same thing.

"Don't confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference."

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref

-Alex

May 12, 2021
On Wednesday, 12 May 2021 at 18:35:49 UTC, Andrei Alexandrescu wrote:
> I will say what follows with the utmost respect. I think Timon is way better at these things (like in, incomparably better) than you and me combined. He most certainly is less skilled than you at other things, but as far as PL theory in this group goes, he and Paul are the only game in town.

It's fine, then just listen to him and not to me. That already would be vast improvement over the current state of affairs.
May 12, 2021
On Wednesday, 12 May 2021 at 15:41:31 UTC, 12345swordy wrote:
> On Wednesday, 12 May 2021 at 15:31:29 UTC, deadalnix wrote:
>> On Wednesday, 12 May 2021 at 14:35:27 UTC, 12345swordy wrote:
>>> Replacing the item in the box with the different yet exact same item, doesn't mean that you didn't modify the box. Again, print the object memory address, and you will see what I am talking about.
>>>
>>> -Alex
>>
>> I legitimately can't tell if you are an idiot or a troll.
>
> What kind of idiot that ignores official documentation provided by Microsoft that clearly states that classes are reference types not value types!? Your coding examples does NOT DISPROVE THIS NOTATION WHATSOEVER!!!!
>
> -Alex

I think, you both talking about same thing. I think what he meant about half value type, half reference type, is that the variables/function parameters, themselves are references to the data an object has, and that reference is basically a value type, while the actual object data is stored in memory on that address found in variable/parameter, and this half value/half reference semantics are packaged in a single type, which cannot be broken apart.

I.e. you can't have a variable that just a simple pointer to some heap memory, and you can't also have a variable that actually contains the data the object has on stack, like in C++ for example.

This is the same thing what you've meant by classes being reference types, he just went a level lower into the implementation of so called reference types.

To remark, I'm Java developer, not C#, though both have common roots.

Best regards,
Alexandru.
May 12, 2021
On Monday, 10 May 2021 at 21:55:54 UTC, deadalnix wrote:
> If you think that invalidate the LSP, I'm afraid there is a big misunderstanding about the LSP. Not all operation on a subtype have to return said subtype. It is made clearer if you consider the slicing operationa s a member function on an object instead - as I seems classes and inheritance is the only way OPP is understood these days.
>
> class A {
>    A slice(int start, int end) { ... }
> }
>
> class B : A {}
>
> Where is it implied that B's version of the slice operation must return an A? Nowhere, the LSP absolutely doesn't mandate that. It mandate that you can pass a B to something that expects an A, and that thing will behave the way you'd expect.
>
> And it does!
>
> If your code needs an A, then you mark it as accepting an A as input. If I have a B and want to pass it to your code, I can too, transparently. You do not need to even know about the existence of B when your wrote your code. This is what the LSP is at its core.
>
> Back to our string example, the code should accept string (A), with zero knowledge of the existence of any enum string (B). You should be able to pass a B to that code and have everything work as expected.

I concede the points that enum strings do not violate the LSP, and that they are subtypes of string. You're right, and I was wrong.

The point I should have made is that, at least in D, the LSP is not universal. There are situations where it simply does not apply. In particular, it does not guarantee that a substitution which changes the arguments used to instantiate a template will succeed; e.g.,

    class A { int x; }
    class B : A { int y; }

    void example(T)(T obj) {
        static assert(!__traits(hasMember, T, "y"));
    }

`example(new A)` will compile, but `example(new B)` will not--because they are not actually calling the same function. One calls `example!A` and the other calls `example!B`. This is an unavoidable consequence of the expressive power of D's templates: without specific knowledge about `example`'s implementation, we cannot guarantee anything about the relationship between `example!A` and `example!B`.

All of which is to say, the fact that you can pass a string as an argument to a template does not *necessarily* imply that you can pass an enum string as an argument to the same template. That `format` handles them differently does not "fly in the face of Liskov's substitution principle" [1], any more than my example above does.

[1] https://forum.dlang.org/post/fnibsejuozasspsggxie@forum.dlang.org
May 12, 2021
On Wednesday, 12 May 2021 at 22:00:57 UTC, Paul Backus wrote:
> I concede the points that enum strings do not violate the LSP, and that they are subtypes of string. You're right, and I was wrong.
>

Thanks.

> The point I should have made is that, at least in D, the LSP is not universal. There are situations where it simply does not apply. In particular, it does not guarantee that a substitution which changes the arguments used to instantiate a template will succeed; e.g.,
>
> [...]
>
> All of which is to say, the fact that you can pass a string as an argument to a template does not *necessarily* imply that you can pass an enum string as an argument to the same template. That `format` handles them differently does not "fly in the face of Liskov's substitution principle" [1], any more than my example above does.
>
> [1] https://forum.dlang.org/post/fnibsejuozasspsggxie@forum.dlang.org

That is true, and there are definitively cases where it is unavoidable.

However, I don't think format fits that bill, because format does expect a string, not any random type. Where I'm getting at is a bit complicated to express clearly, because types are effectively also "values" that you can pass around at compile time, but let me try.

We should reasonably expect the LSP to work when what is passed down is the value of the enum, but not when it it's type - which, in fact, isn't too surprising because the type itself isn't subject to the LSP.

Consider:

class A{}
class B : A {}

void foo(A a); // We should expect the LSP to hold true here, because the value is the only argument passed down to foo.
void bar(T)(T t); // There is no expectation that foo(new A) and foo(new B) behave consistently, because not only the value is passed down, but also the type.

While we expect passing down the value to respect the LSP, no such expectation can exist for the type. So in the second exemple, while we expect the runtime parameter `t` to conform to the LSP, we do not expect the compile time parameter `T` to do so. However, if we do not change the value of `T` but pass a B down to `t`, then we should get back to a situation where the LSP is respected.

For instance:

bar!A(new B()); // We expect this to be well behaved when it comes to the LSP, vs say bar(new A()) because the only change happened to the value parameter, which is supposed to uphold the LSP.

So far, so good, I don't think this is too controversial, even though it is confusing to express that concept clearly.

Now, with enum string, there is an interesting twist, because they can be passed at compile time too. in theory, that should not change anything when it comes to the LSP, but in practice, it seems like it does, which is IMO where the root of the problem is.

Consider:

string format(string S, A...)(A args);

While S is a compile time parameter, it is not a type parameter, but a value parameter. In that case, it is expected as per the LSP that I can pass down string, or any subtype of strings as the first compile time parameter of format, and this ought to work as expected.

May 12, 2021
On Wednesday, 12 May 2021 at 23:08:24 UTC, deadalnix wrote:
> Now, with enum string, there is an interesting twist, because they can be passed at compile time too. in theory, that should not change anything when it comes to the LSP, but in practice, it seems like it does, which is IMO where the root of the problem is.
>
> Consider:
>
> string format(string S, A...)(A args);
>
> While S is a compile time parameter, it is not a type parameter, but a value parameter. In that case, it is expected as per the LSP that I can pass down string, or any subtype of strings as the first compile time parameter of format, and this ought to work as expected.

This *does* work as expected: https://run.dlang.io/is/Ru9phk

The issue with `format` is that it takes an alias parameter, not a value parameter--and the reason it does *that* is to support string, wstring, and dstring with a single overload.