Hacker News new | past | comments | ask | show | jobs | submit login
Redundancy in Programming Languages (digitalmars.com)
54 points by goranmoomin on May 2, 2020 | hide | past | favorite | 30 comments



Programming languages extensively use this to reduce coding errors. If there was no redundancy at all in a language, then any random sequence of characters would be a valid program.

This is completely opposite to the philosophy of the APL-family languages, and those who have learned them seem to also argue strongly that the conciseness reduces errors and makes for higher productivity.

Probably the best example of redundancy in a programming language is unit tests.

Some of my worst experiences have been fighting unit tests that were "overspecified", i.e. testing for inputs which would otherwise be clearly impossible in the context of the whole system, with the result that any trivial change to the code can cause test failures that consume time to diagnose, despite zero change to the ultimate functionality of the system.


I have yet to see any language feature that cannot be used to create a steaming pile of parrot droppings by a well-meaning programmer ticking every box of "best practices".


Useful redundancy is a continuum. On one side is adding "unnecessary" null checks. On the other side re-implementing complex business logic checks. I like to strike a balance.

"Overspeced" unit tests? They should only be testing the API contracts and all edge cases. And refactoring your code should not result in changes to the unit tests as the purpose of the unit test is to tell you if your refactoring broke something. If you have to change your unit tests when you refactor, you're doing something wrong.

Fighting unit tests is a code smell for testing implementation details or poorly designed API that needs to keep changing.

I've been bitten by "clearly impossible in the context of the whole system" way too many times. That said, I'm not going to do horribly complex implementation detailed validation. But sanity checks are worth their weight in gold.


> And refactoring your code should not result in changes to the unit tests as the purpose of the unit test is to tell you if your refactoring broke something. If you have to change your unit tests when you refactor, you're doing something wrong

I disagree a bit here. Without getting pendantic about units and refactors, in practice, it is common to want to modify methods that have unit tests for them. Maybe you want to break out the method into two, or break the class into two classes, or you want to improve the method's interface, change the types it takes, its parameters, the structure it returns, etc.

All these changes will most likely break your unit tests. If they didn't break the unit tests for the method, I'd actually be curious what it was even testing.

Above this, people often in practice also unit tests private methods, so you've often got to deal with those type of unit tests as well even if you're only touching the inside of a class and leaving the public methods intact.

And beyond refactoring, sometimes you are simply adding a new feature or fixing a bug, and this will result in you maybe choosing to modify the behavior of some existing class and its methods. These changes will also break your unit tests.

What all the use cases I just listed have in common is that all of them have good reasons to break the unit tests, and will result in people simply going and changing the tests to reflect the new changes.


> testing for inputs which would otherwise be clearly impossible in the context of the whole system

Off the top of my head...

1. When reusing code, unit tests that specify unexpected inputs are useful because you don't know the future use case. That's a rather weak reason to write the unit tests now, but a reason.

2. There is no way to enforce what is an expected input to a function, once a system grows large enough. Changes in the upstream dataflow being traced through the system by investigating every unit test is impractical. Being defensive up front, is reasonable.


because you don't know the future use case

...and thus you shouldn't care about it. I've seen enough problems caused by attempts at "futureproofing" to know that YAGNI is always a good choice. When/if it does matter, then you can do the work; but inevitably, requirements always seem to change in such a way that your attempts to anticipate them results in code that's structured to be flexible in exactly the wrong way, adding unnecessary complexity, or even hinders the desired changes.


There is no way to enforce what is an expected input to a function, once a system grows large enough.

This is exactly what typechecking does for you and is the reason I think trying to build any large system without them is a bad idea.


> This is exactly what typechecking does for you

Having types, isn't a binary choice. eg Generics, plain objects, functions, pointers. There are inevitably gaps in how the type system can ensure your what your outputs will be (even if it's a halt).


Sure, in an absolute sense. But in practical terms they are fantastically helpful here.


APL is a wonderfully imaginative language. But - it is difficult to decipher, and I would imagine rather difficult to debug. (Not to mention requiring a specialty keyboard...)


Reminds me of a bug I found in some Scala code.

  def getString: String = {
    "I am a value to be resolved"
  }
  
  def wrapper1(auth)(thunk: => String): String = {
    if(auth)
      thunk
  }
  
  def wrapper2(auth)(valueAlreadyResolved: String): String = {
    if(auth)
      valueAlreadyResolved. // Auth check did nothing, code already ran!!!
  }
  
  wrapper1(auth)(getString)
  wrapper2(auth)(getString)

These two method signatures/behavior are different but the syntax to call them is identical! One executes "getString" only when the parameter is referenced (v1), the other (v2) executes it before the wrapper is even entered.

This sort of flexibility can be handy, but it can also shoot you in the foot. Some languages like I think Java make you do something like "::getString" to pass a function pointer. That redundancy makes it explicit and prevents the mistake above which could have serious implications.


There was some debate at an old job about whether or not to use semicolons in JavaScript. I would just like to remind everyone reading this that if you don't write semicolons in your JavaScript, you are doing it wrong, and we cannot be friends.


> There was some debate at an old job about whether or not to use semicolons in JavaScript. I would just like to remind everyone reading this that if you don't write semicolons in your JavaScript, you are doing it wrong, and we cannot be friends.

Well the great thing with that issue is that you can use tools that will forcefully put semi-colons in code and no rely on automatic insertion at runtime.

There is no excuse today not to do that. Of course, sticking to a specific coding style and enforcing it automatically with QA tools during CI is also something easy to do and should be mandatory for any codebase.


I’ve said it before, I’ll say it again. Semicolon-free JS is much easier to edit, and the typical foot-guns associated with it can be trivially avoided with a formatter/linter.


Semicolon JavaScript is trivially easy to write with a formatter -- just hit format and it inserts semicolons for you. And has no footguns.


It’s not writing the semis themselves that’s the issue - obviously that’s trivial.

It’s editing lines that have semis. Any “to end of line” operation (and there are tons) you have to think about whether you want the semi or not, which often depends on what the future context will be. This is one of those things where once you get used to “power-editing” in semi-free, it’s very difficult to go back. And note that the bulk of my work is semi-d, I only go semi-free on greenfield projects. So it’s not a case of having more experience one way or the other.

Any lisper/schemester will tell you that reducing syntax makes power editing simple, but for some reason JS people love their semi’s. Beats me.


Semicolons can also be automated, which is what I currently do at work.


See reply to sibling. It’s not about inserting the semi, that’s trivial.


You use unit tests in Python and Javascript among other things to check for type/value in some places which can be easily avoided if you explicitly used types. Also I often see in imperative languages that unit tests are used to declaratively describe something which you had to imperatively implement. To me it often feels like declarative functional languages have these features immediately as you write the code but then you have to invest the mental effort up front so people rather do half the thinking first then wrap things up with unit tests. The problem is you have to go back to the initial code when wiritng unit tests which fail while you could have done the complete work the first time around. Often it feels like people don't want to do too much work so they choose the language which allows them to offload erorrs for later. It even makes business sense to finish sooner then charge for maintenance later. A shame really


> people don't want to do too much work so they choose the language which allows them to offload erors for later

Replace "people" with business and you are right. I feel many devs would prefer to invest the time once rather than hack it once then go back and add unit tests


There are similar stories for gendered articles and conjugation in natural languages. They don't convey any additional information, but in case you miss the word, they provide clues elsewhere in the sentence that significantly constrain what it could have been.


> Eliminating redundancy is often viewed as a way to streamline productivity and reduce waste

AFAICT, that is much more often said about _programs_ (and perhaps libraries) than about programming _languages_.


Author here. AMA.


Why do you this authors of these programming languages try to creat a different set of keyword names and syntax. If google liked python why not follow the already familiarpython syntax to create go?


Different languages have different semantics, so the language designers choose to use a different syntax to reflect the different semantics.


We also use different syntax in places where the original was known to be a source of problems.

For a trivial example, `unsigned long long` is annoyingly long to type, `ulong` is much better. `uint64` seems at first impression to be better because the bit length is in the name, but digits are harder to touch-type, not so pleasing to look at, and after using the language for a day the reminder that it is 64 bits gets tedious.


Just remove ++p or p++ from your language, problem solved ;)

Interesting blogpost though I do not agree on everything you say but I respect your perspective as a language designer and developer.


I like your observations in this article, but I find them really hard to apply to design

Redundancy is evil... but also essential. What characteristics distinguish one end of redundancy from the other?


> What characteristics distinguish one end of redundancy from the other?

Hard to say. Some call it simply "good taste".


1. That article is short, clear and makes its (IMHO somewhat obvious) valid point well. 2. ... is the article at all about D?




Consider applying for YC's Spring batch! Applications are open till Feb 11.

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

Search: