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.
|