On `or` and `and` operator deprecation

I don’t see how that can work without either fundamentally changing Dialyzer or breaking laziness of these operators. In a dynamically typed language you can’t know the type of a value without computing it. Cue Rice’s theorem and all that.
If I remember lectures by Stavros correctly, Dialyzer won’t complain about an execution path, unless it always leads to the empty set. With lazy operators, there’s always a successful execution path regardless of the second operand. If you “tighten the types” for lazy operators, then at best, you might get a bunch of mysterious “this function will never be called” warnings. Don’t quote me on this, though.

1 Like

…Not to mention it will break even more perfectly valid code for… reasons.

1 Like

To illustrate how Dialyzer thinks:

-module(test).

-compile(export_all).

-spec test1(boolean()) -> boolean().
test1(A) when is_boolean(A) ->
   A or error(bad_type).

-spec test2(boolean()) -> boolean().
test2(A) when is_boolean(A) ->
  A orelse error(bad_type).

Here, I used error(..) as a makeshift proxy for a badly typed second argument. That allows us to model what would happen if orelse was “tightly typed”. Dialyzer generates the following warnings:

test.erl:6:1: Function test1/1 has no local return

No warnings for test2, since there’s one value of the argument for which the execution path leads to a return value.

2 Likes

X andalso Y is equivalent to
case X of true → Y ; false → false end.
That expression has a type which I’ll express as type(Y) union {false}.
It isn’t an error whatever type(Y) is.
Where the error comes in is if the result is used in a context where {false,true} is expected.
The same applies, mutatis mutandis, to orelse. There is an error only if the second argument is non-Boolean and the result is used in a context where Boolean is expected.

But more realistically, who is going to write X andalso trueu? The second argument is going to be a function call that returns a bad value, a function which is expected to return Bookean, and that is the error the Dialyzer should catch before it gets around to andalso.

Show me REAL code where this is an yncaught issue.

2 Likes

Sure, andalso/orelse just pass the second operand through, and it only blows up (if ever) wherever the result later lands in a boolean context. But that’s the downside I’m pointing at, not a refutation of it: with and/or the bad value crashes where it’s produced, not three frames downstream—which is the early let-it-crash I want.

Just in case this wasn’t obvious, my point wasn’t actually about andalso trueu typos but about ... andalso f(X) where f/1 unexpectedly returns a non-bool.

As for real code: the case where both sides are meant to be booleans seems like the common one to me—and the bugs there are exactly the ones that don’t crash, where the result never hits a boolean context and a subtly wrong value just travels on.

I think you can write “clean code” like this:

foo() andalso bar() andalso true
2 Likes

true.

Erlang newcomers will love it :smiley:

No, where it’s produced is the function that is supposed to return a Boolean value and doesn’t. Foo(X) andalso makes perfect sense in some contexts. You seem to think it is always wrong. We could debate whether it should be but by design, it isn’t. In your sketch, it is f that should be reported as the site of the error, because that is whwre the error IS.

I am really getting tiredof bombination in a vacuum (or maybe it’s the pain meds, either way, I’m fed up.)

LEt’S HAVE A REAL EXAMPLE FROMREAL CODE.
What do we need an example of?
An occurrence of L andalso R where R is not a function call or an if or case or try form,
where R yields a value other than true or false but was not intended to, and that this was found in released code after causing a mishap.
An example using orelse also counts.

If this does turn out to be a real issue, one could imagine a module flagtelling the Dialyzer to treat andalso like and for type checking purposes.

To be clear, I’m not really out to defend and/or as such—my point is narrower: this runtime check is behaviour you lose by deprecating them. What to conclude from that is a separate question.

I never claimed R is always meant to be a boolean, either—both cases exist. But for the case where it is meant to be one, I think badarg is preferable to a silently wrong value.

And sure: if f/1 returns a non-bool there, then f/1 is what’s broken, no argument. It’d be nice if Dialyzer simply guaranteed that—but it gives success typings, optimistic by design and explicitly not complete, and the gaps are the dynamic edges (distribution, hot code loading, apply, libs I don’t control).

In my book, that’s just Erlang being Erlang. Our static type analysis tools are improving, but at its core, Erlang’s reliability is still largely based on “let-it-crash at runtime”.