Modern C# is already a statically-typed OO language that takes lots of lessons from functional programming and decades of scathing OO criticism (after all it's basically "Java done right"). And of course there's also Scala and F# if you want something more functional than OO.
I think the author summarizes it best at the end: "I realize there's not much need for a language like this—the space is crowded enough as it is—but it's fun to dream.".
Java and .NET are still catching up to what it offered in 2000.
Sadly it never got a big name sponsor to push it.
Has Eiffel had a high-quality open source implementation? My recollection is that Eiffel Studio's software is proprietary and expensive.
Are there are free tools for becoming a mechanic? No, they got to be paid, either new or used in some form.
And yes, nowadays there is a community version for the free beer crowd.
Meyer's book where pretty highly ranked in the canon of OOD, so at least a few people knew the language from there. But similar things could be said about Smalltalk. Eiffel was a bit in a weird place, though. C++ and later Java ate a lot of its customers, it didn't enter a smaller niche like Delphi and on the safe & secure side, you had Ada.
Doesn't Meyer now have the professorship that Wirth had before? Apparently the place for the Cassandras of programming language design.
And you are forgetting the ML -> F# / Scala part.
C# 1.0 was based on Java 1.3 and modern C# has inherited a lot of its warts and added some of its own, like delegates and events.
Though nowadays I think maybe language support for monadic Result<T, X> is the way to go.
Checked exceptions were introduced by CLU, adopted by Modula-3 and C++, two big influences in Java design.
"obvious, non-negotiable things any new OO language should have":
> No nulls
C# has null. It looks like after C# 8.0 there is a way to require annotating types are nullable. But it isn't as elegant as a true Option type, and looks like there may still be some gotchas.
> No unsafe cast
It isn't entirely clear what is meant by "unsafe", but casts in C# can throw an exception if the type doesn't match at runtime, which I suspect fails to meat the criteria here.
> Optional named arguments
C# has this
> Generics
C# has generics, and while I don't have a whole lot of experience with it, it seems pretty good.
> Immutability by default
Fields and variables in C# are mutable by default (but can be marked as immutable with `readonly`).
"less obvious choices":
> Class based discoverability
I think C# has this
> Multiple inheritance
C# does not have multiple inheritance. However default interface methods give some of the benefits.
> Minimal syntax
I'm not entirely sure what is meant by this, but from the comparison with scala, I think C# satisfies this.
> Higher kinded types
AFAICT, C# does not currently have Higher kinded types
> No Exceptions
C# definitely has exceptions
> Unified classes and Typeclasses
C# doesn't have an equivalent of typeclasses. In part because of the absence of higher kinded types (see above).
> Pattern matching without destructuring
C# switch statements do this.
So, C# has some of what the author is looking for, but is also missing a lot. Yes, C# did take some inspiration from functional languages. Yes it did learn from some of the mistakes of earlier OO languages (notably Java). But C# is itself over a decade old and constrained by backwards compatibility. If it had been created today, would probably be substantially different.
Disclaimer: I haven't used C# very much, so this is mostly from what I remember when I learned C# years ago, and what I was able to find with simple searches online.
C# with monads would be great.
public readonly struct Option<T>
{
public static readonly Option<T> NONE = new Option<T>();
//
public T Value { get; }
public bool HasValue { get; }
public Option(T value)
{
Value = value;
HasValue = value is { };
}
public Option<TR> Select<TR>(Func<T, TR> selector) => HasValue ? new Option<TR>(selector(Value)) : Option<TR>.NONE;
public Option<TR> SelectMany<TR>(Func<T, Option<TR>> selector) => HasValue ? selector(Value) : Option<TR>.NONE;
public T OrElse(T defValue = default) => HasValue ? Value : defValue;
}
You can of course add more utility functions besides these, eventually as extension methods, but it's a starting point.If you just need some ready-made monads, language-ext is the way to go: https://github.com/louthy/language-ext
We tried to bring some functional ideas into our Unity3D codebase with help of these resources, it's hard but doable.
Java tried to fix that, but doesn't let you generalize over exceptions, "my method throws FooException and whatever exceptions method Bar on the instance of Glorbable you're passing me throws".
Nulls have the same problem. Every time you get an instance of Foo you don't know if it's a null or an object. C# is trying to migrate to explicit nullability, but it's hard to convert an established ecosystem with idiosyncratic nullability rules.
Whether a method throws an exception or not doesn't matter as much as people think it does. You can rarely recover from an exception in the immediate parent calling method anyway. More than likely the error will occur in a stack 20 layers deep and recovery is to restart the entire operation from the start.
Nulls are useful but I totally agree that they should be opt-in in the type system.
Nope, Java picked the idea from CLU, Modual-3 and C++, in what seemed the way forward to declare exceptions.
It does provide both checked and unckecked exceptions, unfortunely it suffers from bad learnings and quick hacks to shut the compiler instead of thinking it through when designing class libraries (yes even the std suffers from this).
Dart has just done that, though admitedly, it did not have an ecosystem as large as C#'s.
Except nothing else in the language forces you to handle the optional case, so you end up with either null checks everywhere, or your code blows up with NullPointerExceptions, or sometimes both.
Ever been deep in a Java call stack, and wondered if you've already checked for null in all cases? In a language with (only) Option types, you can simply declare that variable to be T, and if it's actually Option<T> (i.e. you haven't checked for absence along some path) you'll get an error. A good compiler will even tell you where in the code the None is sneaking in, so you can fix it there, or pattern-match on Some and None where the error is, depending on which is correct for your logic.
I'm ignoring the exceptions question, because the post doesn't even consider a condition/restart system, which means it's not-even-wrong.
An Option type means you must specify Some (pointer) or could-be-None (null, NULL pointer) and the compiler will enforce the distinction. Generally this comes with exhaustive pattern matching, so you can turn Some<T> into <T>, and handle None by raising an error or assigning a default or blowing demons out your nose.
None and null are the same thing, the name isn't the important part. Zig, for example, has optional types as ?*Foo, and calls the null pointer "null".
Additionally you can no longer say Thing is Thing, it's always Thing or null - which results in special handling
if (foo === null){} everywhere and when you don't boom NullPointerException.
Exceptions cause problems in other ways (especially when they are allowed to escape up the stack), business logic shouldn't be handling FileNotFoundException that was thrown 6 layers away.
So instead all 6 layers should have to know about it in addition to business logic? In most cases you can't really ignore the fact that a file didn't exist in your business logic.
No, that's the job of whoever requests a file to be read. But what they may do in reaction to this, or discovering the file is empty, or has malformed data, is to throw a DataUnavailable exception, a subtype of RequestError. This is something appropriate places of the business logic up the stack may be interested in. Maybe some code few layers up the call stack can recover from DataUnavailable by attempting to pull from an alternate data source. Maybe not. Maybe that too will fail. Further up still, something else will intercept RequestError and report a problem to the user.
But, unlike with Result types, only those three places in this piece of business logic have to ever care about some file not being read. Everything else can just assume the operation succeeded.
Exception types follow the same rules as "regular" user-defined types: they're relevant in a subsection of a program, and shouldn't propagate beyond that subsection.
It's easier to be adaptive with error handling with exceptions, and who's going to (correctly) test for all possible errors on every call?
I would have the same question.
Sounds nice.
impl Drawable for MyEntity
void Draw(MyEntity self, Canvas...
Which feels quite natural compared to anemic-entity ECS or naive Composition-over-inheritance like so: class MyEntity {
EntityDrawingComponent drawer
EntityMovingComponent mover;
and of course, over the standard Java/C# OO way class MyEntity : Drawable, Movable, ...
void Move(Vec..)
void Draw(Canvas..)
With this type of declaration, I'm forced to mix the code for my different subsystems making it not just likely but inevitable that someone eventually ties these subsystems together.In general, if I were to design a "better" OO language now; i'd make sure to make it almost not OO at all. I'd allow subclass polymorphism, but no virtual methods or classes. Only interfaces/traits and abstract classes. I'd very clearly separate data types from identity types at the language level (which aren't just a difference betweeen stack and heap as with C# structs and classes).
I'd want the bare minimum of functional niceness: enumerations and pattern matching which check for exhaustion. Having that in a lanugage easily lets the developer use different implementation for two completely different cases: open and closed polymorphism sets. OO is great when you don't know what variants might exist, but it's useless when you DO want to control it. If I as a developer know that the set of Payment options are Credit/Cash/Invoice - then I want it exhaustively checked. I deliberatel do not want to leave that open. I want to be able to switch on the closed set of 3 cases, and be warned if I fail to check a case. I want to be able to do this without inverting the logic like and calling into the unknown like paymentMethod.HandlePayment(order).
Nitpick: C# struct can be on the heap (when it's "boxed").
The fundamental difference between C# class and struct is how they treat assignment operation. The class has "reference semantics" (assignment creates a new reference to the same object) and struct has "value semantics" (assignment creates a new object).
C# uses terms "reference types" (of which "class" is one) and "value types" (of which "struct" is one) to make the difference clear.
Structs being on the stack (most of the time) is just a consequence of their value semantics, and should be treated mostly as an implementation detail.
I’d like my language to be extremely explicit about whether an object has identity or not. It ties into things like ownership, move vs. copy, disposal etc too.
class DrawSystem{
Draw(DrawComponent)
}
With some mapping of DrawComponent to MyEntity. That mapping would most likely NOT be in the MyEntity class definition.Or you can put the DrawComponents on the Draw system and simply track mapping back to the MyEntity instance they correspond to. New code lives in the systems.
In a game you'd have some kind of scene system that could track what entity has what components and then you don't even need a MyEntity class at all. You wouldn't track that composition in a class.
The sugar that Rust is bringing is that it feels like the implementation is being added to the instance instead of an external system outside it, but you can make that work in C# with some extension methods. It would be nicer if/when C# gets extension interfaces.
The disconnect I think is that part of the ECS design is that you have the freedom to break that guarantee. Entities are no longer types but collections of components. The soft coupling is the goal.
Few of the things I use inheritance or composition for can be implemented without storing some extra data.
As an example, say a buffered Stream, that takes a Stream instance and adds buffered access. This BufferedStream would need to store the buffer data somewhere.
struct MyEntity {
cache: RenderingCache,
}
impl Drawable for MyEntity {
fn draw(&self, canvas: &mut Canvas) {
self.cache.draw_cached(canvas, |cache_canvas| {
cache_canvas.render(ASSET);
});
}
}
const ASSET: Asset = load_asset("foo.png");Think Rust differs enough that it's a bit hard for me to draw direct comparisons with my primary languages, but with the emphasis on interfaces like that it does seem like a nicer way to implement extension methods.
I was just curious what OP was using these traits for, as in my non-Rust experience seldom have the need to compose stuff without storing additional data.
[1]: https://doc.rust-lang.org/src/std/io/buffered/bufwriter.rs.h...
Then it might be interesting for you that Rust copied this (like a few other things) from Haskell, where they are (somewhat confusingly) called type classes.
Well, that's like your opinion, man ...
The author says there should be no Exceptions and there should be multiple inheritance... I mean, these seem like pretty extreme positions to have when talking about an OO language... if I remember correctly, multiple inheritance, in particular, was one of the features the languages from the late 90's had actually stayed clear of as it had been shown by the previous generation of languages that it was a mostly bad idea. I think even inheritance as done in Java is not the best of ideas, and there seems to be evidence extension methods or, similarly, type classes, are better suited to add behaviour to data structures.
IMO languages like Kotlin and Swift are much more like what modern OOP languages should be than what the author proposes.
My personal take is that implementation inheritance is a bad idea, and, by extension, Object Oriented programming is a dead end.
If you like Ruby and wish it had types, check out Crystal. It's basically Ruby with Go's concurrency model, and it's as fast as Go.
{Edit: repetition}
Both Google and DuckDuckGo give me no results so an example would be appreciated.
(Hope I'm not caught up in a prototype joke :)
My other big problem with OO is the spaghetti code that results once you start mixing overloads and hooks across superclasses and subclasses. The OO model really seems to be a little too low-fidelity overall.
According to Alan Kay, the essential ingredients of OOP are: Message passing, Encapsulation, Dynamic binding.
> OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I’m not aware of them.
* https://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_ka...
* https://medium.com/javascript-scene/the-forgotten-history-of...
* https://softwareengineering.stackexchange.com/questions/4659...
Though it's recognized that Simula invented objects:
At a gig in my college cs lab, teachers talked about OO, 3 teachers, no answer the same.
As a high-level app developer, one has much less use for is-a relationships but what we do sits on top of that well-tread inheritance filled OOP environment.
"The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be."
Typeclasses are a much better way to do polymorphism.
In short, I use OO because everyone else does. Now in my 40s, I’m not going to change merely because another language is more expressive.
But yeah, if I could do it all over again, I’d probably choose Clojure.
OO is a way to bundle a structure with the methods to manage it.
Inheritance is a way to manage the need to customize an object.
Composition is a way to bundle objects.
Why wouldn't you do both? That's what Delphi did back in the 1990s when they built the Visual Component Library. (VCL)
You build your forms in the GUI builder, which composed a form object out of various components which were all eventually derived from tObject. It still lives on, and Lazarus implements something similar for Free Pascal.
What do you think are the major problems with variance in OO?
If you have subtyping you need to think about variance. That's led me to believe that subtyping's simplicity is deceptive, and that it should be avoided as much as possible. (Rust has subtyping but only for lifetimes, and it's confusing enough there.)
Edited to add: Kay also frequently says he's a proponent of object oriented, not class oriented programming. Classes are just a means of organizing the code, objects and their relationships is what matters. In that way, modules in Elixir are not inferior to classes in single-inheritance OO languages: they offer the same level of sharing and composing code (via macros, defoverridable, and defdelegate). Add to it protocols, which can extend arbitrary types (modules) and you get quite nice OO toolbox to use in Elixir.
One source: https://hillelwayne.com/post/alan-kay/
It has not solved the “minimum set of primitives that make OO tenable” problem.
I think there’s room for kitchen sink languages and also opinionated, minimalist languages.
C++ is likely the worst OO language in common usage and C++ developers rightfully avoid using its native OO functionality as much as possible. They will literally jump through hoops and write tons of additional boilerplate code to avoid exposing the language's OO functionality (doing what C++ developers call type-erasure).
C++11 didn't improve OO in C++, it gave programmers many features to avoid having to use it altogether.
Exceptions end up being an elegant way to represent this, you can choose how much you handle and where and match against the types of errors. Everything else I have tried has been worse, forcing me to handle errors I don't care about at that point in the code with a boilerplate response to send it upwards, something exceptions do automatically. I don't get more robust code with explicit error handling I get more verbose code for errors I can't do anything about. In practice it also changes the interface of my methods far more with maintenance and propagates error handling code throughout.
What I found really, really nice for error handling is dataflow-style programming, because the main flow only has to deal with the happy path. If you don't have a result, you simply don't send anything to the next filter in the pipeline.
And you report errors using a stderr style mechanism, so error handling can be centralised and at a high-level, where you actually know what to do with the errors.
Try not to bubble up errors. Many subsystem interactions can be unidirectional. If an error occurs, it is stored right there. The caller might not even care. The situation can be handled later.
Imagine a situation as simple as:
foo = ComputeSomething()
Where ComputeSomething() is a complex operation, spread into multiple functions, with many points that can fail for various reasons. If a function four layers down the stack from ComputeSomething() reports an error, this means all three functions above it need to also pass it back as Result<T>, and if you try to avoid the if-else hell, it means the entire call graph under ComputeSomething() will need to be wired with a monadic interface that smartly doesn't execute functions when Result<T> is of Error type. All it does is make you zoom through the call graph doing nothing, just to simulate the default behavior of an exception bubbling up the call stack.That's suitable for batch programs and other short running things where you can just scrap the incomplete computation and re-try with a blank slate. I'm sure that can be productive for webapp code that sits between a database and the frontend.
For longer running processes and more complex systems, exceptions are not so great. It's almost a given that you will have a hard time tracking down all the error scenarios in your system.
Because the stack unwinding cleanup is the same when an exception occurs or when an operation completes normally this app could recover from anything.
> It's almost a given that you will have a hard time tracking down all the error scenarios in your system.
You don't need to track down all the error scenarios. The only thing you need to worry about is things that you can recover from to retry and things you can't.
Passing errors around places too much emphasis on where errors occur. For recovery you only need to know what you can recover from and where you can do that recovery. This is usually no where near where the error occurs.
This is actually the best argument why exceptions do not "scale": Because in larger systems, the place where you handle the error is almost certainly not up the call stack, but in a different subsystem.
> Passing errors around places too much emphasis on where errors occur. For recovery you only need to know what you can recover from and where you can do that recovery. This is usually no where near where the error occurs.
In a way, errors are just data, like everything else. There is not much sense in making them something special - as I said, with the exception of smaller script-like programs, where you usually want to jump out of a larger subprogram immediately, and rely on the garbage collector / stack unwinding to clean up (hopefully, everything) for you.
I disagree. I absolutely love throwing exceptions from lower levels of my web apps and letting them bubble to be handled by exception mappers that will formulate the correct http response from them.
Many "problems" that people have with functional programming occur because they are trying to use FP techniques in a language that isn't really equipped for functional programming (JavaScript, C#, Java, Go...).
Pascal doesn't have the kingdom of nouns problem. It supports good old fashioned procedures and functions.
Pascal can deal with nulls in strings, because we always know how long they are.
Or perhaps you meant null pointers. Pascal avoids unnecessary pointers, but supports them in a sane manner when you need them.
Pascal can deal with multiple inheritance in a sane manner, by composition. Objects can have other objects as properties, and it just works... no weird restructuring of everything horror stories I've heard about in C++, etc.
Free Pascal has generics there are libraries that let you do dictionaries and lists and trees of your type <T>.
I disagree about exceptions... proper handling of exceptions is a good thing. A function should always return the type you expect, not some weird value you have to test for.
As far as "pattern matching" I assumed you were talking about regex (which can be addressed in a library)... but you're talking about RTTI, or "reflection" where you can get type information at runtime, which is a thing in Pascal and many other languages.
I don't understand the obsession with immutable variables.
[Edit: I think I understand... in Pascal, parameters are passed by value as the default, but can be passed by reference, it depends on how you declare the function or procedure. It has nothing to do with variables that you can't assign new values to, if I'm correct]
Did I miss anything?
Exceptions are an invisible, out-of-band mechanism that will crash your program because you forgot yet another try...catch. Sum types with pattern matching are a much better approach for writing safe and robust code.
The “proper handling of exceptions” panacea I keep hearing about sounds a lot like “OOP done right”... it’s supposed to exist, yet no one seems to know what it actually is.
Out-of-band is the point. It lets you write your code for the correct path, without having to explicitly string a bunch of error types along (or do something annoying like mandating all errors are of type Error, which is a string).
The problem is with invisibility, which is the case with most exception-using languages. Java had it solved right, though, with checked exceptions. Of course back in the 2000s we were a bunch of lazy children and said "checked exceptions are bad because they make us declare stuff up front", which was actually a good thing, but we couldn't understand that, so they became optional in Java.
(On the other hand, exception handling is just a poor shard of proper condition system, like in Common Lisp.)
In my experience, proper exception handling is more difficult than advanced topics like lock free data structures, with the disadvantage that junior programmers have been taught to use exceptions and avoid lock free primitives.
Checked exceptions solve this problem. Checked exceptions vs. result sum types aren’t a black and white dichotomy, however. They are rather two points on a design spectrum, where one design axis is how you want to syntactically handle error escalation up the call chain, another axis is how you want to handle destructuring/restructuring of the results, etc.
Unchecked exceptions are still useful for notifying bugs (e.g. precondition violations) instead of aborting the OS process on failed assertions.
Any way of dealing with errors has to be out of band, we just disagree on the mechanism.
> Or perhaps you meant null pointers.
A major problem in languages like Java is that objects can always be null. A better solution would be to force strict null checks a la Typescript, where if you want to take or return an optionally null reference, you have to explicitly encode that property in the type of the variable.
NullPointerExceptions are frankly a really stupid thing we have to deal with. A proper type system could check for that at compile time.
> but you're talking about RTTI, or "reflection"
OP is talking about ML-style type destructuring, which is kind of the opposite of this.
> I don't understand the obsession with immutable variables.
Mutating variables leads to all kinds of invisibile problems and makes the code a lot harder to reason on. When I write Java, most of my variables are final, the author is right that we should be taking the opposite convention a la Rust, variables should be immutable by default with mutability explicitly annotated.
Am I correct in interpreting that as when you pass a variable to a function by reference it causes problems? Pascal defaults to passing them by value.
There's also the problem of internal mutation of state, with hashtables/collections being again primary examples. It's not always avoidable to have internal mutation, but it shouldn't be the default, in most cases it's just wrong.
(Also, I just discovered that it now targets WASM. Maybe I should give it another go!)
[1] https://tour.dlang.org/tour/en/gems/uniform-function-call-sy...
https://learn.adacore.com/courses/intro-to-ada/chapters/obje...
I mean, Lobster has classes, or at least something that it calls classes:
https://aardappel.github.io/lobster/language_reference.html#...
So you couldn't write `f(a, b, c)` as `a.f(b, c)`, because `f` as a free function couldn't exist to begin with. Either it would already be `a.f`, or `f` would be a method of the current class or a static method imported via `import static`, in which case using the `a.f` syntax could deceive readers about what class `f` is a member of.
That's what I'm going for with this language, for uniformity and discoverability. If you want to add a new method that takes type `A` as its first argument, you should add it as an extension method on type `A`.
Eg, in C#, SqlDataReader is sealed. Which means I have to do this:
reader.GetString(reader.GetOrdinal("FirstName"));
When I just want to skip the verbosity and be able to do this: reader.GetString("FirstName");
So the logical thought there would be just to inherit from it, add an extra overload to those functions, and life got more comfortable, plus you can still pass it to anything that wants a SqlDataReader. But oops, you can't do that, because somebody at Microsoft decided the interface for this thing was perfect is not to be messed with.If there's something that really gets me annoyed is when I run into a limitation that seem completely arbitrary but intentional. It's not there because of the technical limits of the hardware, or because the compiler isn't clever enough, but purely because somebody intentionally decided 'nope, you don't get to do this particular thing you can do all day otherwise'.
Fyi... this stackoverflow Q&A has opinions on why 'sealed' is a rational default for the person who designed the class: https://stackoverflow.com/questions/268251/why-seal-a-class/
It doesn't mean the programmer thinks the base class is "perfect". Instead, the author has not deliberately designed the particular class for inheritance and the "sealed" keyword expresses that.
Looks like my issue with that got fixed with extension methods, which should let me tack on such improvements whether the original designer of the class thought that would be a good idea or not.
That was something I was recalling from a very old project though. I'm not sure right now when that was exactly. Maybe it wasn't in the language yet at the time, or was a new feature still and I didn't find about it, or it wasn't in Mono yet.
I've not done C# in a long time, so no doubt I missed a lot of developments.
Just... exactly this. Hate that so much. I cannot articulate how much I agree with what you have said here.
This issue might not seem relevant in your own projects, where maybe you want to seal a class so a junior developer doesn't break something.
But it's disastrous for library and framework development. Here's an open question of mine related to this:
https://stackoverflow.com/questions/65852139/listen-for-chan...
In that case, I'm trying to write a script that people can attach to a game object to rebuild metadata after a mesh is changed. I need the metadata for my shader, since it needs info about neighboring vertices, which would only be available in geometry shaders (which have fallen out of fashion since they run at the wrong stage of the rendering pipeline).
That way my shader would "just work" without making the user have to remember to set a flag or send an event when something changes (which is not future-proof).
Normally I would just inherit from the Mesh class, override/extend methods like Mesh.SetVertices(), and then explain how to use my class in the readme. Better yet, I would use something like inversion of control (IOC) to replace the project's global Mesh class with my own (this is how frameworks like Laravel work, or try to work). In fact, there are half a dozen ways of accomplishing this, perhaps more.
But with sealed and final, I'm just done. There is no workaround, by design. Because C# inherited this kind of opinionated thinking (anti-pattern) from Java. And Unity uses C#, so unwittingly fell into this trap since so many of their classes are sealed. Which all reduces languages like C# and Java to being toy languages, at least for someone like me working from first principles under some approximation of computer science.
Maybe I should write to Unity and explain that all of this happened during the very first interesting thing I tried to do. Or maybe we could get rid of this concept altogether and avoid the problem in the first place.
Even the gospel of Martin Fowler tends to agree with my stance on this:
Start with this one. It is not out of fashion. Supported by many languages. Also languages do not exist to be cool and admired. They are just a tools that help build things. Concepts like OOP or any other style are not universal. Good for doing some things, not so good for doing other things. Hoping that some concept will magically solve world's problems is very naive at least.
If you're talking about classes, methods, inheritance, and so on, then it's just a syntax equivalent of training wheels. If you want to progress to biking downhill at full speed, most of this needs to come off at some point since the structure is not suitable for more complex situations. This kind of OOP strongly encourages componentization at the smallest level, much much more than is beneficial from an organization standpoint (subsystems).
I wonder if there is a divide between people who programmed before OOP was commonplace and those first experience is learning it in school.
For me the idea that OOP is not suitable for more complex situations betrays it's very conception.
Yep, and also even though OOP was designed as a way to model simulation systems, it isn't the best thing for actual popular simulation systems (video games). They use more flexible patterns like entity-component systems.
OOP also wants to always layout data as array-of-structures, which can be less efficient than structure-of-arrays. Data layout is the most important thing for efficient programs, since memory access is so slow.
Everything above microcode is. Abstractions / paradigms when applied adequately help to see the forest beyond the trees.
But there's a huge variety of programmer personalities; different programmers like different things about programming. We tend to gravitate towards tools that fit our own personality.
The problem is thinking because you found the tool that's the perfect fit for you, and what you're trying to accomplish, that it's a superior tool in general.
I understand why some developers love LISP and think it's the greatest programming language ever. They wonder why nobody else would use a tool so powerful. But it's really not for me -- it's just not what I like about programming. I feel the same about pure functional languages. I've used them enough to understand the appeal. But for me, I find it constrains my creativity. I found the same with Test-driven-development. I can't just can't work by building the tests first -- and I tried. But I can imagine other programmer personality types find that TDD helps their creativity.
PS: I admit I lost the author at "scala is my favourite language" and so am biased.
Elixir looks like the more promising approach. I love its pipe forwarding operator.
Having higher kinded types and type classes like Haskell would be nice though. I think there are definitely things in the article which could be even better, but we've definitely moved that way, and the next generation of language designers will probably have even more insight into how to elegantly combine those features as well.
Don't have to leave the JVM, already there in Scala. And Kotlin Arrow may get merged into the compiler, so Kotlin may have first class support for these language features one day...
At some point, I realized Java code tends to have many more runtime type errors (in particular, NullPointerExceptions and ClassCastExceptions) than similar C++ code (segfaults). Things like rust should be even better than C++, but that’s practical largely thanks to performance optimizations from llvm. Other than Java compatibility, I don’t see any advantage to the JVM at this point.
Care to elaborate what you mean by that?
Here's what I want from a new OO language, exception-wise:
- Checked exceptions only. Every function that can possibly throw, must declare what it throws. Supertypes may be used in declarations to cover a whole family of exception types. Every function must either handle or explicitly declare exceptions that can be thrown from its callees. This is to be baked into type system and enforced at compile-time.
- If the language is driven with IDE use in mind, allow "auto" for checked exception declarations. Will cut down on line noise, at the expense of readability (as type deduction always does). But since exception declarations are resolved at compile time, you won't be able to make an actual mistake here.
- A condition system, not exception system. Extend the out-of-band signalling mechanism to be used for arbitrary things, not just "exceptional situations". I.e. just as I can say `throw SomeError{someData, someMessage}`, I want to also be able to do e.g. `throw Progress{percentage, total}` to feed a progress bar that's declared few layers above in the stack (which would just execute its code and return control to the thrower; no stack unwinding). This is what you have in Common Lisp.
- Stack winding, not just stack unwinding. Also from Common Lisp, I want the exception (condition!) handler to happen prior to stack unwinding, in a way that would allow me to truly recover from the problem and resume execution where the condition was thrown, or somewhere in the middle of the call stack, between the handler and the thrower.
- Separating signalling, handling and recovery, both conceptually and in code. Again, from CL's condition system. A thrower throws an exception (condition), a handler decides what to do (or rethrows), and one of the things it can do is pick a "restart" declared down the call stack - then stack is unwound only to the point of that restart, and control resumes from there. Note the programmatic ability to choose a restart. Not 100% sure how to handle it in a type-safe way, but I believe it could be done.
- All the other stuff from Common Lisp's condition system.
So basically, a statically typed blend of C++, Java and Common Lisp, mashing together their best features into a coherent and powerful system.
If you're thinking like Java.
Go says: interfaces come after classes, not before. They describe the features you want, and any class can match them if it has the features. Therefore an interface is decoupled from the classes it matches.
c++ : both sides can bring state. has diamond issues.
Java : one side can bring state. other just signatures. too restrictive.
What you want is: both sides should be able to bring behaviour, but only side can bring state.
(iirc, ruby does it like that... )And for all of you saying this is basically just Kotlin or Swift.. do those languages have higher-kinded types or true multiple inheritance? Not as far as I can tell. And you could argue those aren’t needed, but that does mean this is a different language with potentially different pros and cons.
Now language is very clunky to work with in ML/"big" data space.
Technically, that is the fault of the runtime, which does not support 64 bit arrays, but naturally teams must be working together.
https://www.airships.net/hindenburg/disaster/oh-the-humanity...
For reasons. So, no, we do not need a new language whose chief organizing principle is inheritance--a poor match for the majority of problems. We know better.
In fact, we don't need a new language. We have lots of languages already, enough so that the few newer ones that have above-average merit are failing to attract enough users to become viable, and bad old languages (C, Javascript, Java, C#) maintain their lead.
If you imagine your new language will be better than any of the already existing languages, think again. A language that doesn't exist has no flaws. Once it starts to exist, you will need to make choices. Sometimes the right choice will not be obvious, with valid arguments for both alternatives. Sometimes there will not be a "right" choice; each alternative leads the language in a different and objectively valid direction. And sometimes a previous choice will box you into the objectively wrong choice, later.
For every existing language, those have all happened, over and over again. Languages are products of humans, and each is riddled with imperfections and compromises. That is the human condition.
So, right now your language is perfect, like all others that don't exist. Make it, and it joins a large crowd of variously imperfect languages competing for painfully limited attention.
Being wrong at the outset on the desirability of a new O-O language (with functional crossover features!), what are the odds it will be even nearly as good as any of the half-dozen best in its target category?
Maybe a better choice would be to join in making the current best in that category more viable. That one will probably still fizzle--it is the natural fate of any language, save a miracle--but its odds are a hundred times better than yours.
And, who knows? You might even choose right and make a difference.
What world does this person live in?
> Go and Rust provide two alternate approaches to error handling. Both treat errors as values, but Go uses multiple return values to return error states, while Rust uses a Result monad.
Then they should consider fixing Go’s greatest oversight: the ability to make exceptions hard and crash your program. I don’t care if the error variable gets set, but I really care if it gets set again before I’ve reset it. That means an error has passed on unnoticed. If “—-hard-errors” could be a compiler flag, that would be perfect.
I believe the consensus for the 20s for error handling is to have both exceptions and error types (or just error pairs). Without exceptions the code starts to bloat up with various try's, ?s, unwraps, pattern matches on sum and option types and nonstandard do notations. You start writing a nice and simple code when prototyping, only to end up with unreadable piece of mess when productionizing and wrapping everything with error handling. Most modern languages which boast for not using exceptions have them anyway, but call them panics or aborts and don't provide first class support for them.
I believe the next paradigm beyond OO and FP is data oriented programming. One could argue that it is what FP is, but modern FP means more Type Oriented programming than Functional Programming. In contrast with Lisp and APL family, which are also considered FP, but whole another class.
OO gets lots is the kingdom of nouns, as the article points out. You often can't even use a stupid function without wrapping it in some class. Type programming makes a type for everything and gets lost in the kingdom of types. You often can't even start programming before you type your data or provide schema for it.
In Data Oriented Programming, you try to limit the number of objects or types to minimum, such as simple linked lists (Lisp) or multidimensional vectors (APL, numpy, tensorflow), json dicts (python, jq), streams of text (shell), tables (sql) or primitives - strings, ints, etc. The big benefit is that you can start coding immediately without defining any classes or types and most operations are already defined for you in a performant way. Once you introduce your custom classes or types, you lose the benefit of carefully designed operations on the language primitives that allow you to stay within the algebra of those language primitives. Eg. in APL you have arrays and numbers and they combine in infinite amount of ways which have been carefully explored and designed over decades for very ergonomic use.
I believe the future is to have small amount of versatile types, not to create languages which promote complexity by introducing tons of non-interoperable custom types.