It's a small thing, but function/method overloading and default arguments. Any modern language (hi Rust) without such basic programmer conveniences irritates me.
In my experience, most cases of overloading are trying to achieve one of two things:
* An argument with a default value: foo(string bar), foo(string bar, string baz)
* An argument which can be of two or more types: foo(string bar), foo(byte[] bar)
I believe the first problem is better solved by an optional/default argument language feature (or in a pinch, a sentinel value).
I believe the second problem is better solved by either using a dynamic language, or an expressive type system that lets you say what you mean, either via polymorphism or union types.
For the remaining cases it's probably acceptable to have a second function with a slightly different name.
While we're on the subject: Keyword-specified arguments are a major quality-of-life improvement, especially once you have large signatures with many defaults. I see no reason why all languages shouldn't adopt python-style foo(x, y, abc=123, xyz=456). DoThing(myflag=False) is generally much clearer than DoThing(False).
I haven't worked with a functional language professionally.
Closest I came was with python which supports these constructs.
I suppose it comes down to trade offs when designing a language and for what purpose you use them.
This is what I'd love to see die the most. All too often logic will be spread out through unmanageable inheritance trees. I've never seen a completely necessary use for inheritance and I've seen tonnes of misuse.
For me list comprehensions are a godsend. I believe the loop logic is much better expressed in a single line of code than multiple lines of for or while syntax. No intermediate variables too. Now, this is becoming a common feature in all young languages, but the first time I encountered it in python a decade ago, I was on cloud 9. It is still one of my filter criteria when deciding if a language is worth investing in.
Each one is invaluable on their own, but combined they make for very clean, maintainable, and easily testable software.
I find it hard to take any modern language that isn't capable of these seriously. For example, Rust (from talking to some Rust devs on /r/rust) does not have interfaces only "traits" and this prevents you from from being able to use DI thus limiting your ability to mock in the process.
And by dependency injection, I don't mean the XML bullshit that I see Java developers complaining about a lot.
I know this can be put in via libraries, but it's so much easier when it's part of the language (or a language that's sufficiently expressive/flexible to allow it to not look bolted on). Actors, CSP, I don't care. The designs that fall out of using concurrency versus interleaving the logic are just better. They're often simpler, more likely to be correct, often delivering more reusable components. Erlang and Go being two modern exemplars of this, though utilizing different mechanisms.
Bit syntax:
Erlang's bit syntax, paired with binary comprehensions (like list comprehensions but over binary blobs), make a lot of my tasks (at least for prototyping since my employers won't use Erlang in final products) a lot easier. It's clean, paired with Erlang's unification/destructuring bind version of assignment to variables.
Pattern matching:
Remove all those if/else/if blocks, switch/case (or make them more effective) statements. Move a lot of decision points closer to where they actually need to be made (in function dispatch). Paired with static typing, this can also be used to enforce things like that functions are total functions.
Expressive type systems:
Ada, Pascal, Haskell, MLs in general. There are other languages that do a decent job, but these do it best. This allows you to spend time specifying your system, and then describe the system via your types. I'm not passing around an array of 4 bytes, I'm passing around an IP address. In Ada, I'm not passing around an integer, and if you do get smarter and name it something like "MessageIdentifier" it's not just an alias for Integer. You can constrain it to only hold values 0-15. You can make a type modular so that it correctly rolls over to 0, rather than indicating an error when you add 1 to the max value. This can be enforced (largely) at compile time, with some runtime checks. Via testing you can mitigate the need for the runtime checks and remove them if you want improved performance. But you have a smaller surface for runtime errors which greatly reduces burden on testing.
(Nearly) everything is an expression, few statements:
See lisps, erlang, really most functional languages. I find that this fits my mode of thinking and writing code much better. It can allow for some greater concision in code, though also potential for some really terrible looking constructs as well. Everything I write should have a return value of some sort. Even if expressions, switch/case blocks. Even if I choose to ignore it or put it into a throwaway variable like _.
18 comments
[ 2.9 ms ] story [ 52.4 ms ] threadThank you Swift.
Java's overloading syntax always annoyed me as being overly verbose.
But what if you accidentally overload?
How do you think it should look?
* An argument with a default value: foo(string bar), foo(string bar, string baz)
* An argument which can be of two or more types: foo(string bar), foo(byte[] bar)
I believe the first problem is better solved by an optional/default argument language feature (or in a pinch, a sentinel value).
I believe the second problem is better solved by either using a dynamic language, or an expressive type system that lets you say what you mean, either via polymorphism or union types.
For the remaining cases it's probably acceptable to have a second function with a slightly different name.
While we're on the subject: Keyword-specified arguments are a major quality-of-life improvement, especially once you have large signatures with many defaults. I see no reason why all languages shouldn't adopt python-style foo(x, y, abc=123, xyz=456). DoThing(myflag=False) is generally much clearer than DoThing(False).
What do yoy think of mostly-functional languages that add an OO library?
Polymorphism on the other hand is a powerful technique to build flexible systems.
Each one is invaluable on their own, but combined they make for very clean, maintainable, and easily testable software.
I find it hard to take any modern language that isn't capable of these seriously. For example, Rust (from talking to some Rust devs on /r/rust) does not have interfaces only "traits" and this prevents you from from being able to use DI thus limiting your ability to mock in the process.
And by dependency injection, I don't mean the XML bullshit that I see Java developers complaining about a lot.
I know this can be put in via libraries, but it's so much easier when it's part of the language (or a language that's sufficiently expressive/flexible to allow it to not look bolted on). Actors, CSP, I don't care. The designs that fall out of using concurrency versus interleaving the logic are just better. They're often simpler, more likely to be correct, often delivering more reusable components. Erlang and Go being two modern exemplars of this, though utilizing different mechanisms.
Bit syntax:
Erlang's bit syntax, paired with binary comprehensions (like list comprehensions but over binary blobs), make a lot of my tasks (at least for prototyping since my employers won't use Erlang in final products) a lot easier. It's clean, paired with Erlang's unification/destructuring bind version of assignment to variables.
Pattern matching:
Remove all those if/else/if blocks, switch/case (or make them more effective) statements. Move a lot of decision points closer to where they actually need to be made (in function dispatch). Paired with static typing, this can also be used to enforce things like that functions are total functions.
Expressive type systems:
Ada, Pascal, Haskell, MLs in general. There are other languages that do a decent job, but these do it best. This allows you to spend time specifying your system, and then describe the system via your types. I'm not passing around an array of 4 bytes, I'm passing around an IP address. In Ada, I'm not passing around an integer, and if you do get smarter and name it something like "MessageIdentifier" it's not just an alias for Integer. You can constrain it to only hold values 0-15. You can make a type modular so that it correctly rolls over to 0, rather than indicating an error when you add 1 to the max value. This can be enforced (largely) at compile time, with some runtime checks. Via testing you can mitigate the need for the runtime checks and remove them if you want improved performance. But you have a smaller surface for runtime errors which greatly reduces burden on testing.
(Nearly) everything is an expression, few statements:
See lisps, erlang, really most functional languages. I find that this fits my mode of thinking and writing code much better. It can allow for some greater concision in code, though also potential for some really terrible looking constructs as well. Everything I write should have a return value of some sort. Even if expressions, switch/case blocks. Even if I choose to ignore it or put it into a throwaway variable like _.