I’m going to have to stop dragging you along and move out myself ahead of you. This is becoming too burdensome. But I will look to interact with you on any insights you have which I observe are actually well thought out. I do believe that you present very important ideas sometimes and I will continue to value your interaction, but I also unfortunately observe that you don’t read my posts carefully and I cannot continue to recapitulate. I tried with all my patience to try to get you to be more focused when addressing topics I have already written about, and after ~3 years I understand that you simply will not do it. So the only choice I have is to just ignore you when you start doing that. So in the future I will have to just walk away and let you think whatever you think and continue on building what I am building on my own. I will interact with you where and up to the point that you’re not doing that. So I will look at each new issue discussed with a fresh and renewed hope, but then it goes South I will just exit the discussion.
The reason is because all that constant recapitulation is not only extremely tiresome, but it is also translates to ~3 years of talking and not a single LOC written.

I think the actor proposal needs more thought to work out the details.

I am not even convinced you assimilated all the details of what I proposed in the Parallelism thread.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416232137
Original Date: Aug 27, 2018, 8:47 AM CDT

@shelby3

That was my epiphany and idea. Please don’t claim it as your own.

It’s etiquette to correct attribute first-to-present as the creator of a concept.

You do realise the first time I talked about actors was in the context of only allowing mutation of properties, and making methods pure state machine transition functions... Go and re-read the history if you doubt me.
I don't even claim originality for any idea, I'm sure someone else has thought of it before. I'm pretty sure Carl Hewitt has discussed all of this in his book on Robustness.
"If I have seen further than others, it is by standing upon the shoulders of giants." Isaac Newton.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416386929
Original Date: Aug 27, 2018, 5:16 PM CDT

@keean wrote:

You do realise the first time I talked about actors was in the context of only allowing mutation of properties, and making methods pure state machine transition functions... Go and re-read the history if you doubt me.

And I credited you for that idea about employing Actors in my posts in the Parallelism issues thread #41. And your idea about making shared state immutable. However, I remember in our discussion about Actors that you never mentioned any idea about how to optimize the GC.
Your idea about using Actors was not the epiphany of realizing that the stack (and bump pointer heap with escape analysis) could be devoid of lifetime analysis and still be near zero-cost similar to Rust’s lifetimes but without the tsuris if we simply looked at the top level stack of the Actor’s pure function as the GC point where the bump pointer is reset to the origin without any lifetime analysis.
I have edited my prior post to quote where I first wrote about that in the Parallelism thread.
As I’ve been pointing out to you in the Subtyping issues thread #8 this past several days, you’re lack of focus when reading my posts is going to cause me to start ignoring you if you can’t stop that. Which is unfortunate because other than your unfairness in the way you digest my posts, our interaction has been very productive. I mean I am very dumbfounded as to how to handle this behavior of yours. I tried to just not say anything and just keep recapitulating over and over patiently but that didn’t even work.
I really do not think you are disingenuous. I really can’t explain this behavior on your part or let’s say the cognitive dissonance I am describing here. I can’t figure out if you simply lack the ability to respect the writings of others (or just of mine?) in a public discussion, or whether it is my writing style, or if you are too busy, or ...  but anyway I think after nearly 3 years if I can’t write and have my posts understood. And have to constantly recapitulate, then I think there comes point where the student stops arguing with the teacher and goes out in the real world and produces something instead talking.
A fact of life is every person has their strengths and weaknesses. We have to accept that a person is who he is. You are very skilled at your profession and have a wealth of knowledge to share. And you do share. And you do try to produce quality work. There are so many great and admirable attributes that can be stated and appreciated about the interaction here on this Github with you. The unfortunate bit, is there’s this problem which I have described in this post. I do not know for sure what is the root cause of the problem. So I think I have no choice but to either grin and bear it (which I have for ~3 years and gratefully as your student to a large extent) or to make some change on my side which does not depend on any change from you. Given I have written no code in ~3 years partially because of the amount of time and effort these discussions consume, I have to weigh carefully whether the constant recapitulation is a worthwhile tradeoff at this juncture. It’s not an easy decision to make.
Actually that you have ostensibly been running a business and coding full-time in addition to attempting to participate in these discussions, is probably indicative of you just being more busy than I am.  That could potentially explain the entire problem. But again, I can’t read your mind. So I don’t know.

I don't even claim originality for any idea, I'm sure someone else has thought of it before. I'm pretty sure Carl Hewitt has discussed all of this in his book on Robustness.

That will be surprising if Hewitt had also assimilated the zero-cost resource allocation issue into his Actors concept. Because I’ve never seen anyone else mention it. Given how amazingly important that insight is, certainly others would have been talking about it. And I read a lot of stuff around the Internet.
It almost seems like you resent if I will have an amazing idea for PL design. I noticed after I wrote about that idea in the Parallelism thread, you didn’t reply about it and you did not even congratulate me on thinking of it. This totally destroys the good feelings of synergizing together. I very much celebrated and credited you in the Parallelism thread for pointing out how we could use Actors. But you did not reciprocate. You seem to not understand that humans are motivated by appreciation. This is what releases dopamine into the brain and gives good feelings.
Perhaps because I have only been doing PL design for ~3 years or so and you were doing it presumably for decades and were already published on the well known HList paper along with the venerable Oleg. So I guess little ole me should not be able to have such an insight that others missed.
But anyway, I can’t read your mind, so I don’t know the reasons for what you do. And I guess I don’t really want to know. I just want to achieve my goal as efficiently as possible. So I’ve been contemplating how I can move the process of the design of Zer0 forward more efficiently.
Ditto my recently design insight for list-of-lists w.r.t. to modularization of multi-parameter typeclasses that I was explaining to you over and over again in the Subtyping thread over the past 8 days or so.
P.S. I noted you upvoted my up-thread Why Not Haskell? post yesterday. Again I’m not requesting praise (that has been easy for me to achieve throughout life simply by coding and not talking). I merely want my posts to be carefully read and appropriately cited in discussions. That is the standard of the way technical and research discussions are conducted. I also of course appreciate corrections and objections.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416557312
Original Date: Aug 28, 2018, 6:57 AM CDT

@shelby3 You clearly were the first person to explicitly mention the memory management implications of the actor approach here in these issue comments. I didn't really have time to do a detailed analysis at the time which is why I didn't directly reply.
Maybe I don't fully understand what you are proposing, but I think that the lifetime of mutables being tied to that of actors  is  kind of obvious, however immutable data, if it can still contain pointers, will mean that there will still be data on the local thread stack that can get shared (although we could force a deep copy, this would be slow, and may not terminate). Also actors will replace objects, and therefore the management of actor lifetimes would become the same problem as the management of object lifetimes in other languages. In the actor model you can usually pass references to actors, which is kind of like passing mutable data.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416578118
Original Date: Aug 28, 2018, 8:10 AM CDT

@keean @shelby3
You did often talking about soundness issues of several languages, but can a language be unsound at all if it allows bottom to subtype every type?

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416581373
Original Date: Aug 28, 2018, 8:20 AM CDT

@sighoya well you can always have out-of-memory, so I guess they want to allow for that? You have to make special allowances to ensure you can't fail for various reasons  at runtime before you can get rid of bottom entirely. Given that another process could crash the CPU, I am not sure it's ever safe to get rid of.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416794022
Original Date: Aug 28, 2018, 8:36 PM CDT

@keean wrote:

I didn't really have time to do a detailed analysis at the time which is why I didn't directly reply.

