You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is less verbosity the only main benefit of typeclasses over just inputting a set of functions?
Typeclasses can do where bar is a member of Function:
foo(x: A) where Function<A>
bar(x)
Without typeclasses we could do:
foo(x: A, y: Function<A>)
y.bar(x)
I understand @keean proposed other functionality for the where clause, but as for the main functionality of providing pluggable (i.e. dependency injection) interfaces, what is the big benefit that justifies creating a new language?
I think the question is why not just pass all the interfaces as arguments? You can of course do this, after all type classes are implemented as implicit dictionary arguments.
The advantage of type classes is that they are implicit, hence you have shorter more readable function signatures. They can also be inferred, so you don't have to explicitly type the constraints. You don't want to have to explicitly thread y through every function call where you pass type A because you might want to call bar at some point. Further when you realise you need to call bar on a value of type A you don't want to have to refactor every call in the program to pass an implementation.
Thanks for the reply. I appreciate those advantages. I am just thinking about near-term priorities. I think achieving the "perfect" language is a major endeavor that needs a significant amount of funding and time. I've been thinking that your goal is maximum genericity (i.e. the ability to write some code that can parametrized over many types and uses cases) and minimum explicitness (although in other cases you argue for more explicitness where I argue for less).
My near-term goal is not that. That is far too much for me to think about and digest at this point. I am wanting some improvements on JavaScript so I can go use the same code base both on the client and the (Node.js) server. For example a cryptographic library.
promises (ES6 has this)
anonymous unions (TypeScript has this)
guards or match (TypeScript has this)
integer types (u8, u16, u32, 64)
structures (handles all the boilerplate on top of TypedArrays and ArrayBuffers)
everything as an expression
Python-like code block indenting instead of curly brace delimited blocks
I've been thinking about achieving a much simpler transpiler to TypeScript that can meet those near-term objectives.
Later can experiment with moving to higher levels of genericity and less explicitness, if warranted by the use cases that are prominent. And with more funding and momentum behind the initial simpler transpiler.
Further when you realise you need to call bar on a value of type A you don't want to have to refactor every call in the program to pass an implementation.
That works if you only allow every data type to implemented only one way for each typeclasses, but as we discussed in the past (with @skaller) that has limitations also. Otherwise, you could get unintended implicit functionality. So explicitly passing has its benefits.
Edit: Dec. 4, 2017
integrated low-level C-like coding with the high-level language and memory allocation mgmt (aka GC)
Perhaps we will eventually think of some low-complexity abstraction that gives us non-marshaled integration for tree data structures between HLL and LLL, and perhaps we can even prove borrowing and lifetimes statically. But I suspect we can not without heading towards the complexity of C++ and Rust.
I would not write a web service in 'C' or even 'Rust' ever, this is really ideal ground for a garbage collected language. I would use Node (TypeScript), Django(Python) or 'Go'.
My view is there needs to be a high-level-language and a low-level-language that share enough memory-layout and data-structure that data can easily be passed backward and forward.
My quest in @keean’s repository has to been to get the advantage of GC and perhaps some other aspects of FP such as first-class functions and closures in a simple language that also can do some low-level things such as unboxed data structures. To further erode the use cases where C/C++ would be chosen, to relegate that clusterfucked C++ language tsuris (and perhaps Rust also but the jury is still out on that) more and more to the extreme fringe of use cases. For example, It is ridiculous that I must employ a cumbersome Node.js Buffer library to build an unboxed data structure.
[…] so that code which would otherwise be written in for example TypeScript and C/Emscripten would not have to be rewritten later in Lucid. More optimum compiled targets could be developed such as WebAssembly which leverage the full power of the design laid out above, including more optimum compilation of the C-like code. One of my test use cases would be to translate my Blake C code to Lucid.
Regarding one implementation per class, that is the advantage of type classes :-) If you want more you need to pass an object or function (because you want the implementation to depend on the value of the object not the type).
To me it seems you can do nearly everything you want in typescript, so why not use it? I am working on a project in typescript at the moment, and for the ES6 stuff plus types it's good enough.
Regarding goals, I agree maximum genericity is my goal, and minimum boilerplate. However I also want to prioritise readability and understandability over conciseness, as you spend more time debugging and maintaining code than we do writing it. However I am happy for explicitness to be optional, which includes type-inference (type annotations optional) typeclass constraint inference (so constraints/bounds are optional). The only place we need types would be modules, where we want a stable interface that enables separate compilation.
Regarding one implementation per class, that is the advantage of type classes :-) If you want more you need to pass an object or function (because you want the implementation to depend on the value of the object not the type).
If one universal implementation for each universal class was an absolute advantage or even feasible, then we wouldn't need scoping for (type) name conflicts in programming. It is impossible to guarantee one global implementation for the universe, because total orders don't exist.
I am not arguing that it can not be a feature that is desirable in some cases in a partial order.
To me it seems you can do nearly everything you want in typescript, so why not use it?
I guess you didn't read my list above, or didn't realize that the items I listed which TypeScript doesn't do are extremely important for the work I need to do.
However I also want to prioritise readability and understandability over conciseness
Ditto myself. I take this even further than you as I am thinking all types shall be explicitly declared, except in an assignment where either the destination or source is explicit, but not required for both. Since passing function arguments is assignment, then anonymous lambda functions can be implicitly typed by the function's argument type.
I thought typescript included ES6 features, and has integer types as well as strucures (classes). The only one it does not have is "everything is an expression" I think, so it seems close to what you want?
Regarding type classes, it is clear that each function can only have one implementation for any given set of argument types. In other words we can only define one implementation for (+)<Int, Int>
Regarding type classes, it is clear that each function can only have one implementation for any given set of argument types.
Disagree (and my reason was already stated). But I don't want to repeat that argument now. I have said all I want to say about it for now. It isn't a priority issue for me at the moment. We can revisit that later if ever get to the point where I want to implement a "typeclass" language.
In 2016, I wrote some very complex (untested) JavaScript code to improve on that concept and provide a polyfill. But it has some drawbacks. I need to revisit that code to refresh my memory.
I looked again at Nim, but it is getting too far from JavaScript (wants to be a systems language competing with C, C++, Rust, and Go). For example, lose JavaScript's Promises, ArrayBuffers, etc..
And it doesn't offer 64-bit integer support on JavaScript.
And I'd much prefer the JavaScript output is recognizeable, so the JavaScript debugger can be effectively used.
I think I envision a robust way to transpile from a syntax and type system I want into comprehensible TypeScript. For example, I can use TypeScript's getters and setters to access the fields by name of a DataView on an ArrayBuffer. When not packed into a binary ArrayBuffer, 64-bit integers can be represented with TypeScript's tuples.
It may be slower than normal JS objects due to packing issues, or it may be faster due to non-string property lookup. It certainly seems the right way to process binary network data, but I would probably use native JS objects everywhere memory layout is not important.
nodejs can mmap files from JavaScipt which would work well with this too. You don't have too many choices in-browser, you are limited to Blobs with a streaming interface, and the file IO API is not supported by all browsers.
Most in the blockchain realm use a systems programming language with good binary control, such as C or C++. But this is painful in other aspects. There is a lot of verbosity that isn't always needed. Need complex libraries such as Boost for asynchrony, etc... Do not have GC by default, etc.. I think it is overkill.
I am searching for a better balance. Rapid coding with less needless verbosity, yet still be able to handle binary data and integer math without verbose APIs. And also some (explicit) typing for correctness checks and better readability (open source).
I also think that is the problem with C++ is that it tried to be Java + C instead of just a better C and leave the high-level stuff to something better designed for high-level.
The problem with C++ is that it eats anything what smells like a feature.
5:16 "You know who likes teamwork? Incompetent people."
One of the truest statements you'll ever hear in life.
[…]
There's a reason why lazy high school kids want to work in groups instead of by themselves during projects. One person does all the work, the rest just tag along.
Note he did not mention mutual defense. That requires teamwork and is essential to surviving periodic societalcide.
I also think that is the problem with C++ is that it tried to be Java + C instead of just a better C and leave the high-level stuff to something better designed for high-level.
The problem with C++ is that it eats anything what smells like a feature.
Yeah but I think my point remains. They add those features for low-level performance optimizations or high-level programming capabilities, because they want to be both a low-level performant and a high-level PL.
I know you all know the following but I will explain for other future readers.
Yet attempting to be both makes it more complex to excel at either end of the spectrum.
FAQ: In 6-12 months you can become proficient, in 3 years you are a local mentor. Some people won't make it - those can't learn, and/or they are lazy. Changing the way you think and what you consider "good" is hard.
FQA: In 6-12 months you can become as proficient as it gets. It is impossible to "know" C++ - it keeps surprising one forever. For example, what does the code cout << p do when p is a volatile pointer? Hint: as experienced people might expect, there's an unexpected implicit type conversion involved.
While some people are better at learning than others, it is also true that some languages are easier to learn and use than others. C++ is one of the hardest, and your reward for the extra effort spent learning it is likely to be extra effort spent using it. If you find it hard to work in C++, trying another language may be a good idea.
Before you subvert the way you think about programming and your definition of "good" in this context to fit C++, it might be beneficial to ask the common sense again. For example, does compilation time really cost nothing (is development time that cheap, are there compilation servers with 100 GHz CPUs around)? Is run time really priceless (don't user keystrokes limit out speed, how much data are we processing anyway)? How efficient a C++ construct really is in your implementation (templates, exceptions, endless copying & conversion)? The reasoning behind C++ may be consistent, but the assumptions almost never hold.
Learning OO has nothing to do with learning C++, and it is probably better to learn OO using a different language as an example. The OO support in C++ is almost a parody on OO concepts. For example, encapsulation is supposed to hide the implementation details from the user of a class. In C++, the implementation is hidden neither at compile time (change a private member and you must recompile the calling code) nor at run time (overwrite memory where an object is stored and you'll find out a lot about the implementation of its class - although in an unpleasant way).
One example is pointer arithmetic which is enables low-level optimizations in some cases yet at the high-level it breaks encapsulation which prevents buffer overruns.
Another example for C++ is the concept of r-values and all the optimization complexity to prevent copying of return values:
Yet templates are Turing-complete so we end up with template metaprogramming which makes it unpredictable what templates even do, i.e. you have to run the compile to see if they even halt or if they resolve in a predictable manner as you refactor code for maintenance and new features.
AFAICS, the complexity clusterfuck is significantly due to the goal of trying to merge high-level and low-level.
I’ve read that even the creator of C++ doesn’t understand all of the language thoroughly.
I used C++ to code CoolPage and Art-o-matic around the turn of the century but that was before much of the complexity became mainstream such as heavy use of template programming and the Boost libraries, etc.. I thus never learned all that. I essentially stopped using C++ when I halted all development of those applications circa 2003. I transitioned to JavaScript, PHP, ActionScript, HaXe, then directly Scala on the tip from @jdegoes (entirely skipping learning anything about Java). Before C++ I was highly expert with C and Intel and Motorola assembly. Learning Scala exposed me to Haskell, which I resisted because it was so foreign to me given I had done low-level imperative programming since age 13 starting from Apple II BASIC (or let’s say it made my head hurt because I didn’t get it and nobody was explaining it circa 2009 in a way that would click for me). I wrote much of WordUp in the late 1980s in 68000 assembly until I discovered C. Then I went on a tear with my new found productivity with C producing WordUp 3.0 (together with my employee/colleague Mike Fulton) before burning out. I remember consuming the K&R C book afair in one evening and being on a programming tear the next morning. C just made sense to me coming from assembler because I had consumed a Radio Shack book on Microprocessors at age 13 when I was laid up in bed from a high ankle sprain from football with nothing else to do. So I was coming into programming from a desire to want to understand how the hardware functioned. I enrolled at the University to pursue BSEE with a math minor but dropped out when I was spending 10+ hours a day in the library doing my own research and programming at night leaving nearly no time for studying given I was also heavily in drinking and sports. Up until my 3rd year, I maintained a 4.0 GPA by not attending classes and cramming on the night before exams.
I’m not a Rust expert having not used it to write any code (other than maybe a few toy snippets). I’ve read the documentation and read a considerable amount of expert discussion (from both sides including the defenses from the Rust fanboys[1]). Rust seems to have many cases where the desire for both high-level and low-level features create complexity that wouldn’t be the case if it has just focused on being a data race safety and “zero-cost abstraction” low-level language. For example the complexity around closures which @keean raised. Others such as the Rust discussion I cited up-thread have enumerated other examples. Again I will reiterate that Rust seems to be an improvement over C++ complexity, at least if total program order lifetime borrowing is not factored in, but they can’t be factored out as that is the essential USP for Rust.
Rust is only slightly more performant on benchmarks than C/C++, yet it is considerably more performant than Go:
I might be interested in a low-level PL that stripped out the type classes, closures and all the obfuscating, implicit conversions cuteness from Rust and focused on being a faster and more safe C.
If Rust had focused on something I really need, I might be a fan. But as it stands right now I do not need the tsuris of Rust for high-level programming. And I don’t need for low-level programming, the complexity of Rust’s ecosystem and high-level conflation. I want the next low-level language I will master after C to be stable and hyper-focused on seamless integration (without FFI marshalling overhead) with whichever high-level PL has formed a market Schelling point.
For example I might be willing to tolerate the lifetime borrowing complexities and annotation noise (given the apparent improvement in performance and some safety guarantees is provides) if there wasn’t the conflation with the high-level feature of type parameter polymorphism (aka generics). A low-level language doesn’t need type parametrisation.
I suppose after creating a high-level programming language then I need to tackle creating a fork of Rust and new low-level focused PL. Ha. Dreaming, if I was only 25 years old again I could possibly do that. No chance of that anymore. 🌔
Does anyone else have a different perspective?
[1] Intelligent guys who make intelligent technical arguments, but they miss the big picture point. They are so religiously attached to the notion that Rust is some hypersonic, hypersafe killer app that will soon conquer the world. And that unrealistic (“aspirational class”) ambition colors and feeds their confirmation bias blindspot — the world doesn’t need a conflation of high-level ease with low-level performance resulting in a complex Frankenstein pita.
For some examples of more powerful ways to integrate pattern matching into OO languages, without breaking data abstraction in the way that functional pattern matching typically does, see PredJ and JMatch.
Have not looked at this yet.
Apologies to ask you for extra effort, but is it possible to elucidate the significance (beyond just being some syntax sugar) and essential concept for JMatch without myself needing to try to figure out how balancing red, black trees, CPS, interation, ADT, forward and backward model abstractions, etc all play together to offer something new and important? I suppose this somehow integrates/unifies iteration and pattern matching in some holistic model. Could you help me grasp it?
Can I somehow receive a tl;dr that creates that “a ha” moment which inspires understanding?
I just don’t have the time nor mental energy to digest all the verbiage of the paper. Maybe someone else here does? I got through the first page and half and it was not getting me close enough to holistic understanding to keep me motivated to push on. Is there any way to get more directly to the point or is the model that complex such that some degree of understanding can’t be conveyed with a paragraph and a simple example? I can’t even decipher what the code example is supposed to be accomplishing. I’m not detecting the key point of it from the example on the home page.
Probably someone who has more experience with the specific use case of your example will have more context with which to form an understanding. I don’t think I’ve ever coded red, black trees. Of course I am familiar with the concept of walking the branches of a tree.
Also I am not proficient in Java although I am in Scala so I do not know if the following is legal Java code:
booleancontains(Objectelem) {
if (left != null && value < elem) returnleft.contains(elem);
if (value.equals(elem)) returntrue;
if (right != null && value > elem) returnright.contains(elem);
} iterates(elem) {
foreach (left != null && left.contains(Objecte)) yield e;
yield value;
foreach (right != null && right.contains(Objecte)) yield e;
}
Thus I don’t comprehend the semantics of the iterates(elem)… there — when and where is it invoked? I don’t recall seeing that syntax in Scala, although perhaps I missed it or have forgotten over the years since I last used Scala.
Also there is a reference to logic programming which I vaguely assimilate although I have never used Prolog for example. @keean has. I am aware of the backtracing feature in Prolog because Keean has mentioned it for example in the context of a model of typeclass resolution and type system unification algorithms.
I understand the concept of a predicate for filtering a set or defining a domain:
I understand by forward and backward you may be referring to the relation between the domain and codomain?
Looking at this with a clearer mind and more sleep and ignoring the white paper and focusing solely on the code examples on the JMatch homepage:
AFAICS the “Containment predicate is also a tree iterator” unification of contains and iteration of contained can be abstracted with an interface that provides a new iterator and a findNext. Thus contains instantiates a new iterator and invokes findNext on it returning the boolean of whether was found. Whereas iteration of the contained will repetitively invoke findNext.
The “Balancing a red-black tree with pattern matching” is a clear example of how constraints expressed as pattern matching are much more coherent and succinct compared to the equivalent conditional expressions code required to accomplish the same algorithm. I presume the JMatch compiler compiles the pattern matching syntax into the optimal conditional expressions code.
The “A client loop selecting elements matching a pattern” exemplifies that pattern matching on iteration more coherently and succinctly expresses the invariants of the algorithm compared to the equivalent conditional expressions code required to accomplish the same algorithm.
I don’t know if there is additional significance or novelty to your work, because I have not tried again to digest the research paper.
I do agree with the utility of these features and would be interested to explore them.
I think you're getting the gist of it. Pattern matching can be viewed as a kind of reverse-mode computation in which values are deconstructed instead of constructed. JMatch allows any method to declare reverse modes that can be used to pattern-match (one or more times) against a set of arguments. What it means to match can be specified with a logical formula, so pattern-matching is not tied to the concrete representation of values as in Haskell, OCaml, etc.
[…] Rust seems to have many cases where the desire for both high-level and low-level features create complexity that wouldn’t be the case if it has just focused on being a data race safety and “zero-cost abstraction” low-level language. For example the complexity around closures which @keean raised. Others such as the Rust discussion I cited up-thread have enumerated other examples. Again I will reiterate that Rust seems to be an improvement over C++ complexity, at least if total program order lifetime borrowing is not factored in, but they can’t be factored out as that is the essential USP for Rust.
Rust is only slightly more performant on benchmarks than C/C++, yet it is considerably more performant than Go:
I might be interested in a low-level PL that stripped out the type classes, closures and all the obfuscating, implicit conversions cuteness from Rust and focused on being a faster and more safe C.
If Rust had focused on something I really need, I might be a fan. But as it stands right now I do not need the tsuris of Rust for high-level programming. And I don’t need for low-level programming, the complexity of Rust’s ecosystem and high-level conflation. I want the next low-level language I will master after C to be stable and hyper-focused on seamless integration (without FFI marshalling overhead) with whichever high-level PL has formed a market Schelling point.
For example I might be willing to tolerate the lifetime borrowing complexities and annotation noise (given the apparent improvement in performance and some safety guarantees is provides) if there wasn’t the conflation with the high-level feature of type parameter polymorphism (aka generics). A low-level language doesn’t need type parametrisation.
I suppose after creating a high-level programming language then I need to tackle creating a fork of Rust and new low-level focused PL. Ha. Dreaming, if I was only 25 years old again I could possibly do that. No chance of that anymore. moon
The creator of Node.js has created Deno to correct the accumulated crud in Node.js. He remarks that Go is a more performant PL for a server but the JavaScript server still has some valid use cases (e.g. sand boxed):
Ryan Dahl mentions the clusterfuck of module importing, require, package.json, index.js, etc., which BTW was what I told the Node.js project to simplify and move to ES6 modules deprecating that crud and got me banned from their Github issues some years ago. Design-by-committee can never shred the crud and start anew. Ryan also mentions that his design wisdom has improved with age. 😜
Ryan loves TypeScript, which furthers my expectation that an improvement on TypeScript would be popular.
The Node.js replacement Deno is advocating Rust as the low-level language for the (probably much) less than ~20% (i.e. Pareto principle aka power-law distribution of resources) of your code that needs to be fast.
They’ve coded the entire Deno executable in Rust, but that’s understandable given all of it needs to be fast given it is a system layer. Their mainstream options were essentially C++ or Rust, so it makes sense they chose Rust per my prior comments in this thread. But for the majority of users who just want to code some lower-level subroutines in a high performance, low-level PL, then the conflation of higher-level Rust features with the lower-level requirements, adds unnecessary complexity and unsoundness.
EDIT: indeed I was correct that Ryan considered C++ as the other option:
My current priority (if I can get healthy enough to consistently think clearly) is to improve upon TypeScript for the high-level language part.
Again Scala was a breath of fresh air for me and a significant improvement over what I had used before it. But then I ran into the aforementioned issues of its non-principled, “kitchen-sink” design (being a jack of all trades and not hyperfocused on a superior paradigm, removal of implicit conversions, simpler and more sound type system, etc), and Scala's lack of unsigned and sized integers, lack of non-disjoint, structural (aka anonymous) unions (Scala 3 adds these), the crap concurrency support with inability of Scala.js to integrate with async / await, lack of green threads on the server, the monolithic GC for all threads (ditto Go and Node.js/Deno which is another aspect I want to improve), etc...
Pony is/was an experimental PL that provided some interesting insights into what is and isn't needed for an Actor-like partitions (ALP) paradigm, but I posit it’s not the right design and will very likely die on the vine. Sorry to be so frank and overtly opinionated, but they didn’t incorporate green threads and their GC scheme is not performant for intense sharing and if you're not going to do intense sharing then the reference capabilities do not need to be as complex and I posit the GC scheme can be made radically more performant with a paradigm-shift.
P.S. Yet another example of how design-by-committee creates a clusterfuck, setting up Unicode character shortcuts for ellipsis, right and left quote marks in Kubuntu is byzantine, although Shift+Ctrl+U+[code] worked fine in Mint Mate and on standard Ubuntu:
(Also in the SO survey I see that […] @keean our age bracket is only 3 – 4% of programmers.)
Ryan also mentions that his design wisdom has improved with age. 😜
“Uncle” Bob Martin points out in the “The Future of Programming” that the rate of increase of programmers is growing so fast that more than half of the programmers have less than 5 years experience and are young. Apparently the younger the programmers, the more heavily tilted towards males the programmer demographic is.
The implication is that PLs and frameworks are becoming less disciplined because the programmer demographic is becoming less disciplined (young age, low-level of experience, high testosterone, more energy than wisdom, etc).
Despite @shelby3's accurate understanding of my technical points, which I appreciate, he's misunderstood my non-technical points almost without fail (e.g. mistaking the logical thoroughness of breaking down a situation into cases as an implicit accusation of the negative parts of each case). I do want to clarify that I am an extremely strong proponent of freedom of expression, just not of having ideology litmus tests backed up by strong rhetoric to try to drive away people with valuable technical skills.
@keean - I don't know if you've programmed in Rust, but along with your comment about Rust being a most-loved language, it's useful to know why Rust is most-loved. It's not just because they've learned from history in a number of areas (e.g. crates, docs, community) and created either a best-in-class or a refreshingly-nice-alternative (for those people who want to use Rust). It also solves a problem that many systems-level programmers have, and for which there were zero good solutions, which is how to use mutability safely.
Not every new language has to allow the same thing. Immutability is another approach, though if you think the borrow-checker is annoying, try putting someone up against either the borrow-checker or the entire field of pure FP ("so, you see, just use State, except no not really, use IO instead...but anyway, monads don't commute, so we need monad transformers (uh, actually, a lot of them), except that final tagless is a better way to go, except..."), the borrow-checker both admits much higher performance without insane compiler wizardry, and is a lot easier to swallow. (Admittedly it solves many fewer problems, but for that problem it's absolutely brilliant.)
I've done a lot of programming in C++ over the years. Although I occasionally get frustrated with Rust not allowing something that is "obviously" safe, it is VASTLY outweighed by the number of times that it stops me from doing something obviously "safe", meaning actually not safe at all, and I didn't think it through carefully enough. It's almost as big an advantage for me as having strong types instead of none.
My point in writing this is not to say that you have to bite off the whole borrow checker thing. My point is that Rust has that covered, and it's a very compelling feature, and if you go head-to-head with it, you have to either come up with something incredible, or you will lose. (That goes for multithreading, too...it's not enough to be 30% better at multithreading if Rust can beat you by 2x on single threads.)
If you don't need to go head-to-head, though, because you're targeting a different area (e.g. not the-very-fastest-speed), then the design constraints are a bit less acute. Competing with Go is vastly easier than competing with Rust on a technical level. (Sociologically it may be harder...I'm not sure.)
@Ichoran I have programmed in Rust, and I have published the iterator chapter from Stepanov's "Elements of Programming" translated to rust as best I can, here on GitHub.
Rust has some nice ideas, but fails in the execution for me. The borrow checker is ad-hoc, and has no systematic design, which means it misses obviously safe cases, and the work arounds add complexity to the language. The type system also seems thrown together with no use of well known concepts from the functional world that solve the problems (like universal and existential quantification). Again this results in corner cases with undefined behaviours, or unsoundness where the type system and the language semantics don't match up. I agree that the safe concurrency is nice, but it is frustrating that it does not allow common use cases like two write pointers to non-overlapping sub-regions of the same region. I would like to see algorithms like quick-sort accepted as safe. I am reasonably sure that there are some identities missing from rusts analysis. If I was going to take on Rust I would want to have solutions to the above problems before doing so. I have a feeling all of these problems are solvable, but not without breaking changes, so the question is, will Rust make them, are they even looking at this? How different from Rust would a language designed around getting the above type-theory correct would such a language look.
Again, for me Go's lack of generics makes it a non-starter. It's like going back to 'C' from 'C++', at first you appreciate the simplicity, and straightforwardness, but by the time you write your third linked list implementation for a different type, you are over it. They are adding generics to Go, but it's going to have added complexity because it's not in the original design, and they have to work around certain things. For me lack of interfaces for operators adds needless complexity to generics.
The thing is that neither of these languages are really a "target". They all tackle different aspects of the same problem space. The language I am looking at could be used for some problems you would use Go for but not all, and some problems you would use Rust for but not all.
I guess I the end I want to design the language I want, that fixes the issues I have with all the other languages I have programmed in. Designing a language is a big task, so I keep hoping someone else will do it for me. When I try a new language, or a new language is created, I get excited that this might be the one, so I start trying to implement solutions to common problems from my experience. I have written software in Assembly, C, C++, Logo, Java, Pascal, Haskell, Ada, Perl, Python, JavaScript, TypeScript, Prolog, Go, Rust, and maybe some I have forgotten. All of these have some parts I like , and some I dislike. The work in Stepanov's "Elements of Programming" is the best treatment of generics I have come across, but I find the use of C and pointers problematic. Ada does well without pointers and the generics are better than C++ templates, but it has some difficult restrictions that make efficient programs hard to write, like the inability to return a writable reference to an array cell, even if you are sure the array is still in scope.
In case you haven’t already noticed, I created a new thread interim while we are waiting for Github to hopefully restore that thread. I sure hope it isn’t lost. There is a few years of valuable discussion in that thread.
Sorry, to mention you again, but the original thread seems gone, here you can block mentions from other users: Link
Are you sure his ongoing problem was mentions from other users because you and I had stopped mentioning him but he came back 3 hours after our cluster of mentions apologizing to him, and said he was still being emailed. Did Github auto-subscribe him to the thread? He said he tried to unsubscribe from the thread. Here is what he wrote last in the accidentally deleted thread #35:
My apologies for asking: is there something I can do at my end to stop these emails? I tried unsubscribe, but it does not seem to be helping?
An orthogonal problem is the type system: in most object-derived systems, there is a complex type system with at least single inheritance. This leads to an error a former customer made: we /completely/ modelled a tournament in the form of a type hierarchy.
Net result? When you wanted to change it, you had to change everything. When you wanted to add something new, you had to apply it to everything. We re-invented spagetti code, only this time it was spagetti data structures.
Instead of abstracting and simplifying, we made it more complex. Bummer!
Yeah, this is why Rust and Go don’t have class inheritance [aka subclassing]. Good call by both design teams.
Absolutely, inheritance in large projects tend to cause so many problems and makes it difficult to understand and follow! OOP with composition and interfaces is all you need.
Except for the lack of Sum types and Generics :D
[…]
At that point I am not even sure what is the point of OOP. Since SQL tables as a single “type” are useful for a tremendous range of purposes, while I never tried systems programming, if I would try I would probably use any “list of thingies with named properties” idiom that comes my way, be that a hash table or a struct.
OOP was pretty much invented for inheritance, at least the ways I was taught at school, these awesome chains of concepts that a BMW inherits from Car and Car from Vehicle, i.e. basically what David described was taught as good design at my school… but if it is not, then why even bother? I take an SQL table or the language equivalent thereof, hashtable, struct, whatever, name it Car, some of the fields will be Make and Model and call it a day.
Essentially I have to find the sweet spot in the conceptual category-subcategory tree, which in this case is car. Inheritance was meant to be able to move up and down on this tree, but the tree is not cast in stone because the Chevrolet company can acquire Daewoo and next week the Daewoo Matiz is called Chevrolet Matiz, then I am sure as hell not having any object class called BMW: that will be data, easily changed not part of the data structure!
Encapsulation is a better idea but unless I have massive, database-like data structures (which in real life I always do but system programmers maybe not), how am I going to automatically test any function that works not only with its own parameters but pretty much everything else it can find inside the same object? I mean great, objects cut down global variable hell to a protected variable minihell that is far easier to eyeball but is it good enough for automated testing? I think not.
I am afraid to write things like this, because only a narrow subset of my profession involves writing code and as such I am not a very experienced programmer so I should not really argue with major CS concepts. Still… for example Steve Yegge had precisely this beef with OOP: you are writing software yet OOP really seems to want you make you want to make something like unchangeable, fixed, cast in stone hardware.
OOP was hugely hyped, especially in the corporate world by Java marketers, whom extolled the virtues of how OOP and Java would solve all their business problems.
As it turns out, POP (protocol-oriented programming) is the better design, and so all modern languages are using it. POP’s critical feature is generics, so it’s baffling as to why Go does not have generics.
Basically, rather than separating structures into class hierarchies, you assign shared traits to structures in a flat hierarchy. You can then pull out trait-based generics to execute some rather fantastical solutions that would otherwise require an incredible degree of copying and pasting (a la Go).
This then allows you to interchangeably use a wide variety of types as inputs and fields into these generic functions and structures, in a manner that’s very efficient due to adhering to data-oriented design practices.
It’s incredibly useful when designing entity-component system architectures, where components are individual pieces of data that are stored in a map elsewhere; entities consist of multiple components (but rather than owning their components directly, they hold ID’s to their components), and are able to communicate with other entities; and systems, which are the traits that are implemented on each entity that is within the world map. Enables for some incredible flexibility and massively parallel solutions, from UIs to game engines.
Entities can have completely different types, but the programmer does not need to be aware of that, because they all implement the same traits, and so they can interact with each other via their trait impls. And in structuring your software architecture in this way, you ensure that specific components are only ever mutably borrowed when it is needed, and thus you can borrow many components and apply systems to them in parallel.
Activity
keean commentedon Jan 30, 2017
I think the question is why not just pass all the interfaces as arguments? You can of course do this, after all type classes are implemented as implicit dictionary arguments.
The advantage of type classes is that they are implicit, hence you have shorter more readable function signatures. They can also be inferred, so you don't have to explicitly type the constraints. You don't want to have to explicitly thread
ythrough every function call where you pass typeAbecause you might want to callbarat some point. Further when you realise you need to callbaron a value of typeAyou don't want to have to refactor every call in the program to pass an implementation.shelby3 commentedon Jan 30, 2017
Thanks for the reply. I appreciate those advantages. I am just thinking about near-term priorities. I think achieving the "perfect" language is a major endeavor that needs a significant amount of funding and time. I've been thinking that your goal is maximum genericity (i.e. the ability to write some code that can parametrized over many types and uses cases) and minimum explicitness (although in other cases you argue for more explicitness where I argue for less).
My near-term goal is not that. That is far too much for me to think about and digest at this point. I am wanting some improvements on JavaScript so I can go use the same code base both on the client and the (Node.js) server. For example a cryptographic library.
TypedArrays andArrayBuffers)I've been thinking about achieving a much simpler transpiler to TypeScript that can meet those near-term objectives.
Later can experiment with moving to higher levels of genericity and less explicitness, if warranted by the use cases that are prominent. And with more funding and momentum behind the initial simpler transpiler.
That works if you only allow every data type to implemented only one way for each typeclasses, but as we discussed in the past (with @skaller) that has limitations also. Otherwise, you could get unintended implicit functionality. So explicitly passing has its benefits.
Edit: Dec. 4, 2017
@shelby3 wrote:
@shelby3 wrote:
@shelby3 wrote:
keean commentedon Jan 30, 2017
Regarding one implementation per class, that is the advantage of type classes :-) If you want more you need to pass an object or function (because you want the implementation to depend on the value of the object not the type).
To me it seems you can do nearly everything you want in typescript, so why not use it? I am working on a project in typescript at the moment, and for the ES6 stuff plus types it's good enough.
Regarding goals, I agree maximum genericity is my goal, and minimum boilerplate. However I also want to prioritise readability and understandability over conciseness, as you spend more time debugging and maintaining code than we do writing it. However I am happy for explicitness to be optional, which includes type-inference (type annotations optional) typeclass constraint inference (so constraints/bounds are optional). The only place we need types would be modules, where we want a stable interface that enables separate compilation.
shelby3 commentedon Jan 30, 2017
If one universal implementation for each universal class was an absolute advantage or even feasible, then we wouldn't need scoping for (type) name conflicts in programming. It is impossible to guarantee one global implementation for the universe, because total orders don't exist.
I am not arguing that it can not be a feature that is desirable in some cases in a partial order.
I guess you didn't read my list above, or didn't realize that the items I listed which TypeScript doesn't do are extremely important for the work I need to do.
Ditto myself. I take this even further than you as I am thinking all types shall be explicitly declared, except in an assignment where either the destination or source is explicit, but not required for both. Since passing function arguments is assignment, then anonymous lambda functions can be implicitly typed by the function's argument type.
keean commentedon Jan 30, 2017
I thought typescript included ES6 features, and has integer types as well as strucures (classes). The only one it does not have is "everything is an expression" I think, so it seems close to what you want?
Regarding type classes, it is clear that each function can only have one implementation for any given set of argument types. In other words we can only define one implementation for
(+)<Int, Int>shelby3 commentedon Jan 30, 2017
Disagree (and my reason was already stated). But I don't want to repeat that argument now. I have said all I want to say about it for now. It isn't a priority issue for me at the moment. We can revisit that later if ever get to the point where I want to implement a "typeclass" language.
Edit: we had a long discussion about this in another thread in 2016.
shelby3 commentedon Jan 30, 2017
Afaik, there are no integer types in ES6. Are you thinking of 64-bit math methods in ESNEXT?
http://kangax.github.io/compat-table/esnext/
Interfaces and classes do not pack into
ArrayBuffers. Perhaps you were thinking of the following proposal which has gained no traction:http://wiki.ecmascript.org/doku.php?id=harmony:typed_objects
In 2016, I wrote some very complex (untested) JavaScript code to improve on that concept and provide a polyfill. But it has some drawbacks. I need to revisit that code to refresh my memory.
shelby3 commentedon Jan 30, 2017
I looked again at Nim, but it is getting too far from JavaScript (wants to be a systems language competing with C, C++, Rust, and Go). For example, lose JavaScript's
Promises,ArrayBuffers, etc..And it doesn't offer 64-bit integer support on JavaScript.
And I'd much prefer the JavaScript output is recognizeable, so the JavaScript debugger can be effectively used.
shelby3 commentedon Jan 30, 2017
I think I envision a robust way to transpile from a syntax and type system I want into comprehensible TypeScript. For example, I can use TypeScript's getters and setters to access the fields by name of a
DataViewon anArrayBuffer. When not packed into a binaryArrayBuffer, 64-bit integers can be represented with TypeScript's tuples.keean commentedon Jan 31, 2017
It may be slower than normal JS objects due to packing issues, or it may be faster due to non-string property lookup. It certainly seems the right way to process binary network data, but I would probably use native JS objects everywhere memory layout is not important.
shelby3 commentedon Jan 31, 2017
Agreed. I didn't intend to imply otherwise. Not only network data, but file data. You know I am working a blockchain design.
Edit: Also memory data. With a blockchain, you need the UXTO stored in RAM and this becomes huge.
keean commentedon Jan 31, 2017
nodejs can mmap files from JavaScipt which would work well with this too. You don't have too many choices in-browser, you are limited to Blobs with a streaming interface, and the file IO API is not supported by all browsers.
shelby3 commentedon Jan 31, 2017
Right agreed. Interacting with binary data in JavaScript on Node.js is via very verbose APIs. I'd prefer a language native solution.
shelby3 commentedon Jan 31, 2017
I wrote "client side" but that doesn't mean we are limited to the browser. I am thinking Android and iOS.
shelby3 commentedon Jan 31, 2017
Most in the blockchain realm use a systems programming language with good binary control, such as C or C++. But this is painful in other aspects. There is a lot of verbosity that isn't always needed. Need complex libraries such as Boost for asynchrony, etc... Do not have GC by default, etc.. I think it is overkill.
I am searching for a better balance. Rapid coding with less needless verbosity, yet still be able to handle binary data and integer math without verbose APIs. And also some (explicit) typing for correctness checks and better readability (open source).
171 remaining items
sighoya commentedon Jun 16, 2020
@shelby3 wrote:
The problem with C++ is that it eats anything what smells like a feature.
shelby3 commentedon Jun 16, 2020
Does anyone really like teamwork? Gavin McInnes at TEDxBrooklyn ← funny
[…]
Note he did not mention mutual defense. That requires teamwork and is essential to surviving periodic societalcide.
shelby3 commentedon Jun 17, 2020
@sighoya wrote:
Yeah but I think my point remains. They add those features for low-level performance optimizations or high-level programming capabilities, because they want to be both a low-level performant and a high-level PL.
I know you all know the following but I will explain for other future readers.
Yet attempting to be both makes it more complex to excel at either end of the spectrum.
Firstly the humorous but sordid tale, C++ Frequently Questioned Answers:
One example is pointer arithmetic which is enables low-level optimizations in some cases yet at the high-level it breaks encapsulation which prevents buffer overruns.
Another example for C++ is the concept of r-values and all the optimization complexity to prevent copying of return values:
https://en.wikipedia.org/wiki/Copy_elision#Return_value_optimization
https://shaharmike.com/cpp/rvo/
https://bastian.rieck.me/blog/posts/2015/return_value_optimization/
Then the complexity of the corner cases where it doesn’t work:
https://www.linkedin.com/pulse/c-return-value-optimization-dipanjan-das-roy/
https://stackoverflow.com/questions/19792135/return-value-optimizations-and-side-effects/19792614#19792614
So we can sort of emulate type classes with templates but without the implicit functionality:
https://functionalcpp.wordpress.com/2013/08/16/type-classes/
Yet templates are Turing-complete so we end up with template metaprogramming which makes it unpredictable what templates even do, i.e. you have to run the compile to see if they even halt or if they resolve in a predictable manner as you refactor code for maintenance and new features.
AFAICS, the complexity clusterfuck is significantly due to the goal of trying to merge high-level and low-level.
I’ve read that even the creator of C++ doesn’t understand all of the language thoroughly.
I used C++ to code CoolPage and Art-o-matic around the turn of the century but that was before much of the complexity became mainstream such as heavy use of template programming and the Boost libraries, etc.. I thus never learned all that. I essentially stopped using C++ when I halted all development of those applications circa 2003. I transitioned to JavaScript, PHP, ActionScript, HaXe, then directly Scala on the tip from @jdegoes (entirely skipping learning anything about Java). Before C++ I was highly expert with C and Intel and Motorola assembly. Learning Scala exposed me to Haskell, which I resisted because it was so foreign to me given I had done low-level imperative programming since age 13 starting from Apple II BASIC (or let’s say it made my head hurt because I didn’t get it and nobody was explaining it circa 2009 in a way that would click for me). I wrote much of WordUp in the late 1980s in 68000 assembly until I discovered C. Then I went on a tear with my new found productivity with C producing WordUp 3.0 (together with my employee/colleague Mike Fulton) before burning out. I remember consuming the K&R C book afair in one evening and being on a programming tear the next morning. C just made sense to me coming from assembler because I had consumed a Radio Shack book on Microprocessors at age 13 when I was laid up in bed from a high ankle sprain from football with nothing else to do. So I was coming into programming from a desire to want to understand how the hardware functioned. I enrolled at the University to pursue BSEE with a math minor but dropped out when I was spending 10+ hours a day in the library doing my own research and programming at night leaving nearly no time for studying given I was also heavily in drinking and sports. Up until my 3rd year, I maintained a 4.0 GPA by not attending classes and cramming on the night before exams.
I’m not a Rust expert having not used it to write any code (other than maybe a few toy snippets). I’ve read the documentation and read a considerable amount of expert discussion (from both sides including the defenses from the Rust fanboys[1]). Rust seems to have many cases where the desire for both high-level and low-level features create complexity that wouldn’t be the case if it has just focused on being a data race safety and “zero-cost abstraction” low-level language. For example the complexity around closures which @keean raised. Others such as the Rust discussion I cited up-thread have enumerated other examples. Again I will reiterate that Rust seems to be an improvement over C++ complexity, at least if total program order lifetime borrowing is not factored in, but they can’t be factored out as that is the essential USP for Rust.
Rust is only slightly more performant on benchmarks than C/C++, yet it is considerably more performant than Go:
https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/rust.html
I might be interested in a low-level PL that stripped out the type classes, closures and all the obfuscating, implicit conversions cuteness from Rust and focused on being a faster and more safe C.
If Rust had focused on something I really need, I might be a fan. But as it stands right now I do not need the tsuris of Rust for high-level programming. And I don’t need for low-level programming, the complexity of Rust’s ecosystem and high-level conflation. I want the next low-level language I will master after C to be stable and hyper-focused on seamless integration (without FFI marshalling overhead) with whichever high-level PL has formed a market Schelling point.
For example I might be willing to tolerate the lifetime borrowing complexities and annotation noise (given the apparent improvement in performance and some safety guarantees is provides) if there wasn’t the conflation with the high-level feature of type parameter polymorphism (aka generics). A low-level language doesn’t need type parametrisation.
I suppose after creating a high-level programming language then I need to tackle creating a fork of Rust and new low-level focused PL. Ha. Dreaming, if I was only 25 years old again I could possibly do that. No chance of that anymore. 🌔
Does anyone else have a different perspective?
[1] Intelligent guys who make intelligent technical arguments, but they miss the big picture point. They are so religiously attached to the notion that Rust is some hypersonic, hypersafe killer app that will soon conquer the world. And that unrealistic (“aspirational class”) ambition colors and feeds their confirmation bias blindspot — the world doesn’t need a conflation of high-level ease with low-level performance resulting in a complex Frankenstein pita.
shelby3 commentedon Jun 18, 2020
@andrewcmyers
Looking at this with a clearer mind and more sleep and ignoring the white paper and focusing solely on the code examples on the JMatch homepage:
AFAICS the “Containment predicate is also a tree iterator” unification of
containsand iteration of contained can be abstracted with an interface that provides a new iterator and afindNext. Thuscontainsinstantiates a new iterator and invokesfindNexton it returning the boolean of whether was found. Whereas iteration of the contained will repetitively invokefindNext.The “Balancing a red-black tree with pattern matching” is a clear example of how constraints expressed as pattern matching are much more coherent and succinct compared to the equivalent conditional expressions code required to accomplish the same algorithm. I presume the JMatch compiler compiles the pattern matching syntax into the optimal conditional expressions code.
The “A client loop selecting elements matching a pattern” exemplifies that pattern matching on iteration more coherently and succinctly expresses the invariants of the algorithm compared to the equivalent conditional expressions code required to accomplish the same algorithm.
I don’t know if there is additional significance or novelty to your work, because I have not tried again to digest the research paper.
I do agree with the utility of these features and would be interested to explore them.
andrewcmyers commentedon Jun 18, 2020
I think you're getting the gist of it. Pattern matching can be viewed as a kind of reverse-mode computation in which values are deconstructed instead of constructed. JMatch allows any method to declare reverse modes that can be used to pattern-match (one or more times) against a set of arguments. What it means to match can be specified with a logical formula, so pattern-matching is not tied to the concrete representation of values as in Haskell, OCaml, etc.
sighoya commentedon Jun 19, 2020
@keean, so your idea is likewise for records.
Records are datatypes and get bounded to fields over typeclasses:
All this kind of stuff can then be generated with normal record like syntactic sugar:
shelby3 commentedon Jun 19, 2020
The creator of Node.js has created Deno to correct the accumulated crud in Node.js. He remarks that Go is a more performant PL for a server but the JavaScript server still has some valid use cases (e.g. sand boxed):
https://youtu.be/M3BM9TB-8yA?t=174
Ryan Dahl mentions the clusterfuck of module importing,
require,package.json,index.js, etc., which BTW was what I told the Node.js project to simplify and move to ES6 modules deprecating that crud and got me banned from their Github issues some years ago. Design-by-committee can never shred the crud and start anew. Ryan also mentions that his design wisdom has improved with age. 😜Ryan loves TypeScript, which furthers my expectation that an improvement on TypeScript would be popular.
The Node.js replacement Deno is advocating Rust as the low-level language for the (probably much) less than ~20% (i.e. Pareto principle aka power-law distribution of resources) of your code that needs to be fast.
https://youtu.be/1gIiZfSbEAE?t=723
Yet the rudimentary integration across the FFI is apparently not mutually typed:
https://youtu.be/HjdJzNoT_qg?t=1379
They’ve coded the entire Deno executable in Rust, but that’s understandable given all of it needs to be fast given it is a system layer. Their mainstream options were essentially C++ or Rust, so it makes sense they chose Rust per my prior comments in this thread. But for the majority of users who just want to code some lower-level subroutines in a high performance, low-level PL, then the conflation of higher-level Rust features with the lower-level requirements, adds unnecessary complexity and unsoundness.
EDIT: indeed I was correct that Ryan considered C++ as the other option:
https://youtu.be/M3BM9TB-8yA?t=1406
My current priority (if I can get healthy enough to consistently think clearly) is to improve upon TypeScript for the high-level language part.
Again Scala was a breath of fresh air for me and a significant improvement over what I had used before it. But then I ran into the aforementioned issues of its non-principled, “kitchen-sink” design (being a jack of all trades and not hyperfocused on a superior paradigm, removal of implicit conversions, simpler and more sound type system, etc), and Scala's lack of unsigned and sized integers, lack of non-disjoint, structural (aka anonymous) unions (Scala 3 adds these), the crap concurrency support with inability of Scala.js to integrate with
async/await, lack of green threads on the server, the monolithic GC for all threads (ditto Go and Node.js/Deno which is another aspect I want to improve), etc...Pony is/was an experimental PL that provided some interesting insights into what is and isn't needed for an Actor-like partitions (ALP) paradigm, but I posit it’s not the right design and will very likely die on the vine. Sorry to be so frank and overtly opinionated, but they didn’t incorporate green threads and their GC scheme is not performant for intense sharing and if you're not going to do intense sharing then the reference capabilities do not need to be as complex and I posit the GC scheme can be made radically more performant with a paradigm-shift.
I wrote more details about this in the WD-40 thread #35.
P.S. Yet another example of how design-by-committee creates a clusterfuck, setting up Unicode character shortcuts for ellipsis, right and left quote marks in Kubuntu is byzantine, although
Shift+Ctrl+U+[code]worked fine in Mint Mate and on standard Ubuntu:https://askubuntu.com/questions/1095339/how-can-i-input-unicode-characters-in-kate-and-konsole-on-kubuntu-18-04
https://bugs.kde.org/show_bug.cgi?id=103788#c15
Maybe eventually I will find the compose key combinations for all the Unicode characters I want:
https://en.wikipedia.org/wiki/Compose_key#cite_note-xorg-12
https://askubuntu.com/questions/34932/where-can-i-find-the-full-list-of-compose-combinations-for-my-locale
And then employ keyboard shortcuts to make them easier to remember and type:https://forum.kde.org/viewtopic.php?f=66&t=97493#p206186
[doesn't integrate with compose key ostensibly because of the aforelinked KDE bug]shelby3 commentedon Jun 19, 2020
“Uncle” Bob Martin points out in the “The Future of Programming” that the rate of increase of programmers is growing so fast that more than half of the programmers have less than 5 years experience and are young. Apparently the younger the programmers, the more heavily tilted towards males the programmer demographic is.
https://youtu.be/ecIWPzGEbFc?t=3055
The implication is that PLs and frameworks are becoming less disciplined because the programmer demographic is becoming less disciplined (young age, low-level of experience, high testosterone, more energy than wisdom, etc).
Ichoran commentedon Jun 20, 2020
Despite @shelby3's accurate understanding of my technical points, which I appreciate, he's misunderstood my non-technical points almost without fail (e.g. mistaking the logical thoroughness of breaking down a situation into cases as an implicit accusation of the negative parts of each case). I do want to clarify that I am an extremely strong proponent of freedom of expression, just not of having ideology litmus tests backed up by strong rhetoric to try to drive away people with valuable technical skills.
@keean - I don't know if you've programmed in Rust, but along with your comment about Rust being a most-loved language, it's useful to know why Rust is most-loved. It's not just because they've learned from history in a number of areas (e.g. crates, docs, community) and created either a best-in-class or a refreshingly-nice-alternative (for those people who want to use Rust). It also solves a problem that many systems-level programmers have, and for which there were zero good solutions, which is how to use mutability safely.
Not every new language has to allow the same thing. Immutability is another approach, though if you think the borrow-checker is annoying, try putting someone up against either the borrow-checker or the entire field of pure FP ("so, you see, just use State, except no not really, use IO instead...but anyway, monads don't commute, so we need monad transformers (uh, actually, a lot of them), except that final tagless is a better way to go, except..."), the borrow-checker both admits much higher performance without insane compiler wizardry, and is a lot easier to swallow. (Admittedly it solves many fewer problems, but for that problem it's absolutely brilliant.)
I've done a lot of programming in C++ over the years. Although I occasionally get frustrated with Rust not allowing something that is "obviously" safe, it is VASTLY outweighed by the number of times that it stops me from doing something obviously "safe", meaning actually not safe at all, and I didn't think it through carefully enough. It's almost as big an advantage for me as having strong types instead of none.
My point in writing this is not to say that you have to bite off the whole borrow checker thing. My point is that Rust has that covered, and it's a very compelling feature, and if you go head-to-head with it, you have to either come up with something incredible, or you will lose. (That goes for multithreading, too...it's not enough to be 30% better at multithreading if Rust can beat you by 2x on single threads.)
If you don't need to go head-to-head, though, because you're targeting a different area (e.g. not the-very-fastest-speed), then the design constraints are a bit less acute. Competing with Go is vastly easier than competing with Rust on a technical level. (Sociologically it may be harder...I'm not sure.)
All this makes me wonder if you've read about Unison, specifically its abstraction construct which it calls abilities.
keean commentedon Jun 20, 2020
@Ichoran I have programmed in Rust, and I have published the iterator chapter from Stepanov's "Elements of Programming" translated to rust as best I can, here on GitHub.
Rust has some nice ideas, but fails in the execution for me. The borrow checker is ad-hoc, and has no systematic design, which means it misses obviously safe cases, and the work arounds add complexity to the language. The type system also seems thrown together with no use of well known concepts from the functional world that solve the problems (like universal and existential quantification). Again this results in corner cases with undefined behaviours, or unsoundness where the type system and the language semantics don't match up. I agree that the safe concurrency is nice, but it is frustrating that it does not allow common use cases like two write pointers to non-overlapping sub-regions of the same region. I would like to see algorithms like quick-sort accepted as safe. I am reasonably sure that there are some identities missing from rusts analysis. If I was going to take on Rust I would want to have solutions to the above problems before doing so. I have a feeling all of these problems are solvable, but not without breaking changes, so the question is, will Rust make them, are they even looking at this? How different from Rust would a language designed around getting the above type-theory correct would such a language look.
Again, for me Go's lack of generics makes it a non-starter. It's like going back to 'C' from 'C++', at first you appreciate the simplicity, and straightforwardness, but by the time you write your third linked list implementation for a different type, you are over it. They are adding generics to Go, but it's going to have added complexity because it's not in the original design, and they have to work around certain things. For me lack of interfaces for operators adds needless complexity to generics.
The thing is that neither of these languages are really a "target". They all tackle different aspects of the same problem space. The language I am looking at could be used for some problems you would use Go for but not all, and some problems you would use Rust for but not all.
I guess I the end I want to design the language I want, that fixes the issues I have with all the other languages I have programmed in. Designing a language is a big task, so I keep hoping someone else will do it for me. When I try a new language, or a new language is created, I get excited that this might be the one, so I start trying to implement solutions to common problems from my experience. I have written software in Assembly, C, C++, Logo, Java, Pascal, Haskell, Ada, Perl, Python, JavaScript, TypeScript, Prolog, Go, Rust, and maybe some I have forgotten. All of these have some parts I like , and some I dislike. The work in Stepanov's "Elements of Programming" is the best treatment of generics I have come across, but I find the use of C and pointers problematic. Ada does well without pointers and the generics are better than C++ templates, but it has some difficult restrictions that make efficient programs hard to write, like the inability to return a writable reference to an array cell, even if you are sure the array is still in scope.
shelby3 commentedon Jun 20, 2020
@Ichoran and @keean I have continued the tangential discussion about Rust in the WD-40 issues thread #35 where it belongs.
I rebutted or let’s say responded to @Ichoran in the aforelinked reply. EDIT: I also replied to @keean’s latest comment at the aforelinked.
thickAsABrick commentedon Jun 22, 2020
Thanks, @sighoya
keean commentedon Jun 22, 2020
@sighoya sorry deleted by accident. Asked to be restored, now waiting...
shelby3 commentedon Jun 23, 2020
@sighoya
In case you haven’t already noticed, I created a new thread interim while we are waiting for Github to hopefully restore that thread. I sure hope it isn’t lost. There is a few years of valuable discussion in that thread.
Are you sure his ongoing problem was mentions from other users because you and I had stopped mentioning him but he came back 3 hours after our cluster of mentions apologizing to him, and said he was still being emailed. Did Github auto-subscribe him to the thread? He said he tried to unsubscribe from the thread. Here is what he wrote last in the accidentally deleted thread #35:
shelby3 commentedon Sep 17, 2020
The big break in computer languages