Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Walter Bright - Programming Language Ideas That Work and Don't Work [video] (youtube.com)
24 points by jay_kyburz on July 16, 2023 | hide | past | favorite | 26 comments


This is a pretty great talk. I listened to it as a podcast instead of a video and it worked very well. You can even figure out the mistakes he's talking about without the code because we've all been there, e.g. the for loop, presumably this:

    for (int i = 0; i < 10; ++i);
    { ...
I agree with him on all points, though I'm not sure about warnings. I think most people are against warnings because they've been traumatised by C/C++ where a ton of the warnings really should be errors (my favourite is -Wreturn-type which can cause insanely difficult to debug issues if omitted). Any sane C++ project has to turn on `-Wall -Werror` because so many of the warnings are really "backwards compatible errors".

So warnings are a disaster in C++ but I don't think that means they fundamentally are. Warnings in Rust seem to be fine.

And having no warnings at all is more annoying than Rust's approach. Go won't let you have unused variables - which is an indication of mistakes - but is really annoying when you're writing incomplete code.


Werror means as soon as someone uses a new compiler with a new warning your code stops building. Even if it's something about misleading parenthesis or brace position. It's not a good thing to have on by default in build script.


But that is still a problem specific to C, not warnings in general.

First of all, languages with less legacy constraints can have the line between warnings and errors in the right place, so users don't have the need to turn all warnings into errors. Werror can be reduced from being the best practice to just overly cautious pedantism.

Then, even Werror can be made more robust. Rust solves this in a few ways:

• cap-lints: it suppresses its Werror equivalent when building someone else's code (dependencies), so the code you can't fix yourself won't break on you due to just a lint.

• editions: Bigger language changes that could render old code incompatible are gated behind the edition setting, which is an opt-in to new stuff, including some new warnings.

• `allow(warning_id)`: you can put it on any code block or even the whole project. So even if you get a new warning that is a false positive or just don't have time to deal with it, you can reliably and precisely turn it off in the source code (as opposed to compiler-specific flags, #pragmas, or reshuffling the code until the warning goes away).

• clippy: all the weaker, experimental, and more opinionated warnings are in a separate tool. The compiler proper adopts clippy's warnings only if they prove to be useful. The type of people who use Werror use clippy, so they'll likely have their codebase clean of these warnings before they make it to the compiler.


You should have it on for CI and for first party developers by default, but have it off if you distribute your source to third parties for the reason you stated.

That's kind of my point. C/C++ have a really bad implementation of warnings, which leads people to think that warnings are fundamentally a bad idea, which is not something I am convinced by.


I think people should look at the output of their compiler and fix warnings before checking in code.

And frankly I'd rather the build not stop on the first error. In fact it would be best if the build system tried to link even if there are errors. Might work.


> I think people should look at the output of their compiler and fix warnings before checking in code.

I agree, but I don't think they should be required to fix all warnings before checking in code, which is what no-warnings languages like Go require.

> And frankly I'd rather the build not stop on the first error. In fact it would be best if the build system tried to link even if there are errors. Might work.

Not sure what your point is here. Most compilers don't stop on the first error.


Thank goodness I'm not the only one who things Macros are evil.

I've seen his comments here quite a bit, It's good to put a face with a name. 8)

As far as compiler flags, that's an interesting take on things. From the meta perspective, not making a decision but putting in a feature switch is essentially kicking a can down the road, with all the grief that eventually entails.


Implicit variable declaration “doesn’t work”, he says. Those millions upon millions of developers creating real world value using Python sure are going to be surprised when they find out.


Python doesn't have implicit declaration. The first occurrence of the assignment syntax declares the variable and binds it to a value.

Proof that Python doesn't have implicit declaration:

  >>> foo
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  NameError: name 'foo' is not defined
Pyhon's design sin is that it fails to distinguish the binding of a variable from assignment. The programmer cannot locally (i.e. just in that statement alone) express "I expect this statement to be freshly creating a variable, introducing it into the scope, with value 42" and "I expect to be assigning value 42 to a variable which already exists in the scope (or else this should be diagnosed)".

An example of a language actually without declaration is Awk. This helps one-liners be very terse, but hinders large programs.

Proof that awk has implicit declaration in contrast to Python:

  $ awk 'BEGIN { print foo }'

  $
No errors. The shell has implicit declaration:

  $ echo $foo
You turn it off in serious scripts with "set -u" to have it diagnosed.


Likewise is the idea that if a variable isn't defined, it can be treated as an unknown mathematical variable of sorts

So if x is unbound, x + x evaluates to 2x

And if x = 2, x + x evaluates to 4

But this means that it's hard to catch typos!

AFAIK Mathematica works like this.


If Mathematica couldn't simplify formulas over variables that are not instantiated, what good would it be? That's an important job of a computer algebra system.


Yes being a CAS and a general purpose language is at odds. I guess there's two paths

a) use sigils to signal that some variable should be treated as an abstract values for CAS purposes. like, :x + :x rather than x + x (or.. use sigils to signal that a variable is "normal", like $x is a normal variable and x + x is CAS stuff)

b) declare CAS variables as such before using them. like with a "var" keyword or something. optionally, declare normal variables, but with a different keyword

Note that formal math always look like "b)" (we declare our variables before using them), it's informal math that looks what Mathematica is doing


> Python doesn't have implicit declaration. The first occurrence of the assignment syntax declares the variable and binds it to a value.

That's exactly what he means by "implicit declaration" though - he even calls out Python as an example.

Python does have implicit declaration, it is just slightly more limited than languages like Bash and CMake - it only implicitly declares variables when you write to them, not when you read them.

Anyway, regardless of the semantics I think we all agree that all forms of implicit variable declaration are a bad idea.


Betz should fix his terminology to align with the industry.

There is nothing implicit about x = 42 in Python. You wrote it explicitly.

The language is not pretending that you invisibly defined x somewhere at the top and are now assigning it.

The most familiar "implicit declaration" to everyone is the feature in traditional C (that any good C compiler warns about and was banished in C++ early). Like that you can call a function without declaring it:

  puts("Hello"); /* no <stdio.h> included */
The compiler looks at the argument types and deduces a declaration. The return type is assumed to be int, and since the "Hello" expression decays to char * type, the implicit declaration is int puts(char *).

Python actually doesn't have declarations at all (except when we consider the new typing features).

When we introduce something into a scope without giving any attributes about it like type, that's a definition. A declaration is something which makes facts known about an identifier: like whether it is a constant, does it have a type, and such. It doesn't necessarily make it exist.

In Lisp declarations can be added about an existing identifier:

   (let ((x 3)) ;; definition of x
     (declare (type (integer x))) ;; declaration about x
     ...)
In languages focused on static type, definitions declare. E.g. in C, all definitions (other than of macros) are declarations; not all declarations are definitions.

In Python, a variable must be visibly defined before it is used, quite clearly; it doesn't have implicit definition. Untyped Python has no declarations.


> There is nothing implicit about x = 42 in Python. You wrote it explicitly.

You wrote the assignment explicitly. You did not write any declaration that "a variable x should already exist" anywhere did you?

> Betz should fix his terminology to align with the industry.

His terminology is aligned with the industry. Google it. Here are the top two results for me:

* https://www.quora.com/What-does-the-explicit-and-implicit-de... * https://stackoverflow.com/questions/56999347/variable-declar...

> Like that you can call a function without declaring it:

That's exactly the same as your Python example!! "There's nothing implicit about `puts("Hello");`. You wrote it explicitly."

> When we introduce something into a scope without giving any attributes about it like type, that's a definition. A declaration is something which makes facts known about an identifier: like whether it is a constant, does it have a type, and such. It doesn't necessarily make it exist.

No. A declaration can only declare that a variable exists. Consider this in Javascript:

    let foo;
Not "having" declarations and declarations being implicit are exactly the same thing. You're getting tripped up over your language.


> A declaration can only declare that a variable exists.

Yes; that's the only correct thing in your comment.

A declaration can declare that a variable exists, and nothing more, which is useful in some language that has a strict rule that variables must have been indicated as existing before they can be used.

If a declaration causes the variable to exist, then it's a definition.

However, about that specific example, "let foo;" in Javascript is actually a definition. It causes foo to exist in the scope where it appears, with a value of undefined.


> Pyhon's design sin is that it fails to distinguish the binding of a variable from assignment. The programmer cannot locally (i.e. just in that statement alone) express "I expect this statement to be freshly creating a variable, introducing it into the scope, with value 42" and "I expect to be assigning value 42 to a variable which already exists in the scope (or else this should be diagnosed)".

I'm not entirely sure this is a bad thing? I'm learning Go right now, and I'm encountering plenty of code like:

    err := DoFoo()
    if err != nil ....

    err = DoBar()
    if err != nil ....
The problem with that is that if you remove the DoFoo line you have to change the DoBar line, even though nothing's really changed there. An (albeit small) waste of time, an extra thing to think about which has nothing to do with the problem you're solving, more lines to review in the PR, and more chance of merge conflicts later.


You could bind the variable each time, if that is allowed:

  err := DoFoo();
  if err != nil ...


  err := DoBar()
  if err != nil ...
DoFoo and DoBar are different functions; their error return might not even be the same type.

If mixed declarations and statements are not supported:

  {
    err := DoFoo();
    if err != nil ...
  }


  {
    err := DoBar()
    if err != nil ...
  }
this is what I do in C to reduce the scope of variables, and it has the benefit over the former example in that we delimit where the scope begins and ends. We know that after the closing brace, nothing can be referencing that err variable. (I'm not a fan of mixed decls and statements).

Modern languages should support binding a variable in if:

  if (err := DoFoo()) {
    // err scoped here
  }
My first example would be "bad" according to David Betz because it perpetrates shadowing.


You can avoid this by using :

  if err := DoFoo(); err != nil { ...
The 'err' variable will be local to the 'if' so you can write also :

  if err := DoBar(); err != nil { ...
It is also nice to write it in one line :-)


You've just hit Go's design flaw: error handling.


Well there's a reason that JavaScript used to support this but no longer does. Characterising it as 'implicit variable declaration doesn't work' seems like a fairly reasonable summary of the view of a very large number of people.

To be true it doesn't need to be impossible to write code in a language with implicit declaration, it just needs to be significantly worse.


In JavaScript, implicit varible declarations goes to the global scope, which is obviously a footgun. In python they go to the local scope as you would expect.


Going to local scope is obviously better, but it still allows misspellings (some of which are very difficult to see) to create new variables and have code that looks like it works but does something entirely different to what you expect.


To be fair, most of his talk is biased towards compiled and statically typed languages (topics like compiler switches, ABI, etc). Not that compiled languages can't have implicit variables, but a lot of people would find it surprising behavior. People familiar with compiled languages expect the compiler to be strict and tend to dislike it when it guesses (especially when it guesses wrong).


Come on, obviously when he's saying it "doesn't work" he doesn't mean "it makes it impossible to use the language to write software".


They actually do, when they have to track down a bug caused by a typo, or have the happy task to refactor a piece of code and forget a couple of places.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: