[HN Gopher] An Object-Oriented Language for the '20s
___________________________________________________________________
An Object-Oriented Language for the '20s
Author : ar-nelson
Score : 127 points
Date : 2021-03-13 14:13 UTC (8 hours ago)
(HTM) web link (adam.nels.onl)
(TXT) w3m dump (adam.nels.onl)
| ajani wrote:
| Most of what he mentions as features needed in this new language
| have existed in common lisp for a long time. CLOS.
| vips7L wrote:
| Personally I'd keep exceptions. Result<T> is boiler plate hell.
| dfgdghdf wrote:
| Not if you have language support for monads / do-notation /
| computation expressions.
|
| 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...).
| PaulKeeble wrote:
| Having used Go a lot recently the err!=nil boilerplate is only
| marginally worse than the monad approach which ends up with a
| lot of match statements. Where monads work better is chaining
| calls together, but at the cost of the of choice how to handle
| the errors.
|
| 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.
| mpweiher wrote:
| Yeah, the problem is call/return: you have to return
| _something_. If you have nothing to return (or not yet -
| async), things get tricky.
|
| 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.
| jstimpfle wrote:
| The key is to collect the errors in the subsystem where they
| originated and to handle them at an appropriate time.
|
| 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.
| TeMPOraL wrote:
| Subsystem is too big of a boundary. GP and GGP are likely
| talking about function level - which is where I also
| experience this.
|
| 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.
| ivan_gammel wrote:
| Appropriate time is quite often at the end of the unit of
| work. Let's say, your microservice is doing some
| interactions with database and querying another services.
| If an unlikely I/O error happens, DB will take care of the
| uncommitted transaction and your service is just fine with
| returning HTTP 5xx. Probability of this happening and
| impact of not implementing error handlers all over your
| stack are very low. Since the majority of developers are
| not writing perfect programs, they will not spend the
| money/time to avoid this risk. Exceptions may not help
| writing a better program, but they are practical enough to
| be supported in the language.
| jstimpfle wrote:
| This absolutely works for a HTTP request handler, where
| all the error handling is implemented in the DB / garbage
| collector, and where we're dealing with almost perfectly
| isolated processes.
|
| In more complex systems, this won't work out. Imagine a
| software that controls sensors and motors, for example.
| What if one of these devices fails? There is no way any
| automatic system (like exceptions) could come up with the
| right way to handle this situation.
| ivan_gammel wrote:
| There's no silver bullet that would solve error handling
| in such cases purely by the means of the programming
| language. Just like it's possible to have memory leaks in
| Java, it is possible to write broken code with
| Either<Result, Error> - imagine accidentally ignoring an
| error from sensor that results in overheating and fire.
| If you want to absolutely make sure, that this won't
| happen, then you build your system in a way, where
| prevention of the exceptional situations is implied by
| design, rather than requires an additional effort from
| developer.
| jstimpfle wrote:
| Yup. To continue with this example, an approach that
| often works is to simply not have any error. For sensor
| readouts, request new samples to be reported
| asynchronously. (Coincidentally this is better for
| performance as well).
|
| In this way, the client code is basically required to
| think -- oh, wait, what happens if I'm not getting
| updates for some time beyond what is acceptable for me?
| Also, in this way, the situation can be handled in a
| single central location.
| jstimpfle wrote:
| Exceptions are great when you want errors to terminate a
| (sub)program with an error message without having to type any
| code at all.
|
| 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.
| wvenable wrote:
| The last desktop app I wrote had a single error handler at
| the event loop that displayed an error message and continued.
|
| 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.
| jstimpfle wrote:
| > 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.
| wvenable wrote:
| > There is not much sense in making them something
| special
|
| The reason you treat them as special is because at the
| point an error/exception occurs you are saying that your
| function (or program) can no longer do any more
| meaningful work, forward progress is now impossible, and
| you can't return anything meaningful.
|
| We've gone backwards in making error returns explicit
| because the most common and intelligent thing to do is
| pass the error back up the chain or right out of the
| subsystem entirely. The lazy approach is the correct one
| with exceptions.
|
| > Because in larger systems, the place where you handle
| the error is almost certainly not up the call stack, but
| in a different subsystem.
|
| If your subsystem is network connected then the most
| likely cause of an re-startable operation is a minor
| network issue. There's no need to inform another
| subsystem immediately. I don't really see how it's an
| argument that exceptions don't scale. If you need to
| inform another subsystem you can do that.
| vips7L wrote:
| > 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.
|
| 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.
| chubot wrote:
| parent: _I 'm sure that can be productive for webapp code
| that sits between a database and the frontend._
| ufo wrote:
| I think the parent poster would classify that kind of web
| app under as a "short running thing". Sure, the application
| process might live for a long time but the requests are
| short-lived and independent of each other.
| TeMPOraL wrote:
| Every program is a collection of a lot of "short running
| things". Exceptions work well with this. There's a piece
| of code that calls some function and expects a result or
| an error; there's a piece of code much deeper in the
| stack that can fail. Everything in the middle doesn't
| care about the failure, except to make sure it doesn't
| get executed if the failure occurs. Without exception-
| like mechanism, you _have_ to make the middle care about
| possible errors, just to prevent code past failure point
| from executing. Your calls like foo(a) get turned into
| if(a != error) { foo(a); }, even if it 's hidden behind a
| nice monadic syntax.
| enriquto wrote:
| why is OOP still a thing?
| AnimalMuppet wrote:
| Because it's useful.
| enriquto wrote:
| people use the strangest tools
| toolslive wrote:
| multiple inheritance. 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... )
| fouc wrote:
| I'm fine with implementing another static Object-Oriented
| language, but please learn from some of the best non-static
| Object Oriented languages out there like Ruby and to a lesser
| extent Erlang/Elixir.
| pkulak wrote:
| Erlang/Elixir are two of the least OO languages I can think of.
| Point still taken though. Elixir is an absolute joy, and the
| more new languages take examples from it, the better.
| andrewflnr wrote:
| On the contrary, at least if we talk about Alan Kay's vision
| then Erlang/Elixir are as close as we have today. Hint: the
| processes are objects.
| zokier wrote:
| > at least if we talk about Alan Kay's vision
|
| Big if there. Most people do not mean Kays OO when they say
| OO.
| dragonwriter wrote:
| No, most people think of some ill-defined idea shaped
| largely by the concrete design of early versions of, and
| practice in, C++ and Java, reflecting a set of
| compromises between early OO visions (including, but not
| limited to, Kay's), C's particular style of statically-
| typed procedural programming, and various practices that
| emerged to deal with the warts of those compromises.
| klibertp wrote:
| Which is why they should be educated, given a chance. No?
| xbar wrote:
| I read "eradicated." But sure, let's educate them first.
| jghn wrote:
| I agree w/ your sentiments here, but it is worth keeping
| in mind that Simula predates Smalltalk and could be
| viewed as both the first OO language and the progenitor
| of what became mainstream OOP.
| klibertp wrote:
| You're right. I'm constantly surprised how much of the
| history of programming I'm still missing, despite trying
| to learn as much as I can about it. The feature set of
| Algol blew my mind when I first learned about it, for
| example - and now I see I need to take a closer look at
| Simula, which was developed as a superset/extension of
| Algol. Learning about that is fascinating and really puts
| the "language wars" of today into perspective.
| jghn wrote:
| BTW I saw you mention class oriented vs object oriented
| elsewhere and I do think that captures the difference
| pretty well, thus why I agreed with your sentiments. The
| actor model basically _is_ object oriented programming in
| that context, whereas what most people mean winds up
| being class oriented.
|
| There's (at least) a third model IMO from CLOS & friends,
| such as Dylan, S4, and I'd argue Haskell Typeclasses. Not
| sure what I'd call it but it's the idea where you define
| data classes, generic functions, and then provide
| linkages between those data classes and the generic
| functions to give you methods on the state
| AnimalMuppet wrote:
| Kay was a visionary, but not every vision of his was the
| final word on reality. It's possible that the
| "mainstream" version of OO is _better_ than Kay 's.
| klibertp wrote:
| Yes, though I believe knowing about him and his vision
| can be helpful in understanding the evolution of OOP and
| its current mainstream incarnations. Basically, "those
| who don't know history are doomed to repeat it", "history
| repeats itself as a farce", and all that... :)
| weatherlight wrote:
| It's possible that I'm a coconut.
| astrange wrote:
| The mainstream version of OO is actually quite bad
| though. You can think of it as just being functions where
| the first parameter is to the left of the function name.
| It has a few more features than that of course, but all
| of these increase complexity and therefore bugs, and it
| doesn't have any features that reduce bugs.
|
| An example would be non-fragile ABIs (ObjC has this, C++
| doesn't) or typestate/built in state machines (so you
| can't call methods if the object is in an invalid state
| for them). There's encapsulation at least, it's a start.
| zokier wrote:
| It doesn't really matter if its better or worse. As a
| term OO means what people understand it to mean. Human
| languages (unlike programming ones) are descriptive, not
| prescriptive.
| dragonwriter wrote:
| > As a term OO means what people understand it to mean.
|
| I would suggest that if you asked people to define the
| term, Kay's definition would likely be the single most
| common (even after normalizing variations in wording).
|
| It would still only be a plurality, sure, but the
| remaining majority doesn't share a common understanding,
| either.
| inopinatus wrote:
| It's _possible_ that I'm a teapot.
| DonHopkins wrote:
| It's possible that weatherlight is a teapot and
| inopinatus is a coconut!
| klibertp wrote:
| Actually, Erlang and Elixir are one of the most OO languages
| out there. The original definition of OO, by Alan Kay, was to
| have a lot of miniature computers, with state and computation
| ability, called objects, which communicate by sending
| messages to each other. Yes, calling a method in Smalltalk or
| Self is called a "message send". Now, take a look at Erlangs
| processes - doesn't it all sound very similar?
|
| 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.
| hyperpape wrote:
| My understanding is that Alan Kay cannot reasonably claim
| to have "the original definition of OO", only "a very good
| early definition". Additionally, it seems like he's either
| been a bit inconsistent or unclear about the essential
| ideas at different times.
|
| One source: https://hillelwayne.com/post/alan-kay/
| smt1 wrote:
| Simula(67) definitely was the original
| concept/implementation of OO. Smalltalk, C++, Java, etc
| all derived different aspects of it.
|
| - Main abstraction are classes (of objects).
|
| - Concept of self-initializing data/procedure objects
| Internal ("concrete") view of an object vs an external
| ("abstract")
|
| - Differentiated between object instances and the class
|
| - Class/subclass facility made it possible to define
| generalized object classes, which could be specialized by
| defining subclasses containing additional declared
| properties
|
| - Different subclasses could contain different virtual
| procedure declarations
|
| - Domain specific language dialects
| astrange wrote:
| Note Java is actually explicitly based on Objective-C,
| not on Simula or C++. They just removed most of the
| Smalltalk bits to make it faster and less scary-looking
| to C++ programmers.
| frankpf wrote:
| Hey smt1, looks like your account is dead and has been
| for a while.
| weatherlight wrote:
| it's clearly not dead.
| klibertp wrote:
| Actually, it might be. The comment was dead, I vouched
| for it, so it appeared.
| keithnz wrote:
| Alan has commented a number of times on a number of
| threads on HN on this....
|
| you can find his various comments here:
| https://news.ycombinator.com/threads?id=alankay
| hyperpape wrote:
| I've seen his account. I'm not sure I've read every
| comment, however. Is there one in particular that you
| think says something not covered in the two pieces I've
| linked in this thread?
| inopinatus wrote:
| Alan Kay provided the first definition of OOP. That is
| not the same as using the word "object" for the first
| time.
|
| As that article clearly shows, at least once you're past
| its clickbait title, Kay acknowledges his influences.
| That isn't a lack of clarity, it's an awareness that we
| all build on the effort of our peers and predecessors,
| that ideas do not spring forth fully formed from the
| void.
|
| This does not discredit anyone's work, far from it;
| context is an aid to understanding.
| hyperpape wrote:
| I could probably have been more specific.
|
| What I mean by lack of clarity is that Kay in 1998 thinks
| that messaging is the key idea, and suggests that was
| always the key idea. But none of the historical sources
| I've seen suggest he was clearly and consistently saying
| that in the 70s.
|
| That's really a minor point, it does nothing to
| invalidate Kay's contributions. You can design a system
| that has value, and only later find the best way to talk
| about its value. That happens constantly: people create
| something and then end up saying "in hindsight, I got X
| right and Y wrong" or "back then, I knew this worked, but
| it was only later that I knew why".
|
| It doesn't disprove the idea that focusing on messaging
| gives you the best version of OOP. It doesn't even
| disprove that "real" OOP is about messaging (though I
| think that argument has a high bar to clear--most terms
| are messy).
|
| What it does disprove is that messaging has a right to be
| the core of OOP because Alan Kay defined it that way back
| in the 70s.
|
| P.S. Maybe the title isn't ideal, but Hillel was
| literally reacting to someone who said Alan Kay invented
| objects: https://lobste.rs/s/8yohqt/alan_kay_oo_programmi
| ng#c_5xo7on. Actually, that might be a better source than
| what I linked to, because it marshals more evidence that
| Kay was not consistently saying "messaging is what
| matters" during the 70s. He may have believed it, he may
| have said it sometimes, but he wasn't consistent about
| it.
|
| Again, that's not a criticism of Kay, except the super
| mild one that his 90s/2000s memory of what he said 20
| years earlier wasn't perfect. But I forget shit I said
| last week, so I'm not gonna judge.
| klibertp wrote:
| Good read, thanks. I knew about Simula, but never
| analysed it deep enough to know how much of an effect it
| had on early Smalltalk.
|
| Still, the definition Kay gave as late as in 1998
| resonates with me the most:
|
| _OOP to me means only messaging, local retention and
| protection and hiding of state-process, and extreme late-
| binding of all things._
|
| And this is almost exactly what Erlang implements: just
| add "asynchronous" before "messaging" and it fits
| perfectly.
| bitwize wrote:
| "Object oriented as Alan Kay intended" has been a strapline
| of Erlang proponents for some time. I think even Joe
| Armstrong said it once. And I seem to recall Kay speaking
| approvingly of the quip, and of Erlang itself.
| hedora wrote:
| Also, C++ has solved many of the issues mentioned in the
| article (especially post C++11).
|
| It has not solved the "minimum set of primitives that make OO
| tenable" problem.
| Kranar wrote:
| Which problems from the article are you referring to?
|
| 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.
| AnimalMuppet wrote:
| I have literally never seen someone program C++ that way. I
| mean, it's probably true that some people somewhere are
| doing that. But saying "C++ developers rightfully avoid
| using its OO functionality as much as possible" seems like
| a complete distortion of the actual situation.
| Kranar wrote:
| Those some people would be the authors of the standard
| library who use type erasure for std::function,
| std::string_view, std::span, std::any, std::ranges,
| std::fmt, the new class of pmr memory allocators use type
| erasure, the upcoming coroutines are built on type
| erasure.
|
| Common C++ libraries like boost have an entire library
| dedicated to type erasure:
|
| https://www.boost.org/doc/libs/1_75_0/doc/html/boost_type
| era...
|
| Facebook's Folly library also provides type erasure
| functionality:
|
| https://github.com/facebook/folly/blob/master/folly/docs/
| Pol...
|
| Google's Abseil is full of type erasure:
|
| https://abseil.io/
|
| Here is Adobe's C++ library for type erasure:
|
| https://stlab.adobe.com/group__poly__related.html
|
| And here's a talk by Sean Parent about how Adobe uses
| type erasure to emulate runtime polymorphism without
| using C++'s native OO features:
|
| https://www.youtube.com/watch?v=QGcVXgEVMJg
|
| I suppose these are just some people somewhere...
| AnimalMuppet wrote:
| A Turing Machine is the minimum (probably) set of primitives
| that make computation possible. That doesn't mean we want to
| program that way. I'm not sure we want to do OO programming
| in the minimum set of primitives that make OO possible,
| either.
| FpUser wrote:
| >"Object-oriented programming is out of fashion now"
|
| 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.
| wvenable wrote:
| Complaining about OOP is like complaining about vaccines. The
| positive results of OOP are so ubiquitous that it's invisible
| to people. Instead the few negative reactions get all the
| focus.
| jstimpfle wrote:
| Depends on what you mean by OOP. If you mean the concept of
| handles, messages, and fault isolation, then OOP is the right
| way to structure many solutions.
|
| 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).
| astrange wrote:
| > This kind of OOP strongly encourages componentization at
| the smallest level, much much more than is beneficial from
| an organization standpoint (subsystems).
|
| 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.
| wvenable wrote:
| > then it's just a syntax equivalent of training wheels.
|
| 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.
| astrange wrote:
| Examples of alternative models to imperative OOP that
| have languages:
|
| https://en.wikipedia.org/wiki/Entity_component_system
| (game-style C++)
|
| https://en.wikipedia.org/wiki/AoS_and_SoA (Jai)
|
| https://en.wikipedia.org/wiki/Relational_model (SQL)
|
| https://en.wikipedia.org/wiki/Logic_programming (Prolog
| and Mercury)
|
| The goto critique of OOP used to be http://steve-
| yegge.blogspot.com/2006/03/execution-in-kingdom... (as
| seen in the article) but I think OO languages have
| managed to steal enough good ideas from functional ones
| by now that it doesn't apply so much.
| wvenable wrote:
| OOP is several orders of magnitude more successful than
| all these alternatives combined. As I said, it's so
| successful that its success is invisible. All that is
| ever talked about are the tiny percentage of exceptions
| where OOP doesn't work well like, for example, where
| entity-component might be more appropriate. Those tiny
| exceptions get way more attention.
|
| Being contrarian to the massive success of OOP is great
| way to get upvotes but it doesn't reflect reality. The
| success of OOP is so huge that it's boring.
| FpUser wrote:
| >"If you're talking about classes, methods, inheritance,
| and so on, then it's just a syntax equivalent of training
| wheels."
|
| Everything above microcode is. Abstractions / paradigms
| when applied adequately help to see the forest beyond the
| trees.
| jstimpfle wrote:
| I've never thought about it this way. To me it's more
| like, many different paradigms and cleverness _are_ the
| forest and as well the trees.
|
| When I see an object-oriented codebase that makes me jump
| between 5 or more files to understand the simplest
| codepaths (because inheritance), and that is not
| browseable without a properly setup IDE and jump-to
| support (because of heavy namespacing and non-telling
| method names), it makes my shy away.
|
| On the other hand for example, larger open source
| projects written in C are often really easy to read.
| Local ("coherent") code paths, fully qualified function
| names in calls, no extra foo that gets executed
| implicitly on data data declaration or cleanup. Nothing
| hidden, every line just works to solve the actual
| problem.
| FpUser wrote:
| Many programmers are in general very opinionated about their
| tools. I have no idea why. I do not get all that excited
| about ways of doing things. To me they are just tools.
| dw-im-here wrote:
| I think this is a well written post, but I'm incredibly puzzled
| at eschewing scalas syntax for type parameters and instead having
| parentheses do double duty. Other than freeing up brackets for
| indexing what is gained from this? Other than that I actually
| think this is a language that deserves to exist so the ideas can
| be tried out in practice, if nothing else to influence the
| current OO languages to be better.
| raspasov wrote:
| An Object-Oriented language for the '20s: don't do it.
| seanalltogether wrote:
| Am I wrong or is OP describing most of what swift currently
| offers. Optionals, extension where syntax, Result<> monads,
| exceptions collapsed to a single line with try? syntax, safe
| casting with as?, etc...
| brobdingnagians wrote:
| Same with kotlin. It appears that most of the advances are
| really really great and emerged in a wave in the '10s. We are a
| decade ahead of the curve ;) I think doing away with checked
| exceptions was a definite benefit in that direction too..
|
| 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.
| virtualwhys wrote:
| > Having higher kinded types and type classes like Haskell
| would be nice though.
|
| 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...
| hedora wrote:
| Scala uses type erasure to preserve compatibility with the
| JVM. No thanks. I want the compiler to statically verify my
| types, and then optimize based on that.
|
| 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.
| pjmlp wrote:
| Or OCaml, Eiffel, Sather,...
| mikewarot wrote:
| It seems to me that you could do almost everything you want
| (except for the immutable thing) in Free Pascal.
|
| 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?
| mmazing wrote:
| But it's not a hot sexy * new * silver bullet for all of your
| problems.
| elteto wrote:
| Have to disagree on exceptions. The return type is part of the
| function signature, it's part of its contract. If a function
| can fail I want to see it right there on the return type and be
| forced to handle it on the spot.
|
| 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.
| layer8 wrote:
| > If a function can fail I want to see it right there on the
| return type and be forced to handle it on the spot.
|
| 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.
| TeMPOraL wrote:
| > _Exceptions are an invisible, out-of-band mechanism that
| will crash your program because you forgot yet another
| try...catch._
|
| 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.)
| elteto wrote:
| If you squint hard enough checked exceptions are sum types
| through other means: try foo()
| catch ExceptionX ...handle X... catch
| ExceptionY ...handle Y... catch ExceptionZ
| ...handle Z...
|
| compare that to (borrowing some Rust-ish syntax):
| match foo() { ExceptionX => ...handle X...
| ExceptionY => ...handle Y... ExceptionZ =>
| ...handle Z... }
|
| with the added benefits that sum types can be used outside
| of error handling.
| TeMPOraL wrote:
| Yep, they are essentially sum types, but out-of-band,
| which is important.
|
| In particular, you can have: def quux():
| xkcd = frob(foo(), zomg()) ...lots of code...
| def bar(): try quux() catch
| ExceptionX ...handle X... def baz():
| try bar() catch ExceptionY
| ...handle Y...
|
| I assume pattern-matching can be partial without explicit
| default value, but (in the languages I've dealt with) it
| gets tricky with expressions. For instance, in C++:
| auto a = foo(); auto b = bar(a, quux());
| return frob(xkcd(a), b);
|
| if foo(), bar() and quux() can throw exceptions, and you
| wanted to replace them with sum types - say,
| std::variant<F, Err1, Err2, Err3>, or for better semantic
| separation (more on this below), tl::expected<F,
| std::variant<Err1, Err2, Err3>> - you'll going to pray
| you have enough "monadic" methods in those classes
| (spoiler alert: you don't, as of C++17/current
| tl::expected) to rescue the spaghetti code you'll have to
| write to model this flow.
|
| Recently, I've been dealing with a C++ codebase that uses
| tl::expected in lieu of exceptions, and while the "main
| flow" becomes more readable - return doThis(x,
| y).and_then(do_that).map(sth).map_error(logErrors); - the
| side effect is that the codebase is _littered_ with
| trivial functions and closures to make that interface
| work. It 's much less readable than a bunch of catch
| blocks every now and then. Maybe it's a limitation of
| C++? I feel like a syntax for partial application would
| remove half of the boilerplate I have to write when
| chaining tl::expected-returning functions.
|
| On semantic separation, I alluded to using a "nested" sum
| type like (Result1 + Result2 + ...) + (Err1 + Err2 + ...)
| instead of Result1 + Result2 + ... + Err1 + Err2 + ... -
| while mathematically equivalent, they aren't
| syntactically equivalent in code. Grouping possible
| return value types separately from possible error type
| values improves reasoning about code - the groups tend to
| change independently from function to function. And if
| you squint, this is what exceptions give you: they
| separate the error sum type from return sum type, and
| give the former a completely different control path. This
| means you can think about them separately, and don't have
| to force-fit the success path into shape that satisfies
| the needs of the error path.
| astrange wrote:
| Typed checked exceptions are a bad thing because they
| require libraries to redeclare things that are its
| implementation details, in particular the API of other
| libraries they use internally.
|
| See Swift's error handling design: https://github.com/apple
| /swift/blob/main/docs/ErrorHandlingR...
|
| (Also, exceptions/nonlocal returns are a pain because the
| invisible control flow is hard to reason about and hard to
| implement in a compiler.)
| TeMPOraL wrote:
| > _they require libraries to redeclare things that are
| its implementation details_
|
| Exceptions that can fly out of a library are just as much
| a part of its public interface as the error types it uses
| for return values. Adding a checked exception Exception
| is equivalent to turning your Foo return value into
| Result<Foo> - the code will no longer work if it doesn't
| handle it.
|
| > _in particular the API of other libraries they use
| internally._
|
| The library can make a choice between handling the
| internal exceptions at the interface boundary, rewrapping
| them, or exposing them as a part of its public interface.
| That's no different than a library considering returning
| a type that's defined in another library it uses
| internally.
|
| > _(Also, exceptions /nonlocal returns are a pain because
| the invisible control flow is hard to reason about and
| hard to implement in a compiler.)_
|
| I'm not a compiler guy so I'm not going to dispute the
| issues with implementing exceptions, but on the
| readability flow, I strongly disagree. There's nothing
| hard in exception-based control flow. The rule is simple:
| a given function either succeeds, returning what it
| declares, or fails - a failure cause the caller to enter
| the appropriate exception handling block if it has one,
| or exit immediately, propagating the exception up the
| stack.
| mikewarot wrote:
| So how do you want to handle something like sqrt(-1.0) for
| real numbers? A runtime exception seems the sane way to do it
| for me. Are you suggesting that sqrt(-1.0) or any other
| invalid value should return a sentinel value like -999, or
| should return NUL instead of a real?
|
| Any way of dealing with errors has to be out of band, we just
| disagree on the mechanism.
| qsort wrote:
| sqrt(-1.0) is NaN, it's typically used in numerical code
| where you can't stop and check for exceptions because
| performance is key.
|
| But just for the sake of argument, the type of 'sqrt'
| isn't: sqrt : R -> R
|
| it is: sqrt : R+ -> R+
|
| So you have two options here. You can have sqrt return
| "Either Error Double" and force the programmer to deal with
| the possibility of an error (Rust does this) or you define
| a "nonnegative real" type that the compiler can guarantee
| will be an acceptable input for sqrt. The latter is much
| harder to do.
|
| But either way no, you only need an "out of band" mechanism
| when the type system isn't strong enough or isn't being
| properly leveraged.
| mikewarot wrote:
| I used square root because it is commonly known that
| negative numbers are a problem for them. What about
| tangent(x) which periodically blows up? A type system
| can't fix that.
|
| In my opinion it is far better to throw an exception that
| can be explicitly handled, rather than cause some math
| error at some random point later because NaN or some
| infinity snuck by as a number.
| qsort wrote:
| Math.tan will never throw exceptions because, for the
| exact same reason as above, it won't check its input.
|
| But again for the sake of argument, the return type of
| tan() could be "Either<Error, double>".
| mikewarot wrote:
| I guess I would get used to that, but it seems broken to
| me. The way to deal with that is out of band, checking
| for the type to change to "Error"
|
| In Pascal tan(pi/2) or tan(3pi/2) causes an overflow. The
| way to deal with that is out of band, to handle an
| exception of type E_Overflow.
| qsort wrote:
| Again no, it isn't. You are well entitled to believe
| Pascal is the best thing since sliced bread, but the two
| ideas are fundamentally different.
|
| There is no type change. The function "tan" would return
| a value of type "Either<Error, double>". An expression
| such as: double x = Math.tan(Math.PI /
| 8)
|
| would fail the compile-time type check, because the type
| of the variable "x" is "double" and the type of the
| expression "Math.tan(Math.PI / 8)" is "Either<Error,
| double>".
|
| It would be the responsibility of the caller to prove to
| the type system that the error has been handled, with
| either a built-in language facility a la ML, or with ad-
| hoc code such as a Bohm-Berarducci construction.
|
| Let's just agree to disagree.
| mikewarot wrote:
| I _know_ Pascal isn 't perfect, and lots has been learned
| since then... this is genuine confusion on my part. So
| tan in your system returns a variant type (could be
| Error, could be Double), right? (I didn't get that)
|
| Compilers can find all sorts of things they couldn't in
| the past, which is amazing.
|
| What happens at run-time if an overflow happens?
|
| again... I'm sorry if I didn't express myself clearly
| enough. I'm coming at this from a Pascal programmers
| perspective... I've missed the past 20 years of compiler
| improvements.
| SAI_Peregrinus wrote:
| In Rust in particular (and many other languages) tan
| operates on IEEE 754 double-precision floating points
| ("double" for Free Pascal, "f64" for Rust). These can
| contain infinity and NAN values, not just numbers. So if
| tan blows up, you get NAN back. It's your job as the
| programmer to check that the result wasn't NAN (or INF)
| before proceeding. "Double" is already such a variant
| type. So you have to check your results which can return
| an error variant for errors, just like you already do
| with floating-point values.
| setr wrote:
| Rust chooses to panic, I believe, but you could always
| have applied the same Error strategy -- it'd just be
| really annoying. The stdlib also provides a bunch of
| methods to pick-your-strategy for overflow behavior, but
| you need to use them instead of the +/* operation
| tene wrote:
| Like you said, this is a variant type that represents
| either a value, or an error. Here's the definition of
| Rust's Result type: pub enum Result<T, E>
| { Ok(T), Err(E), }
|
| I threw together some examples of how you can use a
| Result value, although I used arcsin instead of tan as
| the out-of-domain values are much easier to specify.
|
| https://play.rust-
| lang.org/?version=stable&mode=debug&editio...
|
| I'm not entirely sure what you're asking about regarding
| runtime behaviour, but here's two guesses.
|
| As a user of a function that returns a Result type, at
| runtime the function returns a result that has a flag and
| a payload, where the flag specifies whether the payload
| is your value or an error. The only ways to get a value
| out of the result are either to check the flag and handle
| both possibilities, or to check it and crash the program
| if it's an error.
|
| As someone writing a function that returns a result type,
| you write whatever logic you need to determine whether
| you've successfully produced a value, or which error
| you've produced, and then you either
| return(Ok(my_value)); or return(Err(my_error));. If
| you're doing floating point math, this might be checking
| an overflow flag, or looking at your inputs, or checking
| for NaN after operating, or whatever makes sense for your
| task.
|
| Using a variant/product/discriminated union like this is
| orthogonal to the details of floating point operations at
| runtime. What it lets you do is have the compiler enforce
| that users of your function must check for errors, as
| it's a type error to assign a Result<f64,E> to a variable
| of type f64.
|
| I hope that helps! Do you have any more questions about
| this, or things I didn't address, or topics where you'd
| like more explanation or examples?
| hedora wrote:
| Sqrt(-1.0) should return NaN or raise a signal. Let the
| programmer choose. Throwing an exception doesn't help
| anyone.
| hedora wrote:
| There have been many studies of exception handling
| correctness. They've shown that no one can correctly write
| exception handlers.
|
| 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.
| qsort wrote:
| I'm not the author, but:
|
| > 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.
| mikewarot wrote:
| >Mutating variables leads to all kinds of invisibile problems
| and makes the code a lot harder to reason on.
|
| 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.
| qsort wrote:
| Sure, but passing by value isn't always realistic. If you
| have a hashtable with a million entries, you can't copy it
| just to update a 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.
| mikewarot wrote:
| That's a weird corner case... I wouldn't expect the
| compiler to keep me from shooting myself in the foot like
| that. I'm surprised that some people do expect it.
| edjrage wrote:
| Ever seen any of the top 5 functional languages?
|
| And "weird corner case"? Really?
| qsort wrote:
| What do you honestly expect me to answer? That's kind of
| a moot point: the entire reason we have type systems in
| the first place is that they are partial, machine-
| checkable proofs of correctness. The entire discussion is
| how to strike the best power/practicality balance while
| keeping the thing decidable. Keeping you from shooting
| yourself in the foot is exactly the type system's job.
|
| Why do you even bother with static types if you seriously
| believe that?
| mikewarot wrote:
| I believe in type systems as a way of knowing how data is
| encoded in memory. I don't expect them to somehow prevent
| me from writing that memory location more than once.
| badsectoracula wrote:
| Have you ever used procedure
| Foo(constref Something: SomeType);
|
| instead of procedure Foo(var Something:
| SomeType);
|
| in Free Pascal? Both will pass the same stuff in memory
| (stack), but one has different semantics.
|
| Similarly const NiceFeature = $01;
| NeatFeature = $02; GoodFeature = $04; var
| Features: Byte;
|
| and {$packset 1} type Feature =
| (Nice, Neat, Good); var Features: set of Feature;
|
| would be stored the same way in memory but again,
| different semantics with the compiler helping you in the
| second case to avoid logic bugs (e.g. setting Features to
| an invalid value).
|
| These are cases where Free Pascal helps you from shooting
| yourself in the foot.
| mikewarot wrote:
| I've never used ConstRef, nor $packset
|
| I can see the value of making sure you don't end up with
| invalid values.
| astrange wrote:
| > Sure, but passing by value isn't always realistic. If
| you have a hashtable with a million entries, you can't
| copy it just to update a value.
|
| What else are you going to do? If you want to insert a
| value but also have access to the hashtable without the
| change, then you need to copy it. (Or use a data
| structure with history tracking, like RCU or finger
| trees.)
|
| If you don't need the old value, then you can optimize
| out the copy - this isn't too hard.
| rini17 wrote:
| It seems to me more a syntactic sugar than really tackling the
| main problem with OO: state that tends to get scattered into many
| interdependent objects, hard to manage, hard to refactor.
| mikewarot wrote:
| >state that tends to get scattered into many interdependent
| objects, hard to manage, hard to refactor.
|
| Care to elaborate what you mean by that?
| snidane wrote:
| I'll bite.
|
| 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.
| throwaway4good wrote:
| "First, a few obvious, non-negotiable things any new OO language
| should have: ..."
|
| Well, that's like your opinion, man ...
| throwaway4good wrote:
| Personally I would drop the static type system and reinvent
| Self.
| agumonkey wrote:
| time to dust it off and sell it as new ?
| throwaway4good wrote:
| Yeah - I think the kids are ready for that.
| agumonkey wrote:
| rebrand it `selfie`
| protomyth wrote:
| ...and steal the Soups from NewtonScript on the way.
| mucholove wrote:
| What are the soups?
|
| Both Google and DuckDuckGo give me no results so an example
| would be appreciated.
|
| (Hope I'm not caught up in a prototype joke :)
| [deleted]
| lionsdan wrote:
| "Data in Newton is stored in object-oriented databases
| known as soups."
|
| https://en.wikipedia.org/wiki/Apple_Newton#Data_storage
| protomyth wrote:
| Chapter 11-7 of https://www.newted.org/download/manuals/N
| ewtonProgrammerGuid... using the Self influenced language
| NewtonScript https://www.newted.org/download/manuals/Newt
| onScriptProgramL...
| astrange wrote:
| My impression of systems like soups/OpenDoc/component
| systems/etc is that they're doomed to failure because
| they don't respect Conway's law. The app model works
| because each app has its own development team and teams
| aren't directly editing other teams' data.
|
| They don't have the proper expertise and will break
| something, because it's not possible to enforce data
| consistency well enough. (Apple's modern OO database is
| called CoreData, has a pedigree from WebObjects, and its
| consistency features are pretty hard to use in practice.)
| stitched2gethr wrote:
| After working in Python and Ruby I don't think I could ever
| choose a dynamically typed language for a project with more
| than a few thousand lines. There's just a lot of safety, and
| documentation, you get cheaply when using static types.
| fiddlerwoaroof wrote:
| There's languages like Python and Ruby and then there's
| languages like Smalltalk, Common Lisp, Self or Clojure: if
| dev-time is runtime, you miss compile-time checks a lot
| less
| weatherlight wrote:
| Ruby has conventions and a big testing culture which helps
| mitigate a lot of those issues. I also work with Typescript
| and I see whole classes of bugs that could be avoided if
| everyone just stuck with conventions and best practices.
|
| 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}
| _void wrote:
| Lol, can't ignore the Lebowski reference :) good one! I
| actually think it is very subjective list :)
| brabel wrote:
| That's the problem with language design: too much opinion, too
| little decision making based on actual data.
|
| 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.
| renox wrote:
| Bah MI was given a bad reputation by Java fans but their
| arguments weren't very persuasive.. The diamond issue? As
| shown in the article, it isn't really an issue, Eiffel
| actually use this solution.
| hedora wrote:
| If you want your language to be called "Object Oriented", it
| needs to support implementation inheritance. (Encapsulation
| and interfaces predate OO).
|
| My personal take is that implementation inheritance is a bad
| idea, and, by extension, Object Oriented programming is a
| dead end.
| andrewflnr wrote:
| The author is explicitly using Kotlin as one of their
| inspirations, and IMO has a pretty reasonable approach to
| multiple inheritance (and I suspect they would agree that
| extension methods are preferred when applicable). What
| problems does MI cause that aren't fixed by OP's solution to
| diamond inheritance?
| sriku wrote:
| Erlang is perhaps the best OO lang out there that doesn't look
| like one but it's principles are the heart of OO.
|
| PS: I admit I lost the author at "scala is my favourite language"
| and so am biased.
| hderms wrote:
| Scala can be one of the most productive, elegant, reasonably
| performant languages to work in or it can be the opposite of
| all those. When done right it's amazing but the language is
| complex enough to permit some questionable choices
| blackrock wrote:
| Life is too short to be forever tied to Java. This will be
| Scala's downfall.
|
| Elixir looks like the more promising approach. I love its
| pipe forwarding operator.
| zokier wrote:
| scala-native exists?
| dw-im-here wrote:
| Life is too short to reimplement libraries and have them
| battle tested. As a scala dev I find this a very weak
| argument
| weatherlight wrote:
| Except like the BEAM/erlangOTP(and by extension Elixir)
| is older than the JVM, and arguably as battle tested, if
| not more....
| pjmlp wrote:
| Life is to short to reinvent the JVM and .NET runtimes, and
| IDEs, poorly on FOSS budgets.
| alkonaut wrote:
| I love how rust does interfaces (traits) and allows implementing
| _outside_ the type. It feels much more natural to create a
| subsystem on the side without tangling it into other subsystems.
| E.g. to create a separate rendering subsystem for a game entity
| you simply 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).
| [deleted]
| lukevp wrote:
| I wonder if you could implement this all within a linter on top
| of Rust or some other language? Some of your asks already exist
| at that level (for example, TypeScript autocomplete in VS Code
| knows which enums you haven't checked in a switch, and it also
| knows if a property has been verified that you've narrowed via
| a type union, so perhaps you could extend this to enforce
| coverage of switch statements?
| bombela wrote:
| The Rust compiler already enforces that you match all
| variants of an enum. And Rust Enum is basically a C union
| with an integer tag.
| jayd16 wrote:
| I think you misunderstand the ECS pattern. You want:
| 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.
| alkonaut wrote:
| Thats exactly how I understand an EC system but my issue with
| it is the overly soft coupling with just ids.
| jayd16 wrote:
| I guess you want to know that every MyEntity can be drawn
| and can move so you don't have to try-get the component.
|
| 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.
| magicalhippo wrote:
| I've only briefly looked at Rust. How would you handle your
| Drawable if it needs to store some extra data?
|
| 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.
| coolreader18 wrote:
| Drawable is just a trait, a definition of methods that can be
| abstracted over. If you wanted to store data, you'd do it in
| the type that Drawable is actually being implemented for,
| i.e. MyEntity. 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");
| magicalhippo wrote:
| Thanks, that is very clear.
|
| 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.
| smt1 wrote:
| Here is some nuance with Rust's OOP relative to
| java/go/c++: https://stevedonovan.github.io/rust-gentle-
| intro/object-orie...
|
| If you know haskell, Rust's traits are very similar to
| type classes, except it also has c++-like generics
| (templates) and is primarily expression-based like ocaml.
|
| traits vs haskell type classes:
|
| trait -> class struct -> data instance -> impl
| hderms wrote:
| Rust has BufWriter/BufReader and I'm not an expert but I
| think you'd just view it as extension through composition
| magicalhippo wrote:
| That does indeed seem[1] to do regular, naive as per OP,
| composition.
|
| 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...
| branko_d wrote:
| > stack and heap as with C# structs and classes
|
| 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.
| alkonaut wrote:
| I'm aware of the differences between structs and classes,
| boxing etc. (.net dev since '02), . My point is "class" can
| some times be a faceless "value" with no identity, and some
| times have an identity. The "records" feature is a way of
| addressing this difference, but like everything else it's
| tacked on a bit late. A value class, like a value type
| (struct, enum), should/could be immutable, have structural
| equality etc.
|
| 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.
| brokencode wrote:
| I think this is really cool, and it elegantly brings together
| some pretty advanced features from FP and OO into one purely OO
| language. The only thing I'd like to see more about is how
| immutability would be handled, because that is harder than it
| sounds in OO.
|
| 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.
| maxekman wrote:
| How are Optional named arguments and Generics obvious choices for
| a new OO language? Good to have, sure, but obvious?
| [deleted]
| Traubenfuchs wrote:
| Swift? Typescript? Both fairly new and object oriented. Go?
| Somewhat object oriented.
| [deleted]
| ibraheemdev wrote:
| In what way is Go object oriented?
| gher-shyu3i wrote:
| It has everything except explicit inheritance, and embedding
| covers some aspects of inheritance.
| howinteresting wrote:
| One of the fundamental issues with OO subtyping continues to be
| how variance works. I wish the author spent more time talking
| about how they envision variance.
|
| 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.
| ar-nelson wrote:
| I didn't really think about variance while writing the
| examples; I assumed it would just work the way it does in Scala
| and Kotlin (explicit variance annotations). I do wonder if
| that's necessary, though--in theory, the compiler could infer
| variance from how you use the type parameters, which is in a
| similar spirit to the rest of the language.
|
| What do you think are the major problems with variance in OO?
| howinteresting wrote:
| Explicit variance, especially for types, is just really
| confusing. Implicit variance has the risk of breaking APIs or
| overpromising a particular variance without developers
| realizing.
|
| 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.)
| Hermel wrote:
| Contrary to popular belief, OO is not primarily about
| inheritance. That's one of its distinct features. But
| experienced software engineers tend to prefer composition over
| inheritance. OO is about encapsulation and about putting
| conventionally functions close to the data it operates on.
| branko_d wrote:
| I think this quote from Alan Kay is rather illuminating:
|
| _" 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."_
|
| https://wiki.c2.com/?AlanKayOnMessaging
| throw0101a wrote:
| > _OO is about encapsulation and about putting conventionally
| functions close to the data it operates on._
|
| 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/465
| 9...
|
| Though it's recognized that Simula invented objects:
|
| * https://www.hillelwayne.com/post/alan-kay/
| agumonkey wrote:
| One think you may get (but I may speculate here) from his
| talk is that he had a way more abstract / mathematical view
| of systems and programming. I think he didn't see OO as
| isolating tasteful procedure more than providing interfaces
| and concepts to compose concept cleanly.
| agumonkey wrote:
| Is it funny or sad that after 30+ years, the industry is not
| clear on what is or is not OO.
|
| At a gig in my college cs lab, teachers talked about OO, 3
| teachers, no answer the same.
| wvenable wrote:
| Inheritance is very useful in OO but it's all well-tread
| ground now. The things that have is-a relationships are all
| well defined and part of libraries and frameworks that have
| been around for decades.
|
| 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.
| SomeHacker44 wrote:
| Which begs the question: why use OO at all if you prefer
| composition? I personally am "over" OO, even though I have
| been doing OO professionally for decades.
| invisiblerobot wrote:
| Exactly. Polymorphism is the killer feature and it's
| expressed more simply in functional languages like Clojure.
| mikewarot wrote:
| >why use OO at all if you prefer composition?
|
| 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.
| blowski wrote:
| Over the last 30 years, object oriented languages got more
| attention, more funding, more companies using them, more
| jobs available, more developers, more education resources,
| more libraries.
|
| 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.
| howinteresting wrote:
| Ehhh, inheritance hiearchies are the well-worn grooves of OO,
| the paradigm encourages them even if it doesn't strictly
| require them. I don't buy this argument.
|
| Typeclasses are a much better way to do polymorphism.
| michaelbrave wrote:
| what the author is proposing sure sounds an awful lot like Swift
| to me.
| mangopi wrote:
| You basically want Go, but you haven't learned it yet.
| vanderZwan wrote:
| Regarding the "Kingdom of Nouns", I always thought Lobster[0][1]
| had a really cute idea: x(a, b, c) and a.x(b,c) are equivalent.
| Don't know if that is original to the language, but it's where I
| saw it first. It only really makes sense with the other features
| of the language, though.
|
| (Also, I just discovered that it now targets WASM. Maybe I should
| give it another go!)
|
| [0] http://strlen.com/lobster/
|
| [1] https://github.com/aardappel/lobster
| Snarwin wrote:
| In D, this is called "uniform function call syntax." [1] It's
| really handy, not just for OOP but also for pipeline-style
| functional programming, since it lets you compose functions
| from left to right.
|
| [1] https://tour.dlang.org/tour/en/gems/uniform-function-call-
| sy...
| ar-nelson wrote:
| Author here: unified function call syntax is awesome but sadly
| out of scope for what I was trying to do with this language. I
| mention this in the "Class-based discoverability" section: for
| the sake of uniformity and IDE discoverability (arguably Java's
| killer feature that its successors have diluted), I'd want
| every value and function to belong to a class, which means
| every nonlocal method call should come after a dot.
| vanderZwan wrote:
| This already assumes that unified call syntax would
| inherently get in the way of that - do you have any reason to
| believe that?
|
| I mean, Lobster has classes, or at least something that it
| calls classes:
|
| https://aardappel.github.io/lobster/language_reference.html#.
| ..
| jayd16 wrote:
| Would be kind of neat if the language supported returning
| tuples or anonymous types. ie x(a,b,c) is equivalent to
| {a,b}.x(c)
| tekknolagi wrote:
| This is called unified function call syntax
| vanderZwan wrote:
| You know, now that you mention it, I think every time I
| mention this feature of Lobster someone explains this to me,
| and for some weird reason I keep forgetting. Thanks for
| enlightening me again, hopefully it'll stick this time!
| klibertp wrote:
| Nim also has this. You can also leave out the parens from
| calls, which adds to the syntax flexibility.
| thesuperbigfrog wrote:
| Ada supports that syntax for tagged types:
|
| https://learn.adacore.com/courses/intro-to-ada/chapters/obje...
| brianberns wrote:
| F# is functional-first, but also fully supports OO. It's a good
| balance for the 20's, or any other decade.
| dfgdghdf wrote:
| Agreed. F# lacks the HKT that the OP calls for, but it is
| possible to write plenty of pragmatic functional code without
| them. They might land soon though.
| iso8859-1 wrote:
| What's your source for F# getting HKT's soon?
| dfgdghdf wrote:
| C# has a HKT proposal now. If C# gets it, F# will soon
| follow.
| dunefox wrote:
| It seems like a great language; I'm going to look into it this
| year - maybe for advent of code or some data science/ML stuff.
| For some annoyances there is F#+:
| https://fsprojects.github.io/FSharpPlus/
| gigatexal wrote:
| Ugh. Another reminder that high level OO languages and things
| make my head hurt.
| fit2rule wrote:
| All of this can be done in Lua.
| PaulHoule wrote:
| Bring back Turbo Pascal!
| dale_glass wrote:
| Can we get rid of 'sealed'? I really hate that keyword. Sometimes
| there's a good reason for it, sure, but sometimes it's used
| gratuitously, and then I have to work around it for no good
| reason.
|
| 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'.
| jasode wrote:
| _> Can we get rid of 'sealed'? I really hate that keyword.
| Sometimes there's a good reason for it, sure, but sometimes
| it's used gratuitously, and then I have to work around it for
| no good reason._
|
| 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.
| dale_glass wrote:
| Yes, I know there are good reasons to do it sometimes, but
| still don't get what about SqlDataReader benefits from it,
| other than "I don't know why would anyone would want to
| inherit from it".
|
| 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.
| [deleted]
| zackmorris wrote:
| I second removing sealed (and final) from all languages (except
| maybe for embedded/microcontroller work where implementation
| details matter).
|
| 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:
|
| https://martinfowler.com/bliki/Seal.html
| RedNifre wrote:
| You could do this with an extension method, no inheritance
| required.
| dale_glass wrote:
| Oh, that's very nice, and just the kind of functionality I've
| been wanting for some time.
|
| 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.
| Graffur wrote:
| why no nulls? and no exceptions? I am no language designer but as
| a user they seem ... usable.
| noir_lord wrote:
| If you have nulls (or nullable types) then you end up checking
| for them everywhere (in every codebase I've seen anyway -
| keeping them on the boundaries only works _if_ that 's
| practical and the language always does it for it's standard
| libraries etc).
|
| 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.
| skwirl wrote:
| >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.
| dkjaudyeqooe wrote:
| That's an argument for exceptions. If they shouldn't be
| handling it they shouldn't be catching it. If they're the
| highest level why didn't the other 6 layers handle it?
|
| 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?
| larzang wrote:
| So what's your proposed alternative that doesn't involve
| checking? Option-style wrappers enforce consistent checking
| and handling, but so do explicitly nullable type signatures.
| At some point you're going to need to know whether you have
| data or not, and that requires checks.
| TeMPOraL wrote:
| > _business logic shouldn 't be handling
| FileNotFoundException that was thrown 6 layers away_
|
| 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.
|
| Exception types follow the same rules as "regular" user-
| defined types: they're relevant in a subsection of a program,
| and shouldn't propagate below that subsection.
| orthoxerox wrote:
| Exceptions are not a part of your method signature, so every
| time you call something you don't know if you have to catch an
| exception or not.
|
| 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.
| pjmlp wrote:
| > Java tried to fix that
|
| 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).
| wvenable wrote:
| Assume all methods can throw exceptions. And if, by chance,
| you believe a method doesn't throw an exception today you
| have no idea if it's implementation will change tomorrow and
| it will then throw an exception.
|
| 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.
| brabel wrote:
| > ... but it's hard to convert an established ecosystem with
| idiosyncratic nullability rules.
|
| Dart has just done that, though admitedly, it did not have an
| ecosystem as large as C#'s.
| orthoxerox wrote:
| C# had to invent attributes to work around structs vs
| classes and around TryX methods where out variables are
| null or not based on the return value. And the issue of
| arrays of objects or objects inside structs hasn't been
| resolved yet.
| dkjaudyeqooe wrote:
| They live in a world where nothing ever goes wrong and nothing
| is ever missing.
|
| Sounds nice.
| dunefox wrote:
| It's almost as if nulls and Exceptions aren't optimal for
| modelling missing states and faulty logic.
| samatman wrote:
| From an ADT perspective, letting any type T be null is the same
| as saying everything is an Option<T>.
|
| 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.
| booleandilemma wrote:
| _Object-oriented programming is out of fashion now_
|
| What world does this person live in?
| [deleted]
| [deleted]
| alfiedotwtf wrote:
| Brain: haha why would they need a programming language for the
| 19OH THEY'RE TALKING ABOUT THIS DECADE
| TeMPOraL wrote:
| Hard disagree on exceptions. Passing sum types types along with
| every call is annoying - in a good codebase, probably upward of
| 50% of your code might be returning some kind of Result/Expect
| type. Exceptions can be done right, and there are solutions to
| the main objections (implicit, hard to recover from) in various
| languages - but somehow nobody seems to have managed to put all
| of them in the same language.
|
| 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.
| kidfrommars wrote:
| So more like an algebraic effect system than what most people
| think of as exceptions? I definitely think that's a promising
| area, but unfortunately it's currently still only really a
| thing in research experiments a-la Koka
| (https://www.microsoft.com/en-us/research/project/koka/).
| nesarkvechnep wrote:
| No mention of message passing? Shame.
| tomp wrote:
| Why would you want message passing in the language (as opposed
| to a library)?
| ar-nelson wrote:
| How does message passing work with a statically-typed language?
| (Genuine question, not criticism.) My understanding is that
| Smalltalk-style message passing's main advantage is that it
| allows handing or proxying unknown method calls, but unknown
| method calls are forbidden in a static type system.
| weatherlight wrote:
| Make objects Actors (as per the Actor model of computation).
| You don't call methods on objects, you pass a message to
| their mailboxes, Objects then choose how to process those
| messages. To do this though, messages would have to be
| immutable.
| transfire wrote:
| CLOS (https://lispcookbook.github.io/cl-cookbook/clos.html)
| wongarsu wrote:
| > But, if we made a new statically-typed OO language from scratch
| in 2021, something in the vein of Java or C#, taking everything
| we've learned from functional programming and a decade-plus of
| scathing OO criticism, could we fix this?
|
| 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.".
| pjmlp wrote:
| One of the best OO languages out there is Eiffel, value types,
| DbC, MI, generics, lambdas, non-nullable references, a very
| nice graphical editor, JIT for development and AOT toolchain
| that pings back into the C and C++ compilers of the host
| platform.
|
| Java and .NET are still catching up to what it offered in 2000.
|
| Sadly it never got a big name sponsor to push it.
| le-mark wrote:
| Ocaml also has a long standing claim to being an oo plus
| functional language for the future that hasn't seen a lot of
| uptake. It would have been heartening and reassuring for the
| author to explore more candidates than Scala.
| sfRattan wrote:
| The complaints I remember about ocaml the last time I
| checked it out were related to multithreading. No idea if
| that's changed. But I do see ocaml very often in use to
| write a compiler, especially for languages that aren't yet
| self-compiling, most recently when looking into Haxe.
| cptwunderlich wrote:
| Not yet, but it's a work in progress:
| https://discuss.ocaml.org/t/multicore-ocaml-
| february-2021/74...
| SloopJon wrote:
| > 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.
| Jtsummers wrote:
| You can download Eiffel Studio and use it for free. I'm not
| sure what restrictions there are or capabilities that may
| be removed. It's included in Homebrew (mac) but it's an
| older version. Macports seems to have the current version
| available. Not sure about various Linux distros.
|
| https://dev.eiffel.com/Main_Page
| ghosty141 wrote:
| The biggest problem for me is the age old ,,null".
|
| C# with monads would be great.
| iso8859-1 wrote:
| Lack of null and monads are kinda orthogonal. Yes, you can
| use monads to model "early return", but you can use them for
| so many other things. F# already offers computation
| expressions, which you can use for monads (but with no
| typing). But even Haskell provides no enforcement of the
| monad laws. Surely you must know about F#, so your comment
| makes little sense to me. If you want HKT's in F#, why not
| just say so?
| franknine wrote:
| Highly recommend Functional Programming in C#:
| https://www.manning.com/books/functional-programming-in-c-
| sh... If you want to learn how to do some functional in C#.
|
| 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.
| mdpopescu wrote:
| Something which I've used in the past (edited because I
| spotted some mistakes while re-reading the code [grin]):
| 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.
| ryanjshaw wrote:
| You can also use a library like LanguageExt. In practice
| I'm torn between how neat and logical it looks, and wasting
| 30min figuring out I used the wrong match() on a mixed
| sync/async scenario after a method signature changed and
| that's why my database context is disposed of randomly in
| the middle of operations.
| jayd16 wrote:
| You can set references to default to non-nullable now with
| compiler flags or some such thing.
| orthoxerox wrote:
| > 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").
|
| 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.
| astrange wrote:
| And Java is based on Objective-C, except without the good
| ideas like messages and with new bad ideas like checked
| exceptions.
| erik_seaberg wrote:
| The problem is that way too much of stdlib declares no
| checked exceptions, which forces any useful code to smuggle
| them out in unchecked ones. Generic types in particular
| need exception lists; I should be able to take a
| Function<T, R, X1, X2, ...> arg and imply that I throw
| whatever it throws.
|
| Though nowadays I think maybe monadic Result<T, X> is the
| way to go.
| vips7L wrote:
| IMO Java has learned from the mistakes of C# as well. See
| Task<T> vs Loom's virtual threads. Brian Goetz (the Java
| language architect) himself even said they're waiting to see
| how C#'s nullable types plays out before considering them in
| Java.
| jayd16 wrote:
| Async/await and implicit thread scheduling solve different
| problems. Loom is more about catching up to Go and Kotlin,
| no?
| anoncake wrote:
| Not Kotlin, AFAIK Loom is about the JVM.
| tybit wrote:
| They mostly solve the same problems, albeit in very
| different ways. Loom is primarily about making threads
| cheaper, async await is primarily working around threads
| being too expensive.
| lostmsu wrote:
| Yeah. It's kinda funny. Due to extensive F# -> C# -> Java
| feature bleed you can consider them beta channels of the same
| language. Java being LTS, C# - main, and F# - Dev Builds.
| lostmsu wrote:
| I love C#, but they seriously fucked up Span<T> and Range by
| restricting them to 32 bit length, which by the time when the
| features were concepted was already extremely niche.
|
| 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.
___________________________________________________________________
(page generated 2021-03-13 23:01 UTC)