This is quite understandable that you’re too busy working (coding, code review, business trips, meetings, etc) to give your full attention to these discussion. I think the optimum way for me to avoid frustration is I will back off and slow down when there’s an issue that isn’t being understood. I will wait and reply to those issues (e.g. current one in the Subtyping issues thread #8) when I have enough patience to write some convincing examples or something that could possibly elevate the understanding. Remember I still have a low-hum noise like perpetual headache from this messed up liver and spleen (although quite stable now, no more rollercoaster ride of periodic bouts of total incapacitation except on 1 day following a barbell workout…something about the inflammation that causes), so I’m not supercharged with mental energy to throw around on complex examples and what not. I have a constrained cognitive budget. I generally get about 6 hours daily of reasonably good energy (yet that low-hum is persistent, not the same cognitive vibrancy as before I got ill with TB), then it tails off but I continue to about 12-16 hours daily (at a declining cognitive energy level with sort of a cluttered or constrained thinking noise and slight fatigue) then at the daily limit collapse in the bed unable to keep my eyes open any longer. Since I don’t have time to do that highly explicative reply in the Subtyping thread today for example, then I will just ignore that thread until I do.
So IOW and in summary, I may have to leave more “loose ends” laying around these Github discussions, because I also need to move forward faster.

Maybe I don't fully understand what you are proposing, but I think […]

I replied to that in the Parallelism issues thread #41. Please continue that tangential line of discussion there where that topic was being discussed.

@keean replied:

@sighoya wrote:

You did often talking about soundness issues of several languages, but can a language be unsound at all if it allows bottom to subtype every type?

Well you can always have out-of-memory, so I guess they want to allow for that? You have to make special allowances to ensure you can't fail for various reasons at runtime before you can get rid of bottom entirely. Given that another process could crash the CPU, I am not sure it's ever safe to get rid of.

For the readers who don’t understand, the bottom type ⊥ (aka Nothing in Scala) populates (i.e. is the subtype of) every result/return type of every function/procedure in an eager (aka strict) evaluation programming language, because of the Halting problem.
Thus the implication is that non-termination is a type and thus isn’t unsoundness. But non-termination is not the only form of unsoundness. By loss of type safety soundness, we mean that the type the compiler thinks the runtime has is not the type actually seen at runtime. This happens either because the type system itself is unsound (as is the provable the case for Java and Scala 2) or because the programmer elects to cast away the typing checks or otherwise annotates his code as unsafe. Note there’s a higher-level form of unsoundness which I named “semantic unsoundness” which a compiler doesn’t check nor prevent. The programmer’s API design determines the potential for introducing semantic unsoundness.
In a lazy (~non-strict) evaluation programming language, the bottom type is instead the supertype of all types and instead the top type populates (i.e. is the subtype of) every result/return type of every function/procedure. So in Haskell non-termination is a value, not an effect! Remember that bottom in a strict language can’t be instantiated as a value and thus top in a non-strict language can’t be instantiated as a value. And thus type induction doesn’t doesn’t work because all types are subtypes of each other (this is why Haskell could never have subtyping and be sound). This is claimed to be a flaw in Haskell.
In short, lazy is the categorical dual of eager evaluation. C.f. Robert Harper’s blog linked above.

Original Author: @Ichoran
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416898743
Original Date: Aug 29, 2018, 5:05 AM CDT

@shelby3 - I'm afraid the discussions of the strengths and weaknesses of various languages present on this issue is too extensive for me to read or comment upon fully.
I agree regarding most of the weaknesses of Haskell.  Although I maintain it's at least as good if not better than anything else at expression complex relationships, that does assume that you can do so without any of the weaknesses becoming important.  For instance, laziness does not in any way prevent you from expressing a solution to a problem, but it may prevent you from understanding the computational complexity of a solution to a problem, and may also incur a substantial runtime penalty.  If being correct is not sufficient--being correct and fast enough is required--then Haskell can rapidly degrade from expressing a problem with great elegance to expressing it with great awkwardness.
Regarding other languages, I don't think you always quite express how certain tradeoffs are unavoidable.  For instance, the JVM garbage collector works amazingly well for certain classes of problems; I have a C++ program where I have to maintain a huge variety of memory arenas and free lists in order to get the same kind of performance with constructing and deconstructing and reconstructing elaborate data structures as I get by default on the JVM.  Or, with Rust, you repeatedly mention the burden of managing lifetimes, but don't (so far as I saw) fully acknowledge both that the optimizer and/or borrow checker can become increasingly sophisticated and therefore improve efficiency and/or reduce programmer burden; and you also don't seem to fully acknowledge that Rust frequently achieves incredible performance precisely because it helps you think about lifetimes in a way that must be correct, and therefore allows you to attempt high-performance strategies that would be too daunting in any other language.
So overall, I think the criticisms you level are largely correct but from the spot-checking I've done seem a little stronger than is warranted.
Also, regarding the "Actor epiphany", I do think it could be a valuable idea, but without having at least a prototype to try out, it's hard for me to intuit whether allowing mutability only in that structure is adequate.  For instance, I do a lot of stuff with C++ that involves a great deal of pointer-chasing and reconfiguration on what are otherwise extremely lightweight objects; and these things are part of the persistent state of the program (as it is building up an analysis of complex behavior).  The lightest-weight actors I know about would incur a massive performance penalty due to the need to queue requests instead of simply execute the desired algorithm (with everything needed already present in the CPU cache).  So I don't think the "Actor epiphany" is a universal panacea.  (ARC is not a useful strategy here; it has too much overhead.)  So, then the question is: in practice, just how useful will this be?  I can't tell from inspection.  It really helps to try it out on real problems (either with a full programming language, or as a library in some language that admits that kind of structure, e.g. C++, with the programmer doing manually those things that the language is intended to help with).  My understanding of the history of Rust is that it started out with considerably more tooling, more like Pony, and with experience it was determined that the borrow checker and lifetime system was enough to solve the overwhelming majority of problems (and that was good enough).
Finally, regarding OO being an antipattern (which you mentioned in arguments against Scala)--I don't think that's clearly established.  Some people argue this very strongly, but the arguments I've seen mostly end up being worst-case scenarios.  Admittedly, some of these were encouraged early during the transition to OO programming (some are still encouraged).  But various people (e.g. Odersky, unsurprisingly) have pretty compelling arguments regarding the strengths of OO as an abstraction mechanism.  Anecdotally, I find OO very useful when you want a good deal of code reuse but have a variety of underlying data structures--one could use typeclasses, but with quite a lot more cognitive and programming overhead even in languages that make typeclasses very easy.  The main argument against OO, I think, is that learning best practices in OO programming is a nontrivial amount of work, and learning best practices in FP is a nontrivial amount of work, and maybe you don't want to do that much work.  If it's a contest, FP + side effects is usually safer.  But it doesn't have to be a contest if you have the time to learn both styles.  I use enough Rust now to miss the OO features I have in Scala.  (Dynamic traits are nice, but not enough.)

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416903813
Original Date: Aug 29, 2018, 5:24 AM CDT

@shelby3 I think a compelling argument against OO is that the C++ standard library has almost entirely abandoned OO in favour of generic programming techniques. OO languages without generic capabilities have had to add some form of generic over time (Java/C# etc). However languages that support generics do not need OO to achieve the same level of abstraction (Haskell would be an example here).
As for the comments about Rust, I would suggest lack of familiarity with the Generic approach is the cause of wanting OO features, not any underlying lack of abstractive power.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416915195
Original Date: Aug 29, 2018, 6:11 AM CDT

@Ichoran
Thanks for sharing your incredible experience.
I've a similar opinion about the JVM, it does a great jobs which would be tedious to implement in C++ over and over again.
I would also agree about the performance issues regarding Rust, as it is highly configurable. But do think that rust is a good choice to get the day-to-day jobs done. Is it really worth to tune everything to its maximum and to invest all the power in optimizing. You certainly know that commercialism isn't idealism and the time to do enough research on performance is mostly not available.
But I must admit that the option to tune with lifetimes is considerable better than fighting with null pointer or invalid memory exceptions of C++  to achieve good performance. So what about a language with GC per default and optional lifetime management. How would you stay to it?
Further, I share your opinion about OOP not to be a universal mistake, even if it is bad it has shown remarkable advantages in software development.
@keean
It is of course better to solve things with genericity opposed to subtyping (though genericity is in some sense subtyping).
But there are reasons to request inheritance, for me it is equal how you want to achieve that, for instance with default implementations in typeclasses. But inheritance has its uses and I miss that too in Rust.
@Ichoran
I can't say anything about the best option for parallelism, the actor pattern seems promising, but we will see if it is all that good.
Another question, do you think that Rust scales in large scale development with its lifetimes? I've heard many times several complains about different users that it is not the best choice for large scale development and using the borrow checker becomes fighting the borrow checker.
@NodixBlockchain
What is your opinion to that?

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416916366
Original Date: Aug 29, 2018, 6:15 AM CDT

@sighoya

But there are reasons to request inheritance, for me it is equal how you want to achieve that, for instance with default implementations in typeclasses. But inheritance has its uses and I miss that too in Rust.

I think interface inheritance is okay, but almost every time I see implementation inheritance it saves time to start with but becomes an increasing burden as the software grows in complexity. With generics you should only ever implement an an algorithm once - what do you need inheritance for?
A linked list base class does not work in C++, but a linked list template does. With C++ standard library you never need to implement a linked list by hand again.
Have you a compelling use case for implementation inheritance that you have not seen how to approach generically? I would be interested in any concrete examples that we can examine.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416917397
Original Date: Aug 29, 2018, 6:19 AM CDT

@Ichoran sorry, I meant to address my response above to you, but addressed it to shelby3 by mistake.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416922829
Original Date: Aug 29, 2018, 6:42 AM CDT

I think interface inheritance is okay, but almost every time I see imolementation inheritance it saves time to start with but becomes an increasing burden as the software grows in complexity.

Agree, and in my opinion this can be ameliorated with partial inheritance, i.e. take what you need.
I believe I've seen that in @skaller's language, but I don't find the web site.
Explicitly inheriting or importing names with types behind would stabilize inheritance such that changes in supertypes would not affect subtypes too much. Furthermore, I appreciate modular inheritance, it means you don't have strict hierarchies such that you can exchange the appropriate supertype with another which satisfy the signature to be inherited.

With generics you should only ever implement an an algorithm once - what do you need inheritance for?

Generics alone do not provide inheritance, only subtyping. I mean if you provide record/record types imports/inherits from other record/record types this would be ok.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416925423
Original Date: Aug 29, 2018, 6:53 AM CDT

@Ichoran wrote:

I agree regarding most of the weaknesses of Haskell. Although I maintain it's at least as good if not better than anything else at expression complex relationships, that does assume that you can do so without any of the weaknesses becoming important. For instance, laziness does not in any way prevent you from expressing a solution to a problem, but it may prevent you from understanding the computational complexity of a solution to a problem, and may also incur a substantial runtime penalty. If being correct is not sufficient--being correct and fast enough is required--then Haskell can rapidly degrade from expressing a problem with great elegance to expressing it with great awkwardness.

Oh I agree. Haskell is the best at expressing equational reasoning when performance and lack of abstruseness is not important. If that point didn’t come across in what I wrote, that was not my intention to imply otherwise.
The level of abstruseness required to shoehorn IO with monad transformers can become an abstractions thicket.
The level of expression can become so abstract with so many invisible mechanisms in play under the surface that the imperative programmer has difficulty understanding the code. So expression doesn’t necessary equate with readable, non-abstruse code.

Regarding other languages, I don't think you always quite express how certain tradeoffs are unavoidable. For instance, the JVM garbage collector works amazingly well for certain classes of problems; I have a C++ program where I have to maintain a huge variety of memory arenas and free lists in order to get the same kind of performance with constructing and deconstructing and reconstructing elaborate data structures as I get by default on the JVM.

The 20 point summary that I posted about Java was summarizing up-thread discussion. @keean is actually the one who argues that Java is clusterfucked by its GC and shared memory threading model. He argues its bad inertia that permeates Java libraries and thus hard to get away from. He argues the GC has bad asymptotic performance and is very difficult to control.
I was the up-thread citing Eric S. Raymond’s point that a mainstream popular systems programming language will need easy GC. So I was the one pushing for some AMM instead of linear types (e.g. Rust’s lifetimes.
But then I found the “actor epiphany” which may give us AMM-like ease-of-programming along with Rust-live zero-cost abstraction performance!!

Or, with Rust, you repeatedly mention the burden of managing lifetimes, but don't (so far as I saw) fully acknowledge both that the optimizer and/or borrow checker can become increasingly sophisticated and therefore improve efficiency and/or reduce programmer burden; and you also don't seem to fully acknowledge that Rust frequently achieves incredible performance precisely because it helps you think about lifetimes in a way that must be correct, and therefore allows you to attempt high-performance strategies that would be too daunting in any other language.

I stand by my argument that Rust lifetimes are far too onerous to be used in any mainstream programming language. I think Eric S. Raymond also agrees with me. I expect they will be relegated to a narrow class of embedded and mission critical applications.
I don’t know if you’re aware but there are certain algorithms that Rust lifetimes can’t even check correctly and force you to use an inferior and inefficient one. I have an example far up-thread which I have linked to numerous times but I do not feel like trying to locate again right now. Search the thread if you want to find it. (Make sure you search for all “Load More...” first)
And since I discovered the aforementioned “actor epiphany”, if that turns out to be correct, viable, and general purpose, that could further relegate Rust to the fringe use cases.

Also, regarding the "Actor epiphany", I do think it could be a valuable idea, but without having at least a prototype to try out, it's hard for me to intuit whether allowing mutability only in that structure is adequate.

Agreed that is an unknown until we experiment with it.

For instance, I do a lot of stuff with C++ that involves a great deal of pointer-chasing and reconfiguration on what are otherwise extremely lightweight objects; and these things are part of the persistent state of the program (as it is building up an analysis of complex behavior). The lightest-weight actors I know about would incur a massive performance penalty due to the need to queue requests instead of simply execute the desired algorithm (with everything needed already present in the CPU cache). So I don't think the "Actor epiphany" is a universal panacea. (ARC is not a useful strategy here; it has too much overhead.)

I would appreciate a more complete description of the use case you alluded to. So we can analyse it more deeply. Perhaps we can think of a way to restructure it to avoid the issues you see. @keean and I were able to restructure (in our minds-eye only so far) the blockchain UTXO design to work with our actor concept with near zero extra overhead!
The ARC overhead can be optimized away in some cases also. Again devil is in the details, we need details to analyse.
Nevertheless there will likely remain use cases that are optimal for Rust lifetimes.

My understanding of the history of Rust is that it started out with considerably more tooling, more like Pony, and with experience it was determined that the borrow checker and lifetime system was enough to solve the overwhelming majority of problems (and that was good enough).

Except concurrency and massive multicore parallelism (which requires software cache coherency or no coherency). Whoops.

Finally, regarding OO being an antipattern (which you mentioned in arguments against Scala)--I don't think that's clearly established. Some people argue this very strongly, but the arguments I've seen mostly end up being worst-case scenarios. Admittedly, some of these were encouraged early during the transition to OO programming (some are still encouraged). But various people (e.g. Odersky, unsurprisingly) have pretty compelling arguments regarding the strengths of OO as an abstraction mechanism.

I reiterate @keean’s point.
Also I am seriously looking at adopting Scala as the initial compiler output target for Zer0, and just not using the OOP features which I think are an anti-pattern (c.f. also, also, also “Edit#2”, also). I thought I needed Go for the goroutines, but maybe not.
I remember a discussion (from a video presentation) some years back both involving Martin Odersky; and it was shown that all his OOP examples could be done better with more degrees-of-freedom, better separation-of-concerns in FP. And now I see finally Scala is adding more FP to their standard library some years after I had written a StackOverflow post strongly criticizing the OOP design of the library due to its complexity and corner cases. That’s not to say I necessary want Scalaz as the default library. I am interested to study the new library Scala is developing.
However let me say that I really appreciate the feedback and I want to know more about your use cases. Because we can learn from it and become aware of our lack of omniscience.
I’m confident we’re going to be on the right-side history about this. And we will bet strongly on it by offering no OOP features other than module encapsulation and existentially quantified objects. Actors would be the new objects I guess. But I am always open to reading quality objections from others. So if anyone wants to attempt to show me an example that is better in OOP, then I like to discuss real use cases.
But you can also read in the Iteration versus Enumeration issues thread #16, that we will retain imperative loops as an option.

one could use typeclasses, but with quite a lot more cognitive and programming overhead even in languages that make typeclasses very easy.

I think typeclasses are conceptually easier than OOP classes and interfaces. Someone just needs to put them in a programming language that is not so difficult. Haskell and Rust are both difficult to learn and master. Rust has many little gotchas because the have too many inconsistencies in the design (e.g. the closures are a mess which @keean has mentioned), three different kinds of strings for example, and implicit cuteness.

I use enough Rust now to miss the OO features I have in Scala. (Dynamic traits are nice, but not enough.)

I’d like some examples if you have time?
I’m not arguing that there aren’t use cases that require for example C++ for the combination of abstraction and low-level control, or Rust for the zero-cost resource lifetimes and safey, or Haskell for the equational reasoning prototyping, or …
I’m just wanting a language that doesn’t make my nosebleed and hits the sweet spot in performance and abstraction for what I usually want client and server. Something in between those difficult languages mentioned above and the dynamically typed languages such as JavaScript and Python. Something more performant, statically typed, faster than Java, but not as much of a hassle as C++ and Rust.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416937080
Original Date: Aug 29, 2018, 7:37 AM CDT

@sighoya wrote:

I've a similar opinion about the JVM, it does a great jobs which would be tedious to implement in C++ over and over again.

I am concerned about whether the “actor epiphany” design will be sufficient to implement all those use cases. If not, we may also need to consider a AMM GC as an optional mode (such as per thread perhaps like Nim?). But I’m hoping the pure “actor epiphany” design will be sufficient.

I would also agree about the performance issues regarding Rust, as it is highly configurable. But do [not] think that rust is a good choice to get the day-to-day jobs done. Is it really worth to tune everything to its maximum and to invest all the power in optimizing. You certainly know that commercialism isn't idealism and the time to do enough research on performance is mostly not available.
But I must admit that the option to tune with lifetimes is considerable better than fighting with null pointer or invalid memory exceptions of C++ to achieve good performance. So what about a language with GC per default and optional lifetime management. How would you stay to it?

There is a problem with write-barriers and such when trying to merge AMM GC with zero-cost stack lifetimes. It probably causes a bifurcation between the two worlds of objects that can’t reference each other. I studied GC up-thread.

Further, I share your opinion about OOP not to be a universal mistake, even if it is bad it has shown remarkable advantages in software development.

Examples please. But in a different issues thread please.
Let me suggest if either of you want to discuss the OOP versus FP paradigm and provide examples, please do it in a new issues thread. This thread is getting too long already and can’t barely load anymore on my slow ISP in the Philippines. Please select another extant issues thread or create a new one please.

Have you a compelling use case for implementation inheritance that you have not seen how to approach generically? I would be interested in any concrete examples that we can examine.

Could someone please take the initiative to move the OOP vs. FP discussion to another issues thread?
That will end up being a long discussion that will bury this thread.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416937214
Original Date: Aug 29, 2018, 7:38 AM CDT

@sighoya

Generics alone do not provide inheritance, only subtyping. I mean if you provide record/record types imports/inherits from other record/record types this would be ok.

You misunderstand, DRY is about not repeating yourself. Inheritance in an object oriented system is one way of trying to achieve it. Generic algorithms are a different way of achieving the same goal. Inheritance is not the goal, avoiding repetitive coding is the goal, and generics achieve that aim more completely. In a good generic system each sort algorithm would only ever be implemented once, so how could inheritance make your code any shorter or more understandable?

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416941254
Original Date: Aug 29, 2018, 7:52 AM CDT

@keean wrote:

In a good generic system each sort algorithm would only ever be imemented once, so how could inheritance make your code any shorter or more understandable?

I think it’s more that old habits die hard. The familiar seems easier.
Also we need to show how to do all these things with typeclasses. I think it’s not obvious to most programmers yet.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416944627
Original Date: Aug 29, 2018, 8:03 AM CDT

Inheritance in an object oriented system is one way of trying to achieve it.

No, inheritance is not OO, it means simply to copy existing things.

In a good generic system each sort algorithm would only ever be implemented once, so how could inheritance make your code any shorter or more understandable?

@keean
The point is that you inherit all the time with module imports, could you solve module imports with generics? Inheritance means copying, it is not that bad and I want that for records, too. Subtyping CAN  then be a nice side product if the constellation is good.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416951155
Original Date: Aug 29, 2018, 8:22 AM CDT

@sighoya wrote:

No, inheritance is not OO, it means simply to copy existing things.

It binds the behavior to the data prematurely and unnecessary discards degrees-of-freedom.
Typeclasses will give you the same benefits without the gridlock.

Inheritance means copying, it is not that bad and I want that for records, too. Subtyping CAN then be a nice side product if the constellation is good.

I think it is bad and can be done with typeclasses instead as I explained already in the Subtyping issues thread #8.
But you should make some example code to try to prove me wrong if you can.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416959795
Original Date: Aug 29, 2018, 8:47 AM CDT

@shelby3

It binds the behavior to the data prematurely and unnecessary discards degrees-of-freedom.

I think, we have to differentiate here. What you mean here is that subclassing with tightly bounded static methods is a design fault. I agree with this. C# and Kotlin remedy this by associated methods aka extension methods where in each module new methods for an existing imported type are added. Rust solves this with typeclass implementations so dependent where you import a type you get different associated methods for that type. So yes, it can be solved with typeclasses.
But then there are constituents of a record. These constituents should be tightly bounded to a record, otherwise the use of a record is of no sense.
How would you solve inheritance of these fields between records? With self parameters in typeclasses?
What is about this example:
r=record{a:Int=2,f:Function=...,g:Function=..., z:Double=6.0}
s=record{b:Int=3,  import f,g,z of r }

Do you want to do this with typeclasses, too?

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416967546
Original Date: Aug 29, 2018, 9:10 AM CDT

@sighoya For record values, copy the record, and mutate the changes? I don't really like functions in records anyway, as it makes them non-serialisable, so consider what happens when you remove the functions from records and re-factor them into generic typeclasses.
I don't mind record subtyping (controlled by typeclasses) so you can say: this function works on any record with a "length: Int" property using typeclasses to restrict the parametric polymorphism.
So I would write f and g as free functions on records containing z : Double and then no inheritance is needed :-) You could write your own typeclasses for this, but I like the idea of having a set of build-in "compile time reflective" typeclasses that deconstruct function and data types.
fun[A] f(r : A) requires Record[A].Property["z"].Type[Double]

Note here I am reusing the 'associated types' mechanism to project the property names (as type level name-kinds) and types (as type-kinds).

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416971110
Original Date: Aug 29, 2018, 9:20 AM CDT

Do you want to do this with typeclasses, too?

Yes. As @keean just reiterated and as I explained exhaustively in the Subtyping issue thread #8.
If you need an assignment from subtype to supertype, then by explicit cast only so HM-like type inference remains decidable.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416974747
Original Date: Aug 29, 2018, 9:29 AM CDT

@shelby3

If you need an assignment from subtype to supertype, then by explicit cast only so type inference remains decidable.

Yes, or we could always infer a "row polymorphic type" (using typeclasses) so that the effective type of any inferred record where we do not have a concrete instance has a "| A" added, as in:
fun f(r) : Int =
   return r.length * r.width

would infer the type forall a . {length: Int, width: Int | a} for r, but:
let x = {length: 10, width: 5}

would infer the type {length: Int, width: Int}.
Edit:
Note I think the syntax:
fun f(r : {forall a . length:Int, width:Int | a}) : Int

is more readable than:
fun[A] f(r : A) : Int requires
   Record[A].Property["length"].HasType[Int],
   Record[A].Property["width"].HasType[Int]

But they are essenitally the same constraint, and it may be better to stick to the latter one to keep the type system more compact and learnable at the expense of some verbosity.
Maybe the properties could be directly reflected as associated types to shorten things a bit, and make HasType an infix type-class ==:
fun[A] f(r : A) : Int requires A.length == Int, A.width == Int

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-416997048
Original Date: Aug 29, 2018, 10:31 AM CDT

@keean wrote:

So I would write f and g as free functions on records containing z : Double and then no inheritance is needed :-)

But that are static methods, I want different (function) values for different record values of the same record type.
What is, if I want to exchange the function value at runtime, how could you do that?
@shelby3 wrote:

Yes. As @keean just reiterated and as I explained exhaustively in the Subtyping issue thread #8.

How? Afaict, this can only be solved with "self" arguments in typeclasses, otherwise you provide static functions (which include zero argument functions, too), NOT members. (And I'm not aware you introduced selfness in the proposed thread).

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417008474
Original Date: Aug 29, 2018, 11:05 AM CDT

@sighoya Your example only required static functions, because you inherited them. Do you want to inherit the functions or override them? You use a static function for inheritance, and a typeclass to override. Perhaps you have another example in mind?
In generic programming there are only really two kinds of functions, generalised functions that use parametric polymorphism, for example the id function is fully generalised, and works on any type. The other kind is specialised where the implementation is different for different types, and for this we use typeclasses (constraints). Typeclasses allow us to span the gap between fully specialised and fully generalised in arbitrary ways, but I would suggest that if it is too arbitrary then you are not really breaking the problem down in a structured way. A structured approach to generlisation/specialsation is as important as structured programming, and OO encourages a completely ad-hoc approach. OO is like the 'goto' of structured abstraction.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417014973
Original Date: Aug 29, 2018, 11:23 AM CDT

@keean
You can model signature inheritance by cascading typeclass implementations for a given record:
typeclass C1[A,B]
    a:A
    b:B
typeclass C2[C,D]
c:C
d:D
struct S
//Nothing
S implements C1[Int, Int] ,C2[Float,Float]
a=1
b=1
c=1.0
d=1.0

But in this example, all the fields are static for S s.t. only one value a,b,c,d exists for all struct instances.
Rust remedies with self parameters:
typeclass C1[A,B]
    a:self->A
    b:self->B
typeclass C2[C,D]
c:self->C
d:self->D

With implicitly self filling we now have local storages for a,b,c,d for each struct instance.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417029140
Original Date: Aug 29, 2018, 12:03 PM CDT

@sighoya You don't need self parameters, its just an extra type parameter like:
typeclass C1[S, A, B]
   a : S -> A
   b : S -> B
typeclass C2[S, A, B]
c : S -> A
d : S -> B

we dont need different variable names for C1 and C2 parameters because of scoping.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417056467
Original Date: Aug 29, 2018, 1:26 PM CDT

@keean
The reason behind self is the implicit injection with the dot operator:
record.index() ==> index(record)

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417060153
Original Date: Aug 29, 2018, 1:37 PM CDT

@sighoya

The reason behind self is the implicit injection with the dot operator:

I understand how these mechanisms work in languages like Go and Rust, but it limits methods to single dispatch. You can have multiple dispatch, and you don't need write the first argument to the function as a pre-fix. So as you say:
record.index() == index(record)

If we are dispatching on all function arguments, it makes no sense to put the first one before the function name.
What is the reason to prefer "x.f()" to "f(x)" ?
which is better:
add(x, y)
x.add(y)
x.add.y
(x)add.y
(x, y).add
(x, y)add

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417063145
Original Date: Aug 29, 2018, 1:46 PM CDT

@keean

What is the reason to prefer "x.f()" to "f(x)" ?

In this case the latter, what about this:
transient.add(elem1).add(elem2).remove(elem1).build()

You can have multiple dispatch, and you don't need write the first argument to the function as a pre-fix

This is a problem, but there are use cases for it:
fun(a:Set,l:List,a:Array)
l:List=...
s:Set=...
a:Array=...
l.add(2).remove(3).fun(s,$,a)

$ is a hack to inject the intermediate into it.

which is better:

add(x,y)

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417127025
Original Date: Aug 29, 2018, 5:26 PM CDT

@keean wrote:

Yes, or we could always infer a "row polymorphic type" (using typeclasses) so that the effective type of any inferred record where we do not have a concrete instance has a "| A" added, as in:
fun f(r) : Int =
   return r.length * r.width

Agreed but we still need the cast for supersumption and subsumption.
As you know I had already proposed both concepts in the Subtyping issues thread #8. And we already discussed that in detail there and do not need to re-discuss it again here.
These endless recapitulations all over the place become very tiring.
Why is @sighoya raising this issue here when we already discussed it in the Subtyping thread. He could have raised his concerns there rather than dragging this thread into the mud of recapitulation. This is exhausting guys.
There is no organization over here.

@keean wrote:

@sighoya wrote:

How? Afaict, this can only be solved with "self" arguments in typeclasses, otherwise you provide static functions (which include zero argument functions, too), NOT members. (And I'm not aware you introduced selfness in the proposed thread).

Your example only required static functions, because you inherited them. Do you want to inherit the functions or override them? You use a static function for inheritance, and a typeclass to override. Perhaps you have another example in mind?

Correct rebuttal.
 The familiar seems easier.
Also we need to show how to do all these things with typeclasses. I think it’s not obvious to most programmers yet.

Even Jon Harrop admitted he doesn’t have enough experience with typeclasses yet.

@sighoya wrote:

What is the reason to prefer x.f() to f(x)?

In this case the latter, what about this:
transient.add(elem1).add(elem2).remove(elem1).build()

The compiler can easily offer both and convert that dot notation syntax sugar into:
build(remove(add(add(transient, elem1), elem2), elem1))

PLEASE STOP THIS TANGENTIAL DISCUSSION IN THIS THREAD NOW.
I asked you guys nicely above to not pollute my thread (this is my thread) with off-topic discussion of OOP vs FP. Please move your noisy discussion of that issue to another thread NOW.
The OOP proponents are I am nearly certain just making noise. I am interested to read their noise just to be sure I have not missed something important, but please not in this thread!

Original Author: @Ichoran
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417174319
Original Date: Aug 29, 2018, 10:02 PM CDT

Regarding whether Rust lifetimes are too onerous to allow Rust to become a mainstream language and/or scale to large systems, I would say: no, because mainstream programming presently includes a lot of stuff that is way more onerous to keep track of in comparably high-performance languages (mostly C and C++).  C and C++ are mainstream languages.
We have not yet seen any challenger that can fill that niche so much better than Rust that there's no room left for it.  Of the languages I'm aware of, Pony comes the closest and with Pony there's more you have to keep track of, not less.
I don't think Rust has the potential to become the major programming language, though.  There are a lot of problems where you either can manually fix things after running the code shows that you've mucked it up, and there are some really complex problems for which Rust's abstraction capabilities are inadequate.
Anyway, the way to use Rust at scale seems to me to be to use different paradigms at different scales.  When you have something which is a fiddly detailed operation, you can handle a complex tangle of lifetimes.  The trick is to continually simplify matters for higher-order operations/interfaces.  This is always the trick when programming in anything, really, but I've found that one can do it quite effectively in Rust by making use of things like scaffolding structs, switching from a mutable to an immutable paradigm, and not being afraid to use Cow and Arc.
Let me give an example of one of the more complex pieces of code I've written that involves analyzing the behavior of some animals (they turn out to be nematode worms, but that's irrelevant) moving around an arena.  The data can be collected at high throughput, and being able to do exploratory data analysis is important, and the data files can be large, so efficiency is important (but in this case, not so important that I couldn't use Scala). The path that the animal takes can be represented by a series of points in 2D, but that path can be further decomposed into segments of forward, backward, turning, pausing, or unclassified behaviors.  And forward and backward can be further decomposed into a series of runs that are straight, arced, or not a simple shape; and those runs can be decomposed into periods where the animal is swinging its head to the left or right or where the data does not allow you to tell.  And, as you're going through the analysis, what you know about each of these decompositions varies.  And to decide whether something is a particular behavior, you need to perform some reasonably elaborate analysis, some of which is local and some of which requires global statistics.
So, to solve this, I'm constantly building up and tearing down (in this case, by GC) temporary data structures to do analysis in various places while building up a nested set of data structures that all have certain shared properties.  For instance, you can always ask "how long did this behavior occur".  Add to all this that the result may need to be saved, and that there may be multiple versions of the analysis (historical ones are sometimes needed for comparison even if better methods have been devised).
Solving this problem using a mutable object-oriented approach, with an available GC, was not easy, but it was considerably easier than other things I tried (including things I tried after I had already written the first one).  The reason is that there is plenty of functionality regarding data segments where the implementation can be reused, so an inheritance hierarchy, e.g. where "arc" and "straight" can be subclasses of "segment", naturally represents the nature of the data.  And because of the need to classify things based on local analysis and global analysis, and to classify on the basis of newly built data structures some of which are discarded, having mutable data structures with GC taking care of garbage was also a good fit.
It's not practical to describe this in enough detail to really get a sense of the intricacy of the analysis, but I don't think it's all that rare when dealing with real-world data rather than something that we've entirely constructed for ourselves inside computers.  We can decide, for instance, that people cannot drag arbitrary menu items around inside our UI; we can't decide that nematodes always back up for an integer number of head-swings.  One "solution" is to make simplifying assumptions (i.e. be wrong on purpose to make the analysis easier, hopefully in a way such that you're not wrong about anything too important).  People do this a lot.  I've done it a lot.  But I really appreciate languages that allow me to tackle problems with fewer such assumptions and retain high efficiency.
Although I didn't try to use Rust on this problem (I haven't yet, anyway), this would be exactly the kind of scenario where dyn Trait wouldn't be enough--it's not just the dynamic dispatch that I want, but the inheritance to avoid too much repetition in all the different impl sections.  Yes, I know there's a way to do it with enums, but it's considerably more work on my part.
Anyway, bringing this back to an Actor + ARC model--it's not that it would be impossible, but I'm pretty sure it'd be considerably harder, and probably would be dramatically slower.  But there are a lot of problems that are not much like this one.  Again, I think having a system that is well-worked-out enough to at least implement it as a library in C++ is a very important step.  (Unlike Rust lifetimes which require a borrow-checker to be useful at all, it seems that many of the key aspects could be explored in a library.)

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417229129
Original Date: Aug 30, 2018, 3:08 AM CDT

@Ichoran

but that path can be further decomposed into segments of forward, backward, turning, pausing, or unclassified behaviors.

Of course I don't have all these details, but based on what I have read, my approach to this problem would be something like this:
This sounds a lot like parsing with some degree of backtracking, where you tokensize the incoming data by recognising the low level structures, and can replace groups of tokens by new tokens, backtracking if certain patterns are found. Normally this is done deterministically with text, but can easily be adapted to work with probabilities.
So I would implement this as a stream based parser, using a hierarchy of tokens and recogniser / parser functions. The output would be a token stream. Implemented well this could even run on the data in real-time, reducing the storage requirements. I would put the data-type definitions in a separate module and import them into the parser, and other modules. I would separate modules and organise code by function, so for example data export into format 'X' would be one module, format 'Y' a different module. As core data structure would be a hierarchical token stream, all these modules would have the same structure, a stream reader and tree descent, but completely different code. I think this would result in a better program keeping the parsing algorithm all together rather than distributed throughout the codebase in virtual functions.
When implemented in this way it becomes easier to see the algorithms you are using, and you can choose LL or LR etc, and refer to textbook examples of different algorithms.
I think applications should start from data modelling, based on composing well known data-structures (lists, streams, maps, arrays etc) by using generic implementations you never need to implement your own container unless you are implementing the language standard library. After data modelling the appropriate algorithms should be chosen, the common ones can be available as standard, nobody should be hand coding a sort or partitioning algorithm.
Personally I think OO encourages unstructured spaghetti code that is difficult to read and maintain. I have managed many legacy codebases in the past and it takes a huge amount of time to understand the convoluted mess some developers make. You even find complete re-implementations of algorithms because one version only accepts a certain type, or the developer of one module was just not aware that another had already implemented it elsewhere. To me these are all problems with application architecture, which seems to be encouraged by OO, the idea that I want to have a class hierachy that matches the cognitive model of the problem domain. Whereas I think you should map the cognitive model to well known data structures, as a database engineer would do with tables, then implement the operations on that data bumy composing well known algorithms. With generics these algorithms only get implemented once, so the community can collaborate on improving them as part of the standard library, making everyone using the language more efficient.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417283250
Original Date: Aug 30, 2018, 6:15 AM CDT

@Ichoran wrote:

Regarding whether Rust lifetimes are too onerous to allow Rust to become a mainstream language and/or scale to large systems, I would say: no, because mainstream programming presently includes a lot of stuff that is way more onerous to keep track of in comparably high-performance languages (mostly C and C++). C and C++ are mainstream languages.

To the degree that C++ remains relevant in that people choose it to start new projects not because they have to (i.e. C++ engineers or existing code in C++ inertia), then Rust will also remain relevant because it attempts to reduce complexity as compared to C++ (although with all the little corner cases, they haven’t necessarily succeeded in every area although they surely have at least by discarding templates). Rust has typeclasses instead of C++ templates and I think typeclasses are really important. C++ templates are a nightmare.
The quibble I have with the claim that they (C/C++/Rust) will remain significantly relevant in the mainstream is the recent article that explains “C is no longer a suitable low-level language.”
If Go wasn’t so crippled in the area of polymorphism, I think it would have made even more significant cannibalization of C++ by now.
See this comment:
http://esr.ibiblio.org/?p=7724#comment-1912788
I am guessing that the mainstream will (over the next decade) move more towards prioritization of parallelism, concurrency, typeclasses, FP, and AMM (automatic memory management) without the tsuris of C++ nor Rust. And away from OOP and tsuris about deterministic characteristics. I think my “actor epiphany” (which AFAICT discards the determinism that Rust and C++ worry about at the memory allocation and deallocation layer) may be the secret sauce to give us nearly the same performance of C++ and Rust without the complexity of dealing with lifetimes and r-value temporaries optimizations. I think the Actor model is also what we need to address the parallelism and concurrency in the best way in a brave new world we are entering discarding determinism. I could of course end up being wrong about all that.

We have not yet seen any challenger that can fill that niche so much better than Rust that there's no room left for it.

Agreed not yet. That’s why I am working on a programming language. That is the reason for the title of this thread.
Please note that I refused to track C++ after the turn of the century when it became ever more complex. And I have refused to dive into Rust. My attitude is find something easier and performant, or go bust while trying. Meaning I reject that complexity. But you’re correct that others find themselves with no other choice.  And damn if I am not trying to do something about that.

Of the languages I'm aware of, Pony comes the closest and with Pony there's more you have to keep track of, not less.

I vaguely remember someone had mentioned it to me within the past 3 years (was that you?) and I glanced at it but thought it was too convoluted (c.f. also and also) for me to grok at that time. I just another took a quick look at it. The exception handling is lame. And I must say I am not that overly impressed. Here we go again with the OOP anti-pattern and concurrent, tracing GC, although that concurrent GC has some innovation (c.f. also and also) which apparently significantly improves performance compared to other GC languages. No typeclasses. No emphasis on FP. Their variant of my “actor epiphany” has some significant differences as I explained below. Plus they are talking about ~240 bytes of overhead per actor and @keean and I are calculating 0 bytes of overhead!
The Pony-ORCA GC employs tracing from each actor’s owned objects and each actor keeps a flag set for each object they can trace to. If the sum of these flags is 0 for an object and the owning actor can’t trace to the said object, then it can be freed by the owning actor when it does tracing. The tracing is only done when the actor is idle. My “actor epiphany” also collects only when the actor is idle, but as an additional performance advantage my idea requires no tracing and simply resets the bump-pointer-heap pointer to the origin. Because my idea is to separately reference count (aka ARC) the shared (between Actors/threads) data. So my idea is to encourage the programmer to minimize the shared data and use more non-shared objects. My idea will more efficiently collect non-shared data that was allocated on the per-actor bump-pointer-head instead of the stack giving closer to C++ or Rust performance. Whereas in Pony ostensibly all data that escapes a function and thus must be taken off the stack (because I see no lifetimes annotations nor borrowing to optimize zero-cost stack allocation) in Pony and thus Pony is collected with the tracing GC.
My idea potentially (although it can be entirely ameliorated if desired) suffers an additional (shared versus non-shared, i.e. ARC vs. not ARC) bifurcation in pointer types which Pony-ORCA does not have. Presuming I’m correct about the significant Pony drawback I mention below the following chart requires forsaking iso and trn, which to eliminate also requires employing copying instead in order to meet the alleged needs for massively scaling multicore, then my idea should be less abstruse than Pony, Rust, and C++ while also more performant than Pony (if it eliminates iso and trn) and closer to the performance of Rust or C++ if they were to require the same copying. Whereas, if i am incorrect and can add the iso and trn for Zer0’s ARC objects, then for those ARC objects Zer0 would be similarly abstruse as Pony and less performant (because ARC is less performant than tracing), but for the non-ARC objects Zer0 be less abstruse and more performant. Presuming most of the computation occurs in Actor function lifetimes with non-ARC objects, then Zer0 will be less abstruse and more performant.
Note Pony adds iso, trn and tag pointer types to the ones we had contemplated so far.

Pony
Zer0
Access
Shared Aliasing
Non-Shared Aliasing

isoIsolated

Exclusive read-write(writable)
None
None

valValue
immutable
Immutable(non-writable)
Read-only
Read-only

refReference
read-write
Read-write(writable)
None
Read-write

boxBox
read-only
Read-only(non-writable)
NoneRead-only
Read-writeRead-only

trnTransition

Exclusive write-only(writable)
None
Read-only

write-only
Write-only(writable)
None
Read-write

tagTag

Address-onlyOpaque pointer
Address-only
Address-only

The iso and trn can be moved (aka alias burying) to any other above type (except not trn to iso) because they’re exclusive ownership of writing. Also these types above can be employed within a recover block. Record fields also combine with the record pointer type to give a combined capability type. There’s also subtyping of capabilities, but remember in Zer0 all subsumption is via casting so as to make type inference decidable.
One huge drawback I see in that methodology of allowing more flexibility to share alias burned former mutables between Actors (i.e. moving ownership) is that AFAICT it might defeat the software cache coherency advantage I invented in the Parallelism issues thread #41. So AFAICT their model might not enable massively scaled multicore.
There’s no utility for Zer0 to have such exclusive writable types on non-ARC objects, because (the non-ARC don’t live beyond the Actor function lifetime thus) these can’t be shared without copying them to ARC (and copying of course can be to any type). Zer0 could adopt the iso and trn on ARC objects, but as aforementioned that might break the software cache coherency I invented for massively scaled multicore. Yet the exclusive reading of aspect of iso for immutable ARC objects could possibly prove useful to eliminate the need to trace those for cyclic references because only one pointer could exist.
Note some claimed the original authors of Pony vacated the project due to lack of financial support.
The Pony-ORCA GC is not theoretically faster than stop-the-world mark-and-sweep GC in a single-thread context. The cited paper states:

7 CONCLUSION
Orca represents a point in the GC design space where local tracing of messages both at the sender and the receiver side is used to avoid global GC stops. While this tracing is algorithmically costly, scanning can be performed locally in a thread without impacting the rest of the system. Thus, as the number of cores increases, the global impact of a single message send decreases. This can be contrasted with a stop-the-world GC where additional threads may increase the latency between triggering a collection and the start of the actual collection. Thus, even in an actor system where the entire heap is effectively shared by all actors, the necessary tracing can be performed concurrently, without blocking. Theoretically, a parallel stop-the-world collector should be able to perform a global collection more efficiently than Orca because each object would be marked only once (as opposed to twice per message send plus once per collection), and because of heap partitioning techniques such as generational GC operating on a global scale. For such techniques to be beneficial, however, their gains must outweigh any time lost waiting for threads to stop or due to negative coherency effects for propagating mark bits to cores sharing objects. Marking algorithms that can run in parallel with a mutator can reduce stop-the-world latency, but at the cost of less efficient marking which may slow down the mark-phase, and expensive write-barriers slowing down mutators.

So my point is that it can’t be faster than not having to do any tracing a la Rust or (at least for non-ARC objects) my “actor epiphany.”

I don't think Rust has the potential to become the major programming language, though. There are a lot of problems where you either can manually fix things after running the code shows that you've mucked it up, and there are some really complex problems for which Rust's abstraction capabilities are inadequate.

Absolutely Rust can’t become the dominant programming language because there’s too much demand for easier programming languages, e.g. Python, JavaScript, and to some extent JVM languages. At best, it can cannibalize some of C++’s use cases and perhaps (but I doubt this) pull some people away from the JVM languages.

Anyway, the way to use Rust at scale seems to me to be to use different paradigms at different scales. When you have something which is a fiddly detailed operation, you can handle a complex tangle of lifetimes. The trick is to continually simplify matters for higher-order operations/interfaces. This is always the trick when programming in anything, really, but I've found that one can do it quite effectively in Rust by making use of things like scaffolding structs, switching from a mutable to an immutable paradigm, and not being afraid to use Cow and Arc.

Indeed and we want to try make that even easier in something like what I hope Zer0 could become. But that is a pipe dream at the moment.

Anyway, bringing this back to an Actor + ARC model--it's not that it would be impossible, but I'm pretty sure it'd be considerably harder, and probably would be dramatically slower.

I wouldn’t count on that remaining true. @keean and I seemed to have paradigm-shifted actors significantly. But I will reserve judgment until we can actually experiment with it.
Thanks for sharing your thoughts and describing a use case you think is better fit to OOP.
Wish me good luck in making a total fool out of myself in the near future

@keean wrote:

Personally I think OO encourages unstructured spaghetti code that is difficult to read and maintain. I have managed many legacy codebases in the past and it takes a huge amount of time to understand the convoluted mess some developers make. You even find complete re-implementations of algorithms because one version only accepts a certain type, or the developer of one module was just not aware that another had already implemented it elsewhere. To me these are all problems with application architecture, which seems to be encouraged by OO, the idea that I want to have a class hierachy that matches the cognitive model of the problem domain. Whereas I think you should map the cognitive model to well known data structures, as a database engineer would do with tables, then implement the operations on that data bumy composing well known algorithms. With generics these algorithms only get implemented once, so the community can collaborate on improving them as part of the standard library, making everyone using the language more efficient.

I humbly tip my hat again to @keean when it comes to algorithms and data structures. I think he correct. OOP encourages programmers to be sloppy about design.
To prove it more definitely, we would need to actually see the OOP code and try to rewrite in FP. I have seen enough of those examples in the past to convince me already. But more wouldn’t hurt, if @keean and I weren’t overloaded already.

Original Author: @sighoya
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417299557
Original Date: Aug 30, 2018, 7:21 AM CDT

@Ichoran
D is also a system programming language with many users highly advocated with manual memory management.
The majority of these users believe that garbage collection by default is better.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417409716
Original Date: Aug 30, 2018, 12:52 PM CDT

@sighoya D language has serious problems. And no where near to being ready for massively scaling multicore. The GC in D is one of the big problems. We shouldn’t cite as hearsay in this thread the trolls on the Internet who think their stuck clock is correct always and not just twice a day. It’s about as reliably informational as typing gibberish into a post. AFAICT, D is a kitchen sink language.
What form of AMM GC? What about zero-cost stack allocation management a la Rust and the “actors epiphany” I am contemplating for Zero. I don’t think the users you are citing are fully aware of everything. JavaScript and Java users think tracing GC is dandy also. Fanboys with limited experience of alternatives.
Devil is in the details. I don’t think we yet know what will massively scale multicore and be the best balance of ease-of-programming with good enough performance for the mainstream general purpose programming language. The free market will decide. Significant experimentation going on right now in language design.
For example, we could cite this random dude on the Internet but would it be statistically relevant or even astute insight?

I hope that Crystal isn’t nearly as hard to learn as Rust. I have been trying to learn Rust and I find it to be the most difficult programming language I have ever tried to use. Over the last 20 years have I taught myself C, C++, PHP, JavaScript, Python (in that order), but Rust totally escapes me.
It looks like to me that the C/C++ ecosystem is going to fragment into a whole bunch of languages (Go, Swift, Rust, Crystal, etc.) in the future.

Original Author: @Ichoran
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417645394
Original Date: Aug 31, 2018, 7:13 AM CDT

@keean - I don't really have the time to explain the processing in detail, and what I do now isn't perfect, but a stream-based approach is non-ideal due to the value of informing individual decisions based on global statistics (covering as much as data as is available).  Otherwise, I agree, a streaming parser would make more sense.  Of course with particularly long data streams there isn't a choice but to make it somewhat streaming (which right now the code doesn't handle, actually).  Also, the normal parser operations are really clunky to use with this type of analysis where you often want to know things like "did event X happen within 250 ms of event Y", which is easy to tell if you have a list of pointers or indices to events Y and can then scan +- 250 ms in your data stream; but it's really hard to express (when you have a variable data rate) with standard parsing.
Parsers are good for many token-stream structuring operations, however.  It's entirely possible (somewhat likely, actually) that my example is rather atypical.
@shelby3 - It's unfortunate that you refused to follow C++ or dive into Rust, because those two languages have done by far the most to enable programmers to write complex high-performance code.  C++11 (and C++14 moreso, and C++17 even more) actually allow pretty safe code in most cases, if you follow the proper paradigms.  I rather suspect that any language you write will be left in the dust, on average, by C++ and Rust, unless you understand quite deeply why they've made various choices (and for that you really need to have dived into them in quite some detail).  But there is lots of room left for great languages.  Go, for instance, is unreasonably popular given that its primary characteristic seems to be to make you repeat yourself ad nauseum (while still only having Java-class performance).  But it's simple!  A language that was only as fast as Go while still being simple and yet allowing better code reuse could be quite a valuable addition.
Anyway, good luck!  I don't think I have time to comment more.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417647962
Original Date: Aug 31, 2018, 7:25 AM CDT

@Ichoran

Anyway, good luck! I don't think I have time to comment more.

Sure, thanks for your input.

Parsers are good for many token-stream structuring operations, however. It's entirely possible (somewhat likely, actually) that my example is rather atypical.

I don't see any great rebuttal that this could not have been done with a more algorithm and data-structure approach, using generics to avoid repetition instead of inheritance? Yes using a parser as the algorithm was based on minimal information and is probably incorrect, but you don't contest the methodology? Often when I have time I rewrite code into different paradigms to compare, it would be great to refactor something like this into generics to help show if there is indeed as I claim greater clarity of organisation. This particular example is probably too big though. It would be great to have a nice concise motivational example for OO inheritance that we could compare the generic code side-by-side with, and would not take months to implement.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417672360
Original Date: Aug 31, 2018, 8:57 AM CDT

@Ichoran wrote:

So, to solve this, I'm constantly building up and tearing down (in this case, by GC) temporary data structures to do analysis in various places while building up a nested set of data structures that all have certain shared properties. For instance, you can always ask "how long did this behavior occur". Add to all this that the result may need to be saved, and that there may be multiple versions of the analysis (historical ones are sometimes needed for comparison even if better methods have been devised).
Solving this problem using a mutable object-oriented approach, with an available GC, was not easy, but it was considerably easier than other things I tried (including things I tried after I had already written the first one). The reason is that there is plenty of functionality regarding data segments where the implementation can be reused, so an inheritance hierarchy, e.g. where "arc" and "straight" can be subclasses of "segment", naturally represents the nature of the data.

This means you want to use a tree data structure and inheritance has nothing to do with it. Inheritance (being a compile-time decision) would obstruct the degrees-of-freedom to restructure hierarchies at runtime. So unfortunately your example use case makes no sense to me as a justification for the need for OOP inheritance. OOP inheritance is an anti-pattern (c.f. also, also, also “Edit#2”, also) because it binds things together in unnatural and inflexible hierarchies. It also binds methods to data at instance construction time which fails Wadler’s Expression Problem. Much better to use typeclasses instead to apply operations to data at the function call sites to allow maximum degrees-of-freedom and dependency injection.
As for the mutation part, when I say OOP is an anti-pattern that doesn’t mean that the antithesis is only pure, immutable FP. It just means discarding that nonsense about class, methods, and the baggage that accompanies those such as virtual inheritance and covariance annotations on method parameters in the type parametrised case.

It's unfortunate that you refused to follow C++ or dive into Rust, because those two languages have done by far the most to enable programmers to write complex high-performance code. C++11 (and C++14 moreso, and C++17 even more) actually allow pretty safe code in most cases, if you follow the proper paradigms.

I recognize they probably have done that.  They’re on the wrong side of history in terms of not prioritizing optimization around massive scaled multicore (e.g. 1024 lightweight cores) parallelism that could ameliorate their tsuris/woe cost given the transition to massively multicore parallelism on the horizon. Although presumably most anything can be added to those languages via libraries (or if necessary tweaks to the languages), the problem they have going forward is that the tsuris and woe that programmers would gladly exchange for something more friendly to program with that achieve nearly the same performance.
Also I don’t think Zer0 will need to require programmers to all do the clusterfucked tsuris and woe to get within ~33 – 50% of their performance (and perhaps even faster). Not in the first PoC or MVP which is likely to transpile to Scala, but eventually later with an LLVM or C compile target.

C++11 (and C++14 moreso, and C++17 even more) actually allow pretty safe code in most cases, if you follow the proper paradigms.

Ah come on you know C++ is notoriously an unsafe and corner case laden language. C++ is a brittle language, especially the template (e.g. SFINAE) resolution clusterfuck. And new programmers don’t have time to master all that complexity and the 1500 page manual.

I rather suspect that any language you write will be left in the dust, on average, by C++ and Rust, unless you understand quite deeply why they've made various choices (and for that you really need to have dived into them in quite some detail).

I have dived into more detail than you may presume. Enough to know where some of the key pain points are and where some of the key optimization is coming from at the generative essence level. What I mean is I refused to become an utmost expert at programming all the tidbits of complexity that they throw at me. If I paradigm-shift the solution away from needing to do that for example r-value temporaries optimization (or at least where the programmer doesn’t need to do anything special for it to happen), then I don’t need to mire myself memorizing a 1500 page manual. Nor do I need to know about every single optimization they did in order to pursue a paradigm-shift which provides a use case and market which doesn’t need to me focus on those arcane optimizations initially. Of course over time more optimizations can be considered and the PL can be refined.
Paradigm-shifts can (in rare instances where they are discovered) drastically reduce the complexity and work involved. Not to say it will be easy to accomplish. Also C++ is designed by committee and that often means rigor mortis. AFAIK one of the most significant boons to C++ since the turn of the century or so was Boost and Alexander Stepanov. But he is gone now. Who will provide the next epiphany to keep that morass ahead of the pack nipping at its heels?
Rust seems to be too much locked into their lifetimes model to actually attempt to do something that leverages Actors which must move away from the lifetimes in order to gain maximum economies. And I think Actors are going to be the critical paradigm-shift.

But there is lots of room left for great languages. Go, for instance, is unreasonably popular given that its primary characteristic seems to be to make you repeat yourself ad nauseum (while still only having Java-class performance). But it's simple! A language that was only as fast as Go while still being simple and yet allowing better code reuse could be quite a valuable addition.

Which has been exactly what I am saying Zer0 should accomplish at a minimum. So I don’t know if you’re agreeing with me or you just not aware that I have been making that same observation and plan for Zer0.
Yet OTOH you come in here and I thanked you for doing so, but your remark to me is you think anything I create will be left in the dust. Maybe you mean “dust” only in terms of comparative performance, not potential popularity?

I guess what bothers me about your comments (which I significantly appreciate even though slightly irritating here at the end) is that which ostensibly bothers you about me. You seem to take offense with my disrespect for other PLs. And I take offense that you think I don’t have a valid reason to disrespect (in the form of being critical of) those PLs or to complain. There’s a reason I am creating Zer0. Because I need it! If no one else in the world needs it (unlikely) then I still need it. You seem to make presumptions about the level of resources at my disposal. Although it may be true that I might only have $1 million to spend on development right now, I would necessarily assume that will remain at $1 million. It might well be 10 times that over the next couple of years. Remember I am in the cryptocurrency arena.

Anyway, good luck! I don't think I have time to comment more.

Disappointed. I was hoping for some meat and potatoes discussion where I would actually get a chance to refute your detailed logic and not just a drive-by shooting.
Yet I do thank you tremendously for turning me back on to Pony again. Reviewing their work is helping me refine my understanding of the “actor epiphany” I proposed.

@keean wrote:

It would be great to have a nice concise motivational example for OO inheritance that we could compare the generic code side-by-side with, and would not take months to implement.

Plenty of comparative examples will come our way if we actually stop talking and start coding.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417815137
Original Date: Aug 31, 2018, 6:45 PM CDT

@Ichoran
When you argue for the viability and importance of Rust and C++, you seem to have entirely ignored 155+ IQ Eric S. Raymond’s point about readability of a programming language being the utmost priority “bar none” in the design of a programming language (especially in the open source code sharing era of computing we’ve entered over the past decade or so). You may have heard of Eric as he the progenitor of the term “open source” and author of The Cathedral and The Bazaar. Up-thread I have cited and quoted excerpts of his key thoughts about programming language design and the future he sees for systems programming not being C nor Rust, but rather something that generates higher programmer productivity such as Go. He thinks Rust is disorganized thus far and C++ is a horrendous clusterfuck of gotchas.
Eric said:

“C++ has the exact opposite problem to the virtue I had called out in Python.  Long-term maintainability of C++ code, TERRIBLE.”

How can Rust code be readable and how can the programmer be productive if he must obscure his algorithm and focus of thought with all this minutia about lifetime annotations? That is noise just there to insure zero-cost stack allocation of memory. I posit that perhaps we can entirely pivot away from that noise with my “actor epiphany” model with my proposed hyper-efficient bump-pointer-heap that I posit will roughly approximate zero-cost allocation of memory.
Readability and programmer productivity are facets that contribute to a programming language’s popularity. Of course for as long as everything else is 3 – 10 times slower than Rust and C++, then those programming languages which are more productive and readable will sometimes not be chosen because of performance needs. But if we can bring Zer0 up to close the gap in performance to a significant enough degree, then more and more use cases will be cannibalized from those unreadable clusterfucks C++ and Rust.
C++’s optimizations are largely about avoiding r-value temporaries and thus reducing copying and load of heap allocation and cache locality. I posit that my “actor epiphany” could possibly automatically deal with the heap allocation load issue. And if Zer0 will have a copy and move constructors, then we can also optimize away some of the redundant copying.

Original Author: @Ichoran
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417831016
Original Date: Aug 31, 2018, 11:06 PM CDT

@keean - I was addressing the memory usage pattern more than why OO was useful.  Since Shelby requested that this not be sidetracked into a discussion of OO vs FP, I felt that was counterproductive.  Also, as you point out, an actual example would be more informative than my rather vague descriptions.
Regarding algorithm-and-data-structure, my code is chock-full of algorithms and data structures.  I've just specialized them to what needs to happen to support the analysis rather than using generic algorithms and data structures.  (I use those too when they suit.)  Creating special-purpose algorithms that can skip a lot of work is one key way I achieve adequate performance.  When a highly-optimized (or highly nonintuitive) general-purpose algorithm is adequate, I use that instead.  Anyway, in the context here, probably thinking about graph-construction and graph-traversal problems are better than my peculiar case.  They're typically awkward to manage in languages that favor eager operations and immutable data structures, which Zer0 seems to tend towards.
@shelby3 - C++ is way more brittle and dangerous when not using it according to C++11 etc best practices.  I was speaking specifically about that.  I generally don't use it that way because it is still less safe and more awkward than Rust--so why not use Rust?
Regarding "left in the dust", I just mean in performance.  Getting to within 1/3 of C++/Rust is pretty achievable with a lot of languages and techniques (Java, Haskell, Go, and OCaml all do, despite being radically different).  A new language in that class would be great--I'm not disparaging your attempt at all, just pointing out that you really, really want to learn deeply from the languages that reliably deliver the highest performance if you want a language of that class.
Regarding readability, I don't find C++ terribly readable despite extensive experience, but I find Rust quite readable except when really exotic macros are used.  It does take some training.  In fact, I find Rust more readable, usually, because of the constraints imposed by lifetimes.  It forces a kind of simplicity into the logic that you wouldn't otherwise necessarily get; and that logic always has to be right in every language anyway, Rust just tells you exactly what it is.  The downside is that there are many correct (but more complex) things that are not expressible.
Regarding my taking issue with your disrespect for other programming languages, that is because you have an idea, not a working programming language.  It seems like a great idea, honestly, from what I can see.  But existence is a very compelling feature.  Many of the warts and awkward features found in other programming languages come precisely from the demands of existence.  That doesn't mean you shouldn't try to do better / better-for-your-use-case.  But without the "existence" feature, you shouldn't be surprised that other people may doubt that things will actually work out to be as universally lovely as you envision; and that those same people (e.g. me) may view expressions of disrespect a bit unfavorably.
Finally, regarding the opinions of well-known people with reportedly high IQs: I am not impressed.  We have enough experience here to make arguments based on evidence and logic rather than appealing to authority.  I have a good deal of respect for ESR, but everyone makes mistakes, and the best way to avoid mistakes is to appeal to evidence and logic (including personal experience, but one must recognize the limited scope of personal experience as evidence).  So let's cut out the middle-man and do that directly.
P.S. I have responded again at length, but as per previous warnings, at some point I'm going to stop.  I had thought that the point had already been passed, but apparently not yet.  Possibly it has this time.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417839116
Original Date: Sep 1, 2018, 2:16 AM CDT

@Ichoran no need to reply, unless you want to of course.

Anyway, in the context here, probably thinking about graph-construction and graph-traversal problems are better than my peculiar case. They're typically awkward to manage in languages that favor eager operations and immutable data structures, which Zer0 seems to tend towards.

Have you read "Data Structures and Network Algorithms" by Robert Endre Tarjan? I think this is probably the best work on graph algorithms. These are exactly the kind of algorithms that I work with, and are particularly the kind of thing that I want to ensure the language can deal with efficiently. One of the goals of the language is to synthesize low level memory layout and pointer control (or references) with functional techniques like immutability and typeclasses. Rust has already done this to some degree, but I find Rust's type system to not be well thought trough and lifetimes although interesting get in the way, and I have similar feelings about Scala. I imagine a simple and compact imperative style language ('C' like) with functional abstraction tools that allow composition of larger programs. I am also a fan of logic languages (and a type system is a simplified logic language) so maybe even a logic language for meta-programming.
The weaknesses of Haskell were initially type-system weaknesses (around type-classes) that came to light when working on the HList paper (2004, Kiselyov et al.) But reading Alex Stepanov's "Elements of Programming" I realised imperative programming could be beautiful too. If you have not read this book, you really should. This made me want to program in C++ again, but after a while the problems were too much, you needed too much template boilerplate to do seemingly simple generic things, and the language had too many kitchen sink features, tying to be multi-paradigm it fails to do any particularly well.
So my original idea was 'C' like syntax, Haskell like type-system with type-classes. Having seen Rust which really was now following a similar path, I found I could not adequately express Stepanov's Elements in Rust, it's type system is too limited, and it has several, what I think are, mis-features.
Shelby had the idea of a language which in some way solves the expression problem by combining union types with type-classes. When I had explored union types previously it was in combination with intersection types, which I had abandoned as a useful type-system (due to lack of inference, undecidability, and poor separate typing).
On the 'eager' operations part, I would refer you to this paper by Oleg Kiselyov:
http://okmij.org/ftp/continuations/PPYield/yield-pp.pdf&ved=2ahUKEwjtntq1n5ndAhUpBcAKHa9SAMMQFjAAegQIBhAC&usg=AOvVaw3WvH-lqwF8AzR52I87sdKm&cshid=1535785704999
As you can see the benefits of lazy processing, without the drawbacks can mostly be provided by an eager language with generators, (or completely by an eager language with co-routines). This is the way I want to go.
So I would say the language will be eager, but to say it favours immutability, the goal is to make programming with mutable data straightforward. In my opinion Haskell makes programming with mutable data too hard (mainly because monads don't compose), C++ doesn't even have immutable data.
I am tending towards favouring referentially transparent immutable values, combined with mutable objects/actors. Shelby prefers pointers and 'C' like variables.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417862139
Original Date: Sep 1, 2018, 9:08 AM CDT

@Ichoran wrote:

Since Shelby requested that this not be sidetracked into a discussion of OO vs FP, I felt that was counterproductive.

Sometimes it is difficult to convey meaning precisely. For example, I was wanting to not name anyone specifically (for obvious reasons that it may offend). I was wanting to avoid @keean and @sighoya (or @NodixBlockchain) going off on a 50 or 100 posts thread jack about OOP vs. FP, because it would bury for example these rather few posts of yours and my prior post summarizing the comparisons to all the major extant programming languages. You don’t seem like the sort of person who write many short posts back and forth ending up in a 50+ posts thread sidetrack. So far, your posts have been few, significant, and not arguing minutia back and forth. If you indeed wanted to discuss the minutia of OOP vs. FP, I would ask to shift to the extant issues thread #43 Functional Programming vs. OOP. But as it stands now, IMO your overarching feedback is appreciated in this thread.
Actually I don’t like telling anyone what they can or cannot post in a thread, which is why I am not that happy about Github’s features.  But for the moment, we’re stuck with this.

Regarding algorithm-and-data-structure, my code is chock-full of algorithms and data structures. I've just specialized them to what needs to happen to support the analysis rather than using generic algorithms and data structures. (I use those too when they suit.) Creating special-purpose algorithms that can skip a lot of work is one key way I achieve adequate performance.

I appreciate that not getting bogged down making everything generic for your special use case may be more expedient in some cases and maybe also more performant. But in any large scale project, this eventually comes back to bite you I think.

Anyway, in the context here, probably thinking about graph-construction and graph-traversal problems are better than my peculiar case. They're typically awkward to manage in languages that favor eager operations and immutable data structures, which Zer0 seems to tend towards.

Actually we’ve been discussing how to have lazy iterators.

C++ is way more brittle and dangerous when not using it according to C++11 etc best practices. I was speaking specifically about that. I generally don't use it that way because it is still less safe and more awkward than Rust--so why not use Rust?

Yeah I think a lot of people want to avoid C++ if they can. Which I think is sort of the point I been trying to make. AFAICT, one can tweak more performance out of C++ than Rust if every last ounce of performance is critical.

Regarding "left in the dust", I just mean in performance. Getting to within 1/3 of C++/Rust is pretty achievable with a lot of languages and techniques (Java, Haskell, Go, and OCaml all do, despite being radically different). A new language in that class would be great--I'm not disparaging your attempt at all, just pointing out that you really, really want to learn deeply from the languages that reliably deliver the highest performance if you want a language of that class.

Okay so we’re on the same page then.

Regarding readability, I don't find C++ terribly readable despite extensive experience, but I find Rust quite readable except when really exotic macros are used. It does take some training. In fact, I find Rust more readable, usually, because of the constraints imposed by lifetimes. It forces a kind of simplicity into the logic that you wouldn't otherwise necessarily get; and that logic always has to be right in every language anyway, Rust just tells you exactly what it is.

Well I can understand that. @keean seems to like to express everything literally (yet he wants inferred types, so that seems inconsistent). I am the guy who wants to express everything literally but I want to reduce the minutia that I have to express to the minimum. So I want to eliminate the entire concept of lifetimes. Lifetimes is to me an unnecessary detail that can hopefully (our untested idea) be avoided by paradigm-shifting the lifetimes to the Actor function lifetime.

The downside is that there are many correct (but more complex) things that are not expressible.

And that is one of my complaints. And I am positing we can entirely eliminate that constraint and eliminate the minutia. We’ll have to see though when we work through the details whether the idea really pans out. Too early yet to speak with any confidence.

Regarding my taking issue with your disrespect for other programming languages, that is because you have an idea, not a working programming language. It seems like a great idea, honestly, from what I can see. But existence is a very compelling feature.

No argument for me on that. Yeah my ranting is only to fire myself up to get myself motivated to actually implement. Who wants to waste time creating a PL! Nobody who is sane creates a PL! Always use what is already available instead! So yeah I need to be a little irrational to even be attempting this. I hope you understand.
I will quote Sylvan Clebsch the designer of the Pony programming language:

Ok, sure, a language. But never write a programming language. That’s like rule #1. Everybody will just tell you it isn’t needed and then argue about syntax.

 We may never actually succeed in completing anything. Life happens. People get sick or what not. There’s no ecosystem yet. Nothing. I agree with you!
Given I view the art of programming as an art of creative energy, I just really loath the concept of monotonous tedium of lifetimes. It’s akin to slave labor or being asked to work where you do the same repetitive motion over and over again. I imagine the analogy of an artist creating a painting and needing to record on a ledger every dab of paint they take from the palette resting on the easel to the canvas.

Many of the warts and awkward features found in other programming languages come precisely from the demands of existence. That doesn't mean you shouldn't try to do better / better-for-your-use-case. But without the "existence" feature, you shouldn't be surprised that other people may doubt that things will actually work out to be as universally lovely as you envision; and that those same people (e.g. me) may view expressions of disrespect a bit unfavorably.

Indeed I am well aware that I may be disappointed and that the result will end up having just as many warts as where I started from. Darn it!
That is why I am trying to figure out if I can make a pure syntax transformation to Scala for PoC rough draft to sort of at least test out the look & feel before attempting to invest more effort and time.
I wish I was still in my 30s and healthy. Progress would be much faster. I was a coding machine back then. Now I don’t know. We’ll see as soon as @keean and I stop talking about the design.

Finally, regarding the opinions of well-known people with reportedly high IQs: I am not impressed. We have enough experience here to make arguments based on evidence and logic rather than appealing to authority. I have a good deal of respect for ESR, but everyone makes mistakes, and the best way to avoid mistakes is to appeal to evidence and logic (including personal experience, but one must recognize the limited scope of personal experience as evidence). So let's cut out the middle-man and do that directly.

Actually I observed that some of the Rust experts who argued with him about his criticisms of Rust caught him making some errors on the minutia. But on the big picture stuff, I do look carefully at his opinions but I don’t always agree with them. For example:
https://www.quora.com/Do-advances-in-AI-mean-that-before-long-we-won-t-need-computer-programmers-as-we-ll-be-able-to-just-ask-a-computer-to-write-me-some-code-that-does-the-following/answer/Shelby-Moore-III
I just think he is very much correct that programmers will choose the PL that gives them the maximum productivity for the level of performance they need. You even just stated that is the reason you choose Rust over C++. And they will pick the PL that has the most reliable ecosystem. Those factors may explain why Python is popular.
Of course as I am sure you agree, there are other reasons PLs are used such as for example inertia, what large vendors adopt (e.g. Java on Android and Swift on IOS), what every browser offers (i.e. JavaScript) and what is taught in the schools, e.g. Java.
The main reason to create Zer0 is @keean and I both think there is an unmet need for typeclasses in a PL that is easier and more productive than Rust or Haskell. And a single PL that can be used to program the client and the server. Scala (with the excellent Scala.js) is almost that PL, except unfortunately Scala is complicated because it has too many paradigms in one PL. You may not agree because we have not yet convinced you to abandon OOP. And the typeclass implementation in Scala is I think lacking in a few areas. I think we can streamline and end up with a much more friendly PL that newbies can easily hold in their head.
If TypeScript had HKT and typeclasses, I probably would never have continued on to this point of attempting to create Zer0. Although that might have meant relegating me to Node.js on the server perhaps. So I am happy that didn’t happen, because I think the JS ecosystem is a mess.
Now with the recent “actor epiphany” the prospects are getting even more enticing. And I am (along with @keean and @sighoya’s peer review and critiques) also possibly inventing a completely new solution to the Expression Problem in the Subtying thread.
I also want to clean up warts such as no native unsigned integers in Scala (and the JVM).
https://www.quora.com/Why-are-programming-languages-constantly-being-invented-What-more-can-you-do-when-there-are-a-wide-variety-of-languages-already

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417865355
Original Date: Sep 1, 2018, 10:00 AM CDT

@keean wrote:

But reading Alex Stepanov's "Elements of Programming" I realised imperative programming could be beautiful too. If you have not read this book, you really should. This made me want to program in C++ again, but after a while the problems were too much, you needed too much template boilerplate to do seemingly simple generic things, and the language had too many kitchen sink features, tying to be multi-paradigm it fails to do any particularly well.

I agree with this. I want to produce a streamlined PL with only what we need to be able to do typeclasses and low-level imperative coding when we need it, all in one language without the overhead of trying to support OOP, template-level programming, etc.

So my original idea was 'C' like syntax, Haskell like type-system with type-classes. Having seen Rust which really was now following a similar path, I found I could not adequately express Stepanov's Elements in Rust, it's type system is too limited, and it has several, what I think are, mis-features.

Again I agree with this assessment, especially the following:

Rust has already done this to some degree, but I find Rust's type system to not be well thought trough and lifetimes although interesting get in the way, and I have similar feelings about Scala.

@keean and I both seem to want the same general concept for a PL as follows:

Have you read "Data Structures and Network Algorithms" by Robert Endre Tarjan? I think this is probably the best work on graph algorithms. These are exactly the kind of algorithms that I work with, and are particularly the kind of thing that I want to ensure the language can deal with efficiently. One of the goals of the language is to synthesize low level memory layout and pointer control (or references) with functional techniques like immutability and typeclasses […] I imagine a simple and compact imperative style language ('C' like) with functional abstraction tools that allow composition of larger programs. I am also a fan of logic languages (and a type system is a simplified logic language) so maybe even a logic language for meta-programming.

@keean did teach me that intersections makes the type system undecidable as follows:

Shelby had the idea of a language which in some way solves the expression problem by combining union types with type-classes. When I had explored union types previously it was in combination with intersection types, which I had abandoned as a useful type-system (due to lack of inference, undecidability, and poor separate typing).

Yeah I also have been pushing for the non-disjoint, structural union types since I think 2012, which I also pushed Scala to add and they have finally been added to Scala 3 (not claiming they added it because of me). Typescript, Flow, Ceylon and Pony have them also. But I’m proposing to use them in novel with ways with typeclasses to solve to some degree Wadler’s famous Expression Problem.

So I would say the language will be eager, but to say it favours immutability, the goal is to make programming with mutable data straightforward. In my opinion Haskell makes programming with mutable data too hard (mainly because monads don't compose), C++ doesn't even have immutable data.

Agreed again.

I am tending towards favouring referentially transparent immutable values, combined with mutable objects/actors. Shelby prefers pointers and 'C' like variables."

We’re still trying to sort out this issue in the Mutable Objects issues thread #32.

Original Author: @Ichoran
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417890819
Original Date: Sep 1, 2018, 5:14 PM CDT

@keean - I have only lightly browsed Tarjan's book, and do not own it; it was a long time ago and I don't remember it all that well.    Anyway,

lifetimes although interesting get in the way

I find the exact opposite.  Lifetimes are essentially just types in Rust, and things have lifetimes in every language, and sometimes you care about them; having them explicit is as refreshing and good for correctness as (ordinary) types are as contrasted with a language with weak dynamic typing.  Having no types doesn't free you from thinking about them; it just frees you to make a lot of uncaught mistakes so is only a good strategy when the problem is so simple you're unlikely to make mistakes.
I do wish there were ways to better express more complex lifetime relationships.
@shelby3 - I don't have time now to come up with a good OOP vs FP example; the case I mentioned was one where the OO inheritance model saved me a lot of work, I think, but it's too elaborate to easily be extracted into an example, and I don't have time for anything complex.
Regarding other points--I think we understand each other adequately now and largely though don't completely agree, and I'm not sure it's worth discussing more.  Good luck, though!

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417891817
Original Date: Sep 1, 2018, 5:31 PM CDT

@Ichoran

I find the exact opposite. Lifetimes are essentially just types in Rust, and things have lifetimes in every language, and sometimes you care about them; having them explicit is as refreshing and good for correctness as (ordinary) types are as contrasted with a language with weak dynamic typing. Having no types doesn't free you from thinking about them; it just frees you to make a lot of uncaught mistakes so is only a good strategy when the problem is so simple you're unlikely to make mistakes.

True, except a garbage collector frees the programmer from having to worry about lifetimes.
Personally I like the idea of statically proving code safe, what I find problematic is Rust's type system. Lifetimes are not treated as 'first class' types, there are limitations on what you can do with them and how you can pass them, partially apply them etc. I think linear type systems are interesting (Rust has an affine type system). What I don't like about Rust is how the type system is realised and presented to the programmer, for example the borrow-checker seems to be an informal heuristic. I think there is never any need to annotate lifetimes, the only time the programmer ever need be aware of them is when they make a mistake. I think Alias Types provide an interesting approach.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-417893051
Original Date: Sep 1, 2018, 5:59 PM CDT

@Ichoran, @keean:
Augmenting @keean’s response about lifetimes, and @keean please correct me if the following assessment is incorrect. @keean has very much been interested in lifetimes. I think I was the one who pestered him to consider AMM more as an option. But I think he also observed that there are algorithms that couldn’t be expressed (c.f. also) with Rust’s lifetimes, which you @Ichoran also admit. And @keean doesn’t like breaking out of the type system and creating unsafe code. So @keean wants static typing but he wants it to not have corner cases. @keean is a purist about typing.
Whereas, I started out wanting the typical young generation plus mark-and-sweep GC of Java, Go, JavaScript and Haskell. But @keean and @NodixBlockchain explained to me that GC for example on Java is difficult to control and leads to difficult to resolve performance issues. So that instigated me to read a book on GC designs and also read research papers on the state-of-the-art GC designs. From that I began brainstorming about how we could get a balance between Rust lifetimes and those extant GC designs. First idea I had was segregate AMM into a young generation collector for each thread both short-lived objects and automatic reference counted (ARC) for long-lived objects that are shared between threads. Sort of a mix of what Nim has and similar to a new model proposed for multicore OCaml. This would have eliminated the write barrier for the short-lived objects that need to be on the heap, thus bringing it closer in performance than Rust. But this would require the programmer to manage a bifurcation on objects based on allocation lifetimes. So that was not really ideal.
FYI, @keean the Pony white paper Deny Capabilities for Safe, Fast Actors compares Pony’s model to Rust’s lifetimes in §6. Related Work on pg. 8. They mention linear types.
Then @keean suggested Actors (actually he originally suggested them in 2016) when I raised the issue that parallelism is going to become very important to scale Moore’s law as we move into the massively scaled multicore. Initially @keean resisted, but then the 64 hyperthread AMD Threadripper was announced and I think @keean warmed up to that version of the future.
From the Actor idea, I realized heck I could refine my prior idea and just segregate the object lifetimes based on whether they persist beyond the Actor function’s lifetime. The programmer has to form this segregration anyway to make the Actor paradigm work correctly (and to support a future software muticore HW designs that will have software assisted cache-to-cache transfers based on Actor message sends so that multicore can easily scale to 1024 cores synonymous with the experimental Rigel design). So we’re actually promoting a new HW design not just a new PL. Actually I realized now that Pony is also compatible with that new multicore massively scaled paradigm. So I will be more carefully studying and relating the Pony design to my idea in the Parallelism issues thread #41.
From that we eliminate Rust’s lifetimes and we get back full algorithmic flexibility (the only inflexibility is has to be expressed with Actors but you need this anyway to scale multicore) while getting roughly the same performance as Rust because deallocation is only reseting the bump-pointer-heap pointer after the Actor function returns and no tracing required (so it should be more performant and easier to program than Pony because Pony employs tracing and conflates object types for those that are shared and not-shared, although we’ll also offer some of this power for experts it won’t be as necessary to use it as is forced in Pony). So therefore if that comes to fruition as hoped, that would prove that Rust’s lifetimes are an entirely unnecessary abstraction and tedium, at least in the context of an Actor programming paradigm.  Again nothing that exists today and some programmers comment Pony is convoluted for them to learn.
So let’s be clear that I do want static typing. I just don’t want unnecessary and superfluous static typing. I had a vision that I could make Rust’s lifetimes superfluous by paradigm shifting. And maybe I have succeeded in my goal, but now we must implement to find out. Or at least think more deeply about my design idea, which I presume we will be doing in the Parallelism issues thread #41.
Also let me say that it’s possible that Pony’s tracing could end up being more performant than my idea because ARC is less performant than tracing, but in my proposal for the “actor epiphany” we will have no tracing at all for young objects that don’t escape the Actor function’s lifetimes, so I lean towards my idea will be significantly more performant than Pony.
I would like to read what @keean thinks about this summary I have written in this post?
P.S. I’m delighted that you instigated us to write down these summaries of the history and our views. So that later on if we succeed, then it will easy for others to access of a summary of events. To digest all the issues threads over here would be a Herculean task.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-418807560
Original Date: Sep 5, 2018, 12:08 PM CDT

Proposed Specification for Go-2.0 Generics (Parametric Polymorphism): https://go.googlesource.com/proposal/+/master/design/go2draft-generics-overview.md

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-419049446
Original Date: Sep 6, 2018, 5:45 AM CDT

@keean wrote:

Proposed Specification for Go-2.0 Generics (Parametric Polymorphism): https://go.googlesource.com/proposal/+/master/design/go2draft-generics-overview.md

I read your post at the Go issues thread about that.
I didn’t have time to digest that document fully. @keean discussed that with me in private messaging and he explained to me that is somewhat similar to C++ Concepts. My understanding so far is that it is similar to typeclasses in that generic interfaces can be required (e.g. at function call sites), but the implementations are not scoped by typeclass (i.e. concept) name and instead whatever is in scope qualifies for the implementation. We think this is a bad idea because as we discussed in the Modularity and Subtyping issues threads, we want to highly discouraging injecting different variants of implementations for the same data types in different contexts because it can lead to strange (perhaps even dreaded Hiesenbug) bugs. We think the programmer should have more scoping and nominal control over implementation selection. And use variants only very cautiously. This is even worse in their design because they match structurally and not by matching to concept name. Haskell infers structurally typeclass requirements but implementations are matched nominally by typeclass name.
Also Go’s proposal seems to create two ways of doing typeclass-like interface: their existing interfaces which are not generic and these new concepts which are generic. There was some issue with operator overloading and backward compatibility that apparently motivates this duplicated overlap of functionality.
AFAIK, the Go proposal has no associated types so it can’t express some semantics. I think it would be impossible to have associated types with their proposed structural only implementations selection.
So apparently the generics proposed thus far for Go version 2 are not going to be what we would want.
EDIT: Kotlin is also (at least initially) not offering associated or abstract types with their HKT approximation (c.f. also) of functional programming (apparently based on KindedJ). Appears to also be some rough approximation of typeclasses.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-419289386
Original Date: Sep 6, 2018, 8:13 PM CDT

@Ichoran
Wow I think you arrived on the scene at the perfect moment and inadvertently helped us make a huge design decision correctly!
We essentially need to adapt Pony to low-level coding (as well as retaining its high-level), to typeclasses instead of Pony’s OOP anti-pattern, improve on Pony’s lame exceptions, and consider my “Actor epiphany” as a performance enhancement.
And this looks (c.f. first link for why) to be a C-killer as well as being the correct mainstream language for the future.

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-419643769
Original Date: Sep 8, 2018, 8:56 AM CDT

@shelby3 I have started looking at Pony, and you are right that Pony seems to be implementing the no l-value/r-value idea correctly, as well as the single-threaded actors with immutable sharing (and exclusive access transferring).
What it gets wrong in my opinion is going for the OO model.
Here is an interesting article on composition. Note category theory is all about composition:
https://julesh.com/2017/04/22/on-compositionality/
What do you make of those points?

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-419645191
Original Date: Sep 8, 2018, 9:18 AM CDT

@shelby3 Pythons popularity is rising: https://www.i-programmer.info/news/98-languages/12116-python-displaces-c-in-tiobe-index.html

Original Author: @keean
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-419695221
Original Date: Sep 9, 2018, 1:57 AM CDT

@shelby3 >Freudian slip? I will delete this after you edit that.
Phone spelling auto-correct, it was obviously programmed by a tailor.

Original Author: @shelby3
Original URL: https://github.com/keean/zenscript/issues/35#issuecomment-419695258
Original Date: Sep 9, 2018, 1:58 AM CDT

OOP Is Not Class Inheritance
@keean wrote:

What it gets wrong in my opinion is going for the OO model.

Because I will argue below that class inheritance OOP is not really what OOP is. When we say OOP is an anti-pattern (c.f. also, also, also “Edit#2”, also), what we really mean is that class inheritance is an anti-pattern.

Here is an interesting article on composition. Note category theory is all about composition:
https://julesh.com/2017/04/22/on-compositionality/
What do you make of those points?

I think his blog statements are correct (have not delved into his research yet) and his articulation is equivalent to my conceptualization. For example my recent point in the Subtyping issues thread #8 (which was in the context of adding some modularity to multi-parameter typeclass existentials) pointing out that dynamic total orders (don’t exist because they) lack degrees-of-freedom so thus lead to emergent discord. Your source wrote:

More generally, I claim that the opposite of compositionality is emergent effects. The common definition of emergence is a system being ‘more than the sum of its parts’, and so it is easy to see that such a system cannot be understood only in terms of its parts, i.e. it is not compositional.

[…]

As a final thought, I claim that compositionality is extremely delicate, and that it is so powerful that it is worth going to extreme lengths to achieve it. In programming languages, compositionality is reduced by such plausible-looking language features as goto statements, mutable global state, inheritance in object-oriented programming, and type classes in Haskell. The demands placed on game theory are extremely strong (and of course, these demands have been written retrospectively): seeing a game as something fundamentally different to a component of a game such as a player or outcome function breaks compositionality; so does seeing a player as something fundamentally different to an aggregate of players; so does seeing a player as something fundamentally different to an outcome function. Open games include all of these as special cases.

Famous programmer Alan Kay wrote on Quora something that is incomprehensible, but what he really means is time can’t exist if thermodynamic processes could be reversible (because that would be the multiverse where past and future are indistinguishable). So the implication is the same point I made against total orders. We can’t have immutability everywhere. Referring again to your source cited above and his point about interfaces, we can have immutability as an interface between threads as we are exploring for Pony’s Actor design which was discussed up-thread and in the Parallelism and Mutable Objects issues threads #41 and #32. Ditto our work to add some modularity flexibility to typeclasses.
Alan is correct that object orientation is not antithetical to the compositionality of functional programming, but he fails to state (or maybe doesn’t even realize yet) that such object orientation compositionality can’t be achieved with the class inheritance that causes Wadler’s Expression Problem (and break degrees-of-freedom in other ways). Instead the compositional OOP paradigm (as Alan describes OOP) is Hewitt’s Actors for the reasons you eloquently summarized from our recent discussion! Alan states:

John’s desire to have both unambiguous reasoning about situations and actions and causes and to have changes over time led to his powerful ways to deal with these.

[…]

Yes, the key is to not let the CPU determine time relative to your models.

Alan wants objects to have behavioral interfaces, opaque (aka encapsulated) state, and react to their environment over the time domain, but not bind the objects to any a priori total ordering of objects in time. That is exactly what the Actor model does. There’s no guarantee whatsoever about time ordering or delivery of messages between actors (aka objects in this context).
Btw, I did not go searching for Alan’s Quora answer. Quora spammed my email Inbox with it. So the universe wanted me to write this post today. I think y’all conspired with Quora.
From Alan’s answer I discovered via his link an answer by Henry Story that explains that behavioral interfaces are a co-algebra (aka co-induction) and functional programming is an inductive algebra. So they’re categorical duals and we know this from Wadler’s statement of the Expression Problem, that functions can add new operations (functions) to existing types but not add new types to existing functions. And interfaces can new data types to existing interfaces but not add new operations (functions aka methods) to existing interfaces. Typeclasses enable adding new operations and new data types because we don’t bind these until the typeclass bound of the function call (aka use/application) site. Thus typeclasses have more degrees-of-freedom than class inheritance w.r.t. to the Expression Problem although they can also be anti-modular in terms of consistency (aka canonization/canonicity) of (the abstract algebra of) instances.
I wrote the following Quora answer:

Because most people incorrectly think OOP is about conflating data types with interfaces. Thus w.r.t. Wadler’s Expression Problem, class inheritance and functions are categorical duals. But once we correctly define OOP to not be the anti-pattern of class inheritance (which prematurely binds interface to data), then we discover that OOP and functional programming are mutually compositional paradigms. More details at the following link.
OOP is really Actors not Class Inheritance