Lazy OR and AND

That’s a very flawed line of reasoning.

Only because someone did something in the past in some way and it didn’t change for a long time doesn’t mean that you should continue to do it in the same way!

With that “argumentation” you can just go back to program in C. Because “it’s been a de-facto standard for literally 50 years now”, so it must be fine…

Also a warning about a very likely bug doesn’t have any downsides. But a lot of upsides!

And just imagine, there are people who don’t “come form JS or Java”…

1 Like

I would like the boolean operators to be usable as pattern guards.

Is this also part of the desire to see the world burn?

def f(i: Int) = i match { case 42 | 27 | 3 if i > 17 | i == 3 => i }

We also need a subthread about symbolic operator syntax.

The compiler’s “summary” output that counts the deprecations could add,

We didn't start the fire.

Someone posted that Poland is doing quite well despite an ongoing dearth of vowels.

image

1 Like

Het “melkmeisje” :grinning:

Different tasks lead to different edges of optimization (lines of code, constructions, space used, etc.).
We all want short but powerful code, less but more powerful constructions used, usually in cost of clarity and bugs-free stability.

Counterexample of thinking of abstract guy:

Task is: turn on maximum number of servers using TurnServerOn(n): Boolean and test only if all are on:

// 3 servers:
if (TurnServerOn(0) & TurnServerOn(1) & TurnServerOn(2)) ...

// short & simple for N servers:
if ((0 until N).foldLeft(true)(_ & TurnServerOn(_))) ...

Imagine guy has old keyboard (or thinks that all logical operands are non-lazy but strict - he was assembler or hardware/controllers guy previously: no laziness at all))) and he accidentally got lazy && in his code. Compiler passes his code but logic changes: after the first server that failed to start no rest one will even try to init.

So, spending time he finds the problem and thinks: “why && not is strict and why lazy one && exists in general ? I know that lazy-one produces complex understanding constructions and changes flow control instead of simple and clear strict logic computations.” )))

My questions: if & is also lazy as you ask or absent at all, then:

  1. why should he write 3 instead of 1 line and produce unnecessary terms ?
val res1 = TurnServerOn(1)
val res2 = TurnServerOn(2)
if (TurnServerOn(0) &lazy res1 &lazy res2) ...
  1. what is the simple implementation for N servers ? In cost of “additional collection” only?) - “non-optimal” for that guy ))
if ((0 until N).map(TurnServerOn).forall(identity)) ...

In case of your example, all safe-thinking solutions lead to eliminating lazy/strict dilemma at all:

  • case _ if a.headOption.getOrElse(-1) > 0 =>
  • Map-like **withDefault** for Array may help in such cases if to implement it in Scala, or Map[Int, Lem] just backed to passed Array[Lem] - array with Map interface to use its safe .getOrElse(i, def) ))
  • Make your own guaranteed lazy extensions &&& ||| - also is safe to miss one letter )))
  • create check function for guard with && once and forget the problem )

I think, in general, lazy-&& construction produces more bugs and thinking complexity than strict-& does.
Everything is simple && clear until the first bugs (and tons of time to fix wasted) like you had.


I know and use ‘&/&&’ since 86 too. So, please believe, I understand you clearly ))
By the way, similar to your mind-edge: when I’m deep in logic and away of language in thinking I write “or”/“and” instead of “||” / “&&” - Microsoft fooled me :latin_cross: ))) . I do not know if that “or”/“and” are lazy or strict, but compiler helps resulting no logic errors

Not at all! As you know I’m quite a fan (Suggestion: if-comprehensions - #40 by LPTK).

The problem with @som-snytt’s suggestion is that it would silently break most past, present, and future Scala code without a good way of palliating the breakage.

1 Like

Modern pharmaceuticals are quite remarkable.

1 Like

I agree. This would be good for beginners. I often see beginners write one fish & instead of two fishes && just because they don’t remember that it should be two, or see no good point of writing it twice, and they often have no clue about esoteric bit operators. With a warning they would grok where in their code the fishy thing is going on…

5 Likes

Perhaps we could, in the spirit of having introduced the new control syntax then, do, also introduce less cryptic and, or, not – following again the Algol-tradition, which IMHO is more friendly to beginners as explicit keywords reads better, esp. compared to || which is not commonly understood as “or” in natural language. It could initially be opt-in.

We get a step on the way with these definitions, but not all the way to common parenthesis-less precedence rules etc. so needs to be keywords+syntax to be really useful:

extension (a: Boolean) 
  inline infix def and(b: => Boolean) = a && b
  inline infix def or(b: => Boolean) = a || b

inline def not(a: Boolean) = !a
scala> if not(1 == 1) and 1 / 0 == 0 then "T" else "F"
val res0: String = F

Demonstrates short-circuit.

3 Likes

You can use an inline argument to avoid the overhead of a by-name argument while remaining short circuit, as in https://github.com/charpov/TinyScalaUtils/blob/main/S/src/main/scala/tinyscalautils/assertions/implies.scala.

3 Likes

agree, now precedence is the only problem with “human” names of custom or aliased operations.

New keywords with such names always influence code backward compatibility.

But for any custom DSL (including our example of renamed binary ops) it it will be good to have an ability at least to link the priority level of new defined operation to any existing operation with known precedence
like in:

def not<!>(...
def and<&>(...
def or<|>(...

or with use of an annotations:

def @samePriorityAs(&) and(...

For other custom DSLs it may be useful to define precedence relations between newly defined methods. Something like

def eat(...
def @higherPriorityThan(eat) buy(...

That is not possible. You would need to resolve method references to know their precedence, in order to parse recordings containing them, in order to resolve method references.

Precedence most be defined syntactically once and for all.

2 Likes

Thinking about it, it seems that its a historical legacy we’re just stuck with. If we were designing a language for 2023, then it would obviously make sense to use the single character operators for Booleans and the double character for bit wise as in 2023 the former are far more common than the latter. The way it is made total sense when C was created.

I value correctness, so will strive to correct all my misuses of the & and | operators. But it seems an inevitable fact of life that will people will use the shorter operator for Booleans with no obvious reason not to.

Aha. Thanks! Cool!

Well, we were not too much stuck with if (expr1) expr2 else expr2 as we were able to introduce the “new control syntax” as in if expr1 then expr2 else expr3, which I think is a clear success among students and objectively better for readability.

So I guess we could introduce (optional) “new logical connectives” with and or not if we want to, without breaking legacy && || & | ! ~etc.

The downside is introducing another way of doing the same thing.

The upside is making Scala easier to learn and read.

I for one was definitely not a fan of this change, making the language both more complicated and more verbose. Why do need these constructs at all? if-else is just a “fold” method on a Boolean and the bare if construct is just a “forTrue” method on a Boolean. Where I’ve got short expressions in my own code I just use the following.

/** if-else. If the condition is true, use 2nd parameter, else use 3rd parameter. */
inline def ife[A](b: Boolean, vTrue: => A, vFalse: => A): A = if (b) vTrue else vFalse

Yes, I also think introducing if then was a mistake. Not because the new syntax is worse, but because it’s not really better either, and churn is itself a big downside if done without proper consideration

I’m not against lightweight syntax in general. In a way, i was the one who came up with it. But the way lightweight syntax was implemented, rolled out, and decided in Scala 3 is in no way a shining example that we should try hard to emulate going forward

5 Likes

It took me quit some time to understand that you actually mean:

(1 != 1) && ((1 / 0) == 0) ? "T" : "F"

“Reads as English” does not help in anything! Especially when it comes to very basic operations. And if the code gets longer and you need to process and translate more words first it gets actually much harder to understand.

Code isn’t processed like language by our brains (only people who can’t program read it “as language”). Code is processed more like math, in an abstract symbolic way, according to fMRI scans. (Of course you still need language processing for the surface understanding as most programming syntax uses regular words. But I would bet a lot that this would go completely away if someone would read for example APL.)

It has reasons why math is mostly symbolic…

Also the whole “reads as English” thingy is moot when people don’t know English… Then “or” is as good as “||” or “或”. Makes exactly no difference.

Nevertheless I would strongly agree that “||” on Booleans should be (at least) a warning in Scala. I really don’t know when this isn’t an error.

Well, for example Swift can do that:

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/advancedoperators/#Precedence-for-Custom-Infix-Operators

(And if I remember correctly also Agda can define custom operators with custom precedence; Coq can rewrite syntax, so it can do that also, but that’s kind of cheating).

But to be honest, I’m more in the “no operator precedence rules in programming languages” camp anyway. Make it read left to right (and maybe keep the swap around twist, as it’s just one simple rule) and else just use parens for everything.

People couldn’t even remember the most basic “Punkt vor Strich” in school, and don’t even ask people whether they still know the full “PEMDAS”. At the same time programming languages have much more rules! That’s not good design. Especially when it comes to teaching. And of course all the “funny” bugs you can spend really long in chasing, because messed up precedence is nothing easily spotted!

Pyret, a language especially made for teaching programming in the 21st century is (once more!) just right:

https://pyret.org/docs/latest/op-precedence.html#(part._.But_why_not_use_precedence_)

(Also look what they do about numbers, and arithmetic operations in general. Following some “traditions” form the stone age of computing, like most languages still do, isn’t really smart. They don’t. Scala OTOH fell for that trap, blindly following C-likes. Now it’s to late. But maybe Scala 4 could take inspiration from Pyret? :grin:)

Oh, and as we’re ranting about if-else syntax, I have also opinions (just not for Scala)! :smiley:

If I would have a language I would not add IF-ELSE. Because for the “one branch version” that are just methods on Booleans; like in Small Talk (.ifTrue() / .ifFalse()); and the “two branch version” is anyway just pattern-matching—which I would write with ? for “match” and # for “patterns”:

isThisPredicateTrue ?
   #true  => ifTrueBranch
   #false => ifFalseBranch

No irregular IF-ELSE syntax. No special case. Simple and readable. Generalizes well.

(One would need to think here of course how to integrate the features form @LPTK’s proposal. But maybe (likely?) this could be done by mere syntax rewriting. I need to try, I guess.)

I agree here.

I really like syntax with less unnecessary noise. But the current Scala solution is subpar. Scala still does not have a clear concept of a “block of code”! This leads at the same time to the whole mess that the question about the colon is. That’s currently pure chaos. I still need to look this detail up constantly. Because there is no logic behind. Logic like for example “when a block follows it needs to be introduced by a colon”.

And actually the colon. Oh, man… It looks indeed good “on paper”, when code is written out without any usual IDE support like syntax highlighting and indent guides. But with syntax highlighting it looks actually quite odd. (And you of course never know where you need it besides for the body of “class likes”). Imho the above “rule” could be simplified to: “An indented region introduces a block. POINT”. And we’re done here! No colons, no special rules, but blocks as first class citizens. (And of course no “end markers”! That’s a really horrible part of the new syntax, even I like the look-and-feel of the rest. I would take braces-noise anytime instead of the horrible “end markers”. At least it’s just one useless symbol… I was even thinking lately to create a VSC extension to hide “end markers”. Nobody needs this nonsense anyway as IDEs show the header of the current scope, and together with indent guides it’s almost impossible to lose orientation even in deeply nested scopes. Works fine for Python—currently one of the most loved languages—so should also work fine for Scala).

The syntax rambling got quite long, I see! I should stop here… :sweat_smile:

What I honestly don’t understand: Scala got endless flak over many many years for having user-definable infix operators. Swift seems to have even funkier ones where you can define precedence. Yet I saw no laments about the perils of Swift’s infix operators and how that makes the language horribly complex. Why is that?

Is it just that Swift is a corporate language with a good marketing department whereas Scala is community driven with some users who like to pile on? Or is it that Swift users use infix operators more “responsibly” than Scala users? If yes, what does it mean, and why is that?

7 Likes

Imho it’s indeed marketing.

In Scala a single random blog post can create a PR disaster. Because no marketing department will show up immediately to put out the fire. (BTW: Great web-site redesign. Good first step in better marketing! :smiley: )

Complains over “Scala’s slow compiler”, and than Rust…

People also ramble(d) about Scala’s “complex type system”. And than TypeScript…

Same for implicits, and especially implicit conversions: C# has also user-definable implicit conversions, and people use them excessively. Nobody ever complained. More the contrary.

And there’s C++ where people will defend all kinds of its idiosyncrasies, which are usually completely crazy when looking into the details. (OK, C++ gets its fair share of hatred because of all the outright crazy stuff, but still it seems to me that Scala got often much more for much less controversial things.)

Part of the problem is imho that a lot of people compare Scala to Java, as they often come form there. And for a Java dev, especially in the pre v8 era, Scala looked very alien. This resulted in much unreasonable lamenting, imho, just because Scala is not Java. (I would say, don’t listen to the Java people at all, and especially don’t copy their “standards”. They have very weird opinions on what “good code” looks like. But imho Java, or better said to be fair what people consider std.Java is mostly an AntiPatternFactoryFactory)

4 Likes