proj-oot-ootNotes40

" Integer representation – two’s complement Floating point – IEEE 754 Power-of-two byte/word sizes – There’s other good choices, but 8 bits per byte and 2^N bytes per word is a pretty good one Byte endianness – not universal, but basically settled on little endian for processors and I only foresee it becoming more universal with time. Network protocols are big-endian by default but that’s at least well defined, and not enough of an impediment to matter UTF-8 – You’re going to have a hard time coming up with a better variable-length integer representation that prefers small integers. There’s others that are just as good, or better for some specific purposes, but UTF-8 still has some nice properties. Serial connections and UART’s, whether RS-232 or TTL or whatever. There’s a million little variations but the basic setup is perfectly good for what it does. ELF executable format. Some improvements could be made, I’m sure, but all in all it’s pretty Fine. Paging over segmentation for memory management. There’s nothing fundamentally wrong with segmentation, but at least on any 32-bit chip, either you are an embedded system without much memory and don’t need an MMU, or you’re large enough to afford an MMU with paging. It’s still used on 8-bit microcontrollers but basically no popular 32+ bit CPU developed since the VAX has used segmentation. " -- https://wiki.alopex.li/ThingsWeGotRight

--- ---

~ mwcampbell 8 hours ago

link flag
    Mistake 7 : Build lifetime heavy API

I’ve struggled a bit with this. More generally, as a newcomer to Rust, I’ve found myself acting as if the slightest compromise in efficiency is a slippery slope straight to the excesses of Electron. Rust promises zero-overhead abstractions, so sometimes I try too hard to use the zero-overhead solution for everything. Another example is trying to make a struct or function generic instead of just using Box<dyn MyTrait?> or even Arc<dyn MyTrait?>.

    6
    zaphar 16 hours ago | link | flag | 

Rust exposes the fact that memory management for a linked list is hard. You can implement a linked list but satisfying the borrow checker while doing so is difficult. Many times you’ll just drop to some form of weak reference handle system backed by a vec. Which gives you the ergonomics of the linked list and less fighting the borrow checker.

~ fouric 8 hours ago

link flag
    Start by implementing a graph based algorithm.

My favorite personal projects are all intrinsically graph-based. Is Rust a poor fit for me?

    ~
    gpm 7 hours ago | link | flag | 

Use something like petgraph you will be fine. Or if you’re implementing it by hand, store the nodes in an arena/vec and instead of pointers use indicies.

It’s a bit of a bigger step for newcomers, because you’re learning both “rust” and “how to deal with graphs in rust without fighting the borrow checker” at the same time, instead of one and then the other.

~ denys_seguret 19 minutes ago

link flag

Same for me and I still manage to write them. Simply be sure to understand that you’ll have to suffer more than other people learning Rust if you start here. The best working approach is the arena+index one but it needs you to be very cautious (test units help, of course) as it means you’re going around the protection given by the ownership model.

---

generic design template:

(core) data structures; use cases; questions; UI screens/modes and flows between them; ui views / filters / queries; questions; inspirations; prime priorities and non-priorities; to do for design; notes/thoughts/main pages; documents/log/fAQ about design decisions that are supposedly finished and in the main pages; list of parts of the design (for example for a programming language, type system, syntax, control flow, etc) or maybe you don't need a list just look at the files that exist for main pages/thoughts/notes. Toreads. Free text introduction, free text why, free text status / to do. User situations (like user stories but less emphasis on the type of user/marketing)

---

"The entire C separation of preprocessor, compiler, assembler, and linker exists because each one of those could fit independently in RAM on a PDP-11 (and the separate link step originates because Mary Allen Wilkes didn’t have enough core memory on an IBM 704 to fit a Fortran program and all of the library routines that it might use). Being able to fit an entire program in memory in an intermediate representation over which you could do whole-program analysis was unimaginable." -- David Chisnall

---

" In defense of complicated programming languages ... What then, would happen if we were to ban classes from Python?

Oh, it would make the language so much simpler! ... users would find the number of variables would get out of control. Until one day, some programmer gets the neat idea that they could reduce the number of variables to keep inside their head if only they grouped variables in a dict:

def bark(dog_dict): print("WOOF!" if dog_dict["weight_kg"] > 25 else "Woof")

rex = {"name": "Rex", "weight_kg": 35}

And so they would have accidentally re-introduced classes, only this time the existence of classes would have been implicit in the code, and their behaviour ad hoc defined, their invariants spread all over the source files, and with no language-level tooling or introspection to help the programmer. Classes would still exist, but as implicit patterns.

These kinds of structures emerges spontaneously and constantly, all over code.

Programming languages used to not support functions, but then it was discovered that instructions tended to be grouped in blocks by their functionality, and that conceptualizing it as a function made the code easier to reason about. The introduction of functions did not make programming more complex, on the contrary, it became simpler in the way that matters.

Languages used to not have structs, but then programmers discovered the usefulness of grouping sets of data into an abstract, higher-order kind of data called a struct. And again, this feature did not make programs more complex, but made them simpler. ... Julia has a complicated type system. Types like AbstractSet?{Union{Nothing, <:Integer}} are not simple to learn to parse, or trivial to reason about in existing code. But the structure of this type, and thus its complexity, is merely an instantiation of the programmer's intent about the data it represents. With a simpler type system, that type would not exist, but the same intent would be there, nonetheless. ... Not co-incidentally, recent versions of Python have introduced type hints backed by a complex type system, such that programmers can now express the very same idea as collections.abc.Set[typing.Option[numbers.Integral]]. ... A post of the format "In defense of ..." does not have a lot of room for nuance, but of course the issue in this post is not clear cut on way or the other. "More language features" does not equal "more better", and the detractors of modern language complexity do have points that are worth considering, at least in isolation.

All the language features in the examples above - classes, advanced types, and the borrow checker - have an important trait in common: They all feel like they emerge spontaneously from existing code independently of whether the language designer has thought about them. In that sense, they are the best kind of feature; instead of adding new things to worry about, they merely provide a vocabulary and tooling for dealing with already existing problems.

Not all language features are like this. For example, Julia has four different ways of defining a function, and just as many variations on how a for loop looks. One can define a type to be a struct, mutable struct, abstract type and primitive type (all the former possibly parametric). Types can be placed in the type hierarchy as either concrete types, abstract types, union types or unionall types. The representation of types (i.e. type of types) can be either DataType?, Union, UnionAll?, or Bottom. " -- [1]

" A hypothetical language like the one the author considers – exactly like Python, but without classes – would obviously suck because a lot of the code that is currently very concise would get pretty verbose. Presumably, that’s why they added classes to Python in the first place. However, that doesn’t mean a better abstraction mechanism, which could, for example, unify classes, dictionaries, and enums (currently provided through a standard library feature) wouldn’t have been possible. That would get you the same concise code, only with less complexity. ...

Edit: Or, to put it another way, there is such a thing as an “evolutionary leap”. One sufficiently powerful mechanism can make several less powerful mechanisms obsolete. Templates and template functions, for example, made a whole class of C hacks (dispatch tables, void pointer magic) completely unnecessary. " -- [2]

another comment: " This is so weird because I agree with the general concept but I think it would be a much better article with a different example. Python without classes would legitimately be a much better language; encapsulation is so important that it needs to be available in a way that’s not coupled to inheritance and polymorphism.

The article also ignores the fact that many of these features can be added by a 3rd-party library in a well-designed language, see Lua and the many 3rd-party class systems available for it as opt-in features, or Clojure’s pattern matching and CSP/goroutine libraries. " -- [3]

---

a terrible thing about Lisp is that when you alternate between assigning values to variables, and doing 'if' statements, you end up having deeper and deeper levels of nesting as you nest an 'if' inside a 'let*', which is itself nested inside an 'if', which is itself nested inside a 'let*', etc.

imperative programming with mutation avoids this, but also just automatically lowering "x=3; ...rest of function..." to "(let* ((x 3)) (...rest of function...)" would do the trick.

---

and of course another annoying thing about Lisp is when writing arithmetic expressions, lack of any order of operations leads to extra parens

---

throw10920 10 hours ago

root parent next [–]

Thank you for explaining - now that you've pointed it out, I can see that this is just another form of the "graphs are hard in Rust" problem that I've encountered before.

However, I still stand by my point - Rust might be bad at graphs, but I believe that a well-designed language with a borrow checker (maybe something closer to Lobster, which automatically inserts RC cells when you try to multiply mutable borrow[1] - something obviously more correct than what Rust does, I'm not sure how they messed that one up) wouldn't necessarily have to be.

[1] https://aardappel.github.io/lobster/memory_management.html

---

"But these traits are in some ways at odds with each other. The most simple code is probably not the most testable. All those interfaces and injected dependencies make for convenient testing, but have a cost in terms of simplicity.

Your heavy reliance on singletons may make things easy to understand, but it might not lead to a maintainable application." -- https://www.steveonstuff.com/2022/01/27/no-such-thing-as-clean-code

---

https://achievement.org/

---

"I try to optimize my code around reducing state, coupling, complexity and code, in that order. I'm willing to add increased coupling if it makes my code more stateless. I'm willing to make it more complex if it reduces coupling. And I'm willing to duplicate code if it makes the code less complex. Only if it doesn't increase state, coupling or complexity do I dedup code." -- https://news.ycombinator.com/item?id=11042400

---

i guess in order to have something like emacs, where part of the application is written in a scripting language and the user can replace arbitrary provided functions with their own versions, you need an interpreted language and some sort of 'extreme late binding' where eg functions are looked up by name at runtime.

---

FRIGN 19 hours ago

link flag

‘C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off’ - Bjarne Stroustrup

    ~
    singpolyma 7 hours ago | link | flag | 

How does C++ make it harder?

    ~
    walleye 2 hours ago | link | flag | 

RAII for the most part.

~ snej 22 minutes ago

link flag

RAII, smart pointers to manage memory, safer strings, robust collection classes, type-safe templates instead of ad-hoc casting, references vs. pointers, optionals and variants instead of unsafe raw unions…

---

"Apple M1, ARM64, and PowerPC?64 users rejoice! Go 1.18 includes CPU performance improvements of up to 20% due to the expansion of Go 1.17’s register ABI calling convention to these architectures. Just to underscore how big this release is, a 20% performance improvement is the fourth most important headline!" -- [4]

---

" Why pipes, anyway? In our setup, the web service which generates ZIP files communicates with the web server over pipes; it talks the Web Application Socket protocol which we invented because we were not happy with CGI, FastCGI? and AJP. Using pipes instead of multiplexing over a socket (like FastCGI? and AJP do) has a major advantage: you can use splice() in both the application and the web server for maximum efficiency. This reduces the overhead for having web applications out-of-process (as opposed to running web services inside the web server process, like Apache modules do). This allows privilege separation without sacrificing (much) performance. " https://github.com/CM4all/libwas/

---

"I am looking for a real FP language which means supporting composition, partial application, and first-class functions." -- https://lobste.rs/s/gm2ukd/fp_language_which_will_stand_test_time

---

https://jolynch.github.io/posts/distsys_shibboleths/

---

Animats 2 days ago

parent next [–]

But we did see performance improvements by restricting the language in certain ways that aid in static analysis, which allowed for more performant runtime code.

Well, yes. In Python, one thread can monkey-patch the code in another thread while running. That feature is seldom used. In CPython, the data structures are optimized for that. Underneath, everything is a dict. This kills most potential optimizations, or even hard-code generation.

It's possible to deal with that efficiently. PyPy? has a compiler, an interpreter, and something called the "backup interpreter", which apparently kicks in when the program being run starts doing weird stuff that requires doing everything in dynamic mode.

I proposed adding "freezing", immutable creation, to Python in 2010, as a way to make threads work without a global lock.[1] Guido didn't like it. Threads in Python still don't do much for performance.

[1] http://www.animats.com/papers/languages/pythonconcurrency.ht...

reply

chrisseaton 2 days ago

root parent next [–]

> This kills most potential optimizations, or even hard-code generation.

It doesn’t - this has been a basically solved problem since Self and deoptimisation were invented.

reply

Animats 2 days ago

unvote root parent next [–]

In theory, yes. In CPython, apparently not. In PyPy?, yes.[1] PyPy? has to do a lot of extra work to permit some unlikely events.

[1] https://carolchen.me/blog/jits-impls/

reply

---

"Something like Kotlin but with a borrow checker might be the ultimate in developer ergonomics for me. " -- [5]

---

dwohnitmok 82 days ago

parent context prev next [–]on: Understanding higher-kinded types

`Foldable` is basically the FP equivalent of `Iterable` in Java if you're familiar with that. It's an abstraction you can write against if you want to support all sorts of data structures that can be iterated on (e.g. arrays, lists, trees, etc.).

---

"

I've been playing with rust on and off for about two years. I've found myself having the same thought as you. I'm amazed at some of the language features provided by rust and can't help but think that it would be an amazing language to build anything in if only you didn't have to deal with memory.

When I compare it with C# or Java, I find my self envying it's structural type system, error handling philosophy and constructs, often very useful and descriptive compiler messages, the absence of null, the best implementation of the iterator pattern I've seen in any language, traits, tuples, enums, amazing pattern matching and the list goes on.

If only there was a language out there that had made the same design decisions in those areas but was slightly higher level, it would be such a joy to work with. Unfortunately, for me, rust is sometimes a bit frustrating to try to build stuff in. " -- https://github.com/Hirrolot/hirrolot.github.io/issues/6#issuecomment-1145881193

---

https://www.packt.com/bizarre-python/

---

https://mawfig.github.io/2022/06/18/v-lang-in-2022.html shows how a programming language called V doesn't live up to the safety claims on its webpage ( https://web.archive.org/web/20220619034132/https://vlang.io/ ). Let's make sure we don't make that mistake.

---

"

funny_falcon 1 day ago

parent next [–]

I'd really like to find "the low-level functional language" without "the fun stuff". Or at least it should have simple and boring subset that is usable without "the fun stuff".

Something like Caml Light - precursor to Ocaml - but with translation to C instead of bytecode, so it will be fast and will have comfortable integration with C libraries.

reply

jfecher 1 day ago

root parent next [–]

This is definitely an interesting thought. My thinking is that "the fun stuff" tends to help make the language more functional, so removing it you are left with a more imperative language resembling a C clone with traits and type inference. Then if you want easier C interop you must remove traits and either remove modules as well or provide a standard method of mangling module names into function names. At that point I think you may as well use an existing "better C" language like Odin, Zig, Jai, C3, etc.

reply

trumpeta 1 day ago

root parent next [–]

what makes Ante low level? Just from a cursory look over the website seems pretty high level to me.

reply

jfecher 1 day ago

root parent next [–]

My definition of low level is no tracing GC, values are unboxed by default, and users still have control to do low level things (raw pointers, other unsafe operations) when needed, even if it is not the default.

reply "

---

https://www.quora.com/What-are-the-best-designed-programming-languages

" Scheme is beautifully designed. It is a LISP, so there is the beautiful symmetry of code and data, as well as the abstractions of functional programming, and a REPL (LISP machine) that can extend to your executing program. Scheme was designed in/as an introduction to computer science course (the enlightening Structure and Interpretation of Computer Programs). A modern take on why it's so addicting can be found in Why Racket? Why Lisp?

.

C is also a testament to design. The goal of a "portable assembly" that can take human-readable code directly to machine code with as little mucking about as possible was absolutely attained. For all its warts, you can do absolutely magic with C that is often not possible in any other "high-level" language. (I'll exclude C++ because it is a beautiful, misunderstood bastard child).

Haskell and I have a love/hate relationship, but you cannot deny the incredible thought and research that's gone into its design. A truly pure, lazy (non-strict) functional language. It's as amazing to behold and use as it is frustrating to accomplish many tasks in. I gave it up when I realized graphs are an open research problem, but it was another enlightening journey, and its influence is being felt in other languages.

Some second-tier choices:

Python for focusing so heavily on readable and idiomatic code while still being a serious language with some umph to it. It's led to an amazing variety of very high-quality and useful libraries.

Rust for being a functional language that doesn't shove itself down your throat, and focusing on a problem that (some kinds of) real developers actually lose sleep over: memory management. In my mind, Rust is the logical conclusion of C++'s standard memory management model in a world where functional programming exists (RAII and immutability. I'd love to talk to someone about this more in depth). "

" Related What is the most poorly designed programming language?

Thanks for the A2A. Let's have a see

    Java needlessly avoids multiple inheritance. It has a keyword interface as a workaround
    JavaScript has no keyword for private, and does weird things to this. And has weird scope
    C has very limited ways to add modularity at language level. And it has no string type
    C++ tries to retain OOP compatibility with C primitives, making for some really tangled code
    PHP has a weird assortment of standard functions with inconsistent parameter conventions
    Python is full of critical whitespace in a way we haven't seen since COBOL
    Perl was just meant to be a complete mess from day one

My view? Spin a bottle and pick one.

There are no languages that have no faults. None. And all languages have fans and evangelists.

It's almost as if personal feelings had more to do with language choice than their actual design… "

---

tail calls in wasm/reasons to have tails calls at the low-level/assembly:

" j-pb 1 day ago

next [–]

Sorry if I miss something obvious, but how is this not solvable by the compiler?

I'm a huge functional programming evangelist, but high-level stuff like this does not belong in a low level language bytecode like WASM.

Wasm should only care about two things: Security and Performance.

With the standard blowing up like crazy we'll get neither. Worse, we'll cemenent the current duopoly of browser engines, because we'll make it (again) so complex that no-one can create an alternative.

We shouldn't have GC, Exceptions or Tail calls in WASM, as long as the compiler can provide them.

What we should have is multi memory support and memory read permissions, because without them WASM lacks basic security primitives.[1]

Reference types maybe. But I've yet to see a convincing argument as to why they are absolutely necessary.

Everything else (as long as it can be done by the compiler) is just bloat.

1. https://www.usenix.org/conference/usenixsecurity20/presentat...

reply

apignotti 1 day ago

parent next [–]

Tail-calls is fundamentally something that the compiler _cannot_ solve. Trust me, if there was a way we would have avoided ourselves all this work.

The issue is that to avoid stack blow-up you need the engine to recycle stack frames. You might argue that this could happen implicitly in the VM, with all the calls in tail position being automatically converted to tail-calls. The problem with this is that in WebAssembly? (and JavaScript?) the call stack is observable via the .stack property of thrown exceptions.

Since an implicit conversion would be observable engines cannot simply optimize the problem away, and that is the reason why new opcodes (return_call/return_call_indirect) are actually required at the WASM level.

For the specific case of direct calls (return_call opcode) the compiler could solve the problem by inlining, with some luck. But for the case of return_call_indirect there is no other possible solution.

reply

keithwinstein 1 day ago

root parent next [–]

Heya,

(1) Thank you for implementing this in JSC!! I hope they take it, it makes it into Safari, and the tail-call proposal advances.

(2) I don't think you are exactly right about the call stack being observable via thrown exceptions. There's no formal spec for the v3 exceptions proposal yet, but in the documents and tests, there's nothing that would change in WebAssembly? core to make the call stack observable. (There's no ".stack property" that would be added to Wasm itself.) It's true that the proposal amends the JS API (but only the JS API) to describe a traceStack=true option; from Wasm's perspective I understand that's just an ordinary exception that happens to include an externref value (just like any other value) to which Wasm attaches no special significance. The Web-based engines can attach an informative stack trace if they want, but there's no requirement preventing frames from having been optimized out. The non-Web engines won't have to think about this.

(3) I think the real reason that a Wasm engine can't implicitly make tail calls proper is that the spec tests forbid it, basically because they didn't want the implementation base to fragment by having some engines perform an optimization that changes the space complexity of a program, which some programs would have started to depend on. (The spec tests say: "Implementations are required to have every call consume some abstract resource towards exhausting some abstract finite limit, such that infinitely recursive test cases reliably trap in finite time. This is because otherwise applications could come to depend on it on those implementations and be incompatible with implementations that don't do it (or don't do it under the same circumstances.)")

But the issue is much weaker than "call stack is observable" -- it's more like "infinite recursion must trap eventually, but it can be nondeterministic when."

More discussion here: https://github.com/WebAssembly/spec/issues/150

reply "

---

this guy's ideas are mostly things we want to do:

[6]

a contract-based language supporting stuff like forall i, j in 0..len(l): i < j => l[i] <= l[j]

"Most languages have all and any functions but they don’t allow quantifying over multiple elements, and basically no languages have an implication operator (because it’s near-useless for programming). I want to see a language integrate contracts into the syntax well. At the very least, dedicated syntax for preconditions, postconditions, and inline assertions. Also the ability to label and reuse predicates, and “orthogonal” contracts (postcondition A is only checked if precondition A’ was true)."

" One of the coolest things Eiffel sorta did was use contracts to infer tests. If you have contracts, you can use a fuzzer to get integration tests for free. Clojure’s Spec does something kinda similar, I think."

semantic relations

"Inheritance and interfaces are relationships between classes. But what about relationships between functions?"

"window2 is an optimized version of window1 and should have the same outputs for every input. I should be able to encode that in the language, and have the tooling generate checks showing the two are the same, and also run benchmarks testing if the optimized version actually is faster.

I can think of other relationships between stuff. A' is an instrumented version of class A. g is an inverse of f such that f(g(x)) == x. P' is an ontological subtype of P but not a Liskov subtype. UML had things like “traces” and “refines” and “generalizes”, there could be things there too.

Would go nicely with contracts, too, as you could have relationships that preserve/transform/expand contracts. The usual one is that “inherited methods have less restrictive preconditions and more restrictive postconditions.” What else could we do? "

"Everything is a Graph

    Lisp: everything’s a list
    Bash: everything’s a string
    Smalltalk: everything’s an object
    APL: everything’s an array"

"A better calculator language"

"I have a love/hate relationship with J. There’s so much about the language that drives me batty, but I stick with it because it’s the only language that gets remotely close to being a good desktop calculator. When doing quick computations for work projects, I care about two things. The first is the number of keystrokes. Here’s how to get the product of factorials of a list in python:1

import math prod([math.factorial(x) for x in l])

No! Bad python! In J it’s just */ ! l,

..

 J is very much “everything is an array”, though, which limits its potential as a calculator. You can’t work with strings, json, sets, or hash maps very well, date manipulation is terrible, you can barely do combinatorics problems, etc etc etc. I want a language that’s terse for everything. Maybe you could namespace operators.

The other feature I want for a calculator is built-in reactive programming, like an unholy combination of a textual programming language and Excel. I want to be able to do something sorta like this:

input = 1

out1: input + 1

  1. out1 is now 2

input = 4

  1. out1 is now 5

out2: out1.replace(+, -)

  1. out2 is now 3
  2. let's pull an APL input = 4 2
  3. out1 is 5 3
  4. out2 is 3 1 "

" A really dynamically-typed language

you can generate new types at runtime! Combine that with contracts and we can attach constraints to variables, like this:

type Interval(x, y) <: Int { x <= self <= y; }

var i: Interval(1, 10) = 3 i += 7 #ok i += 1 #error!

How cool is that?! Now I don’t know how useful this would be (maybe it’s just syntactic sugar over classes), ...

I also like the idea of modifying function definitions at runtime.

"

"

A language designed around having first-class GUI support

I miss VB6. "

---

"From TFA:

type Interval(x, y) <: Int { x <= self <= y; }

var i: Interval(1, 10) = 3 i += 7 #ok i += 1 #error!

You can do this in Ada, and it will even detect it at compile-time in most cases:

procedure foo is type Interval is range 1..10; I: Interval := 3; begin I := I + 7; I := I + 1; end foo;

Gives me

foo.adb:6:12: warning: value not in range of type "Interval" defined at line 2 foo.adb:6:12: warning: "Constraint_Error" will be raised at run time

" -- [7]

i'm not sure if i care about DYNAMIC types tho..

---

" Also from TFA:

    A language designed around having first-class GUI support
    I miss VB6.

I miss REBOL. :(

    5
    apg 7 hours ago | link | flag | 
    I miss REBOL. :(

Isn’t Red the logical successor of REBOL? Not sure how much community has formed around it (I mean, I’ve never encountered RED in the wild…), but it always struck me as interesting, at least. https://www.red-lang.org/

    ~
    lorddimwit 7 hours ago | link | flag | 

Red is super neat. Last I looked (which was a long time ago) at it they didn’t support a View-like dialect on anything but Windows but it looks like that’s changed! That’s awesome. Good for them. I’ll need to look at it again.

"

---

" ~ hwayne 6 hours ago

link flag

The interval example is just one example, I also want to be able to do something like

type SortedList?[T] <: List[T] { T is Comparable forall x, y in 0..Len(self): x < y => self[x] < self[y] }

def binary_search(l: SortedList?[T], elem: T) { ... }

    ~
    pyj edited 6 hours ago | link | flag | 

Possible in Ada 2012, without using SPARK. You can also use Preconditions/Postconditions in Ada 2012 without requiring proofs, as a runtime assertion mechanism.

-- Rough, probably doesn't compile as is, but this is the idea: generic type T (<>) is private; package Lists type List(Length : Natural) is record Elements : array(Natural range <>) of T; end record with Type_Invariant => Is_Sorted;

    function Is_Sorted(L : List) return Boolean;end Lists;

Actual Example of Type_Invariant Usage, which has Pre/Post usage in the same file https://github.com/pyjarrett/trendy_terminal/blob/2667fcafcdc37c82a64eac119242dc8be035535b/src/common/trendy_terminal-lines.ads#L42

5 Corbin edited 6 hours ago

link flag

That sixth language feature shows up in several lineages of Smalltalk. I’m most familiar with the lineage that contains E and my spinoff language Monte, but I think it also shows up in Newspeak and Objective-Smalltalk. We generally call them “guard objects” or just “guards” in E, by analogy with guard clauses. The E lineage has syntactic sugar for interval guards. In E-on-Java:

? var i :(1..10) := 3

  1. value: 3 ? i += 7
  2. value: 10 ? i += 1
  3. problem: 11 is not in the region 1..!11

In Monte using the Typhon VM:

⛰ var i :(1..10) := 3 Result: 3 ⛰ i += 7 Result: 10 ⛰ i += 1 Exception: 11 is not in <[1, 10] Int region>

Edit: Here is the sorted-list example from another thread. E/Monte guards are Turing-complete.

⛰ def SortedList?.coerce(l :List, ej) { for i in (0..!l.size() - 1) { def j := i + 1; if (l[i] > l[j]) { throw.eject(ej, `list not sorted at ($i..$j)`) } }; return l } Result: <SortedList?> ⛰ def l1 :SortedList? := [1,2,3] Result: [1, 2, 3] ⛰ def l2 :SortedList? := [3,2,1] Exception: list not sorted at (0..1)

" -- [8]

---

 "
    Everything is a Graph

Actually, I would like someone to create a Lisp based around a Lua table. Something that goes further than Lua itself does. Where all the code has a canonical representation as a table as well, to make macro writing easier. Also the metatable stuff in Lua is a little clunky, so perhaps more integrated support for that would also be great.

I just have a feeling that there’s a really powerful and compact design (and implementation) for a language based around this idea. Maybe not as small and concise as Forth, but still pretty small. "

---

"If you’re into Object Pascal, Lazarus is all about UI programming like old VB6. Your just have to be into Object Pascal..." [9]

---

 "matklad 6 hours ago 
link flag

Heh, the only thing I think I need is some variant of ML with rust-level quality of implementation. Specifically, I think I need:

    Functional programming language (as in “programming with values”, not as in “programming with category theory”)
    Aimed at high-level programs (think Java/Go/Swift/Python/Ruby/C#)
    With ruthless focus on human side in the human-computer continuum (so, all ints are big ints, no thinking about stack vs heap, no async in surface-syntax)
    And reasonable concurrency story (either Erlant-style separte heaps, or, if someone invents Send/Sync+borrow checker which is not hard, that)
    With low-overhead implementatin (seamlessly works on all platforms, fast to compile, static binaries)
    And a minimal type-system which strongly encourages low-abstraction code, but still allows implementing JSON serialization in a non-horrible way.

Basically, today I use Rust for everything because it “just works” more than anything else I’ve tried (didn’t’ try Go though), but I really don’t need blazing fastness for eg, my shell scripts. OCaml is in theory what I need, but I find it’s quality of implementation lacking.

    ~
    c-cube 4 hours ago | link | flag | 

Reading the list, I was definitely thinking that OCaml almost fits the bill. Its compiler is actually very good (when it comes to emitting correct code, being fast, and having few compiler bugs).

What is lacking is indeed a few “modern” things:

    no standard deriving/serde (where rust clearly shines)
    bigints are not the default (zarith is neat but it’s not as seamless as int)
    the portability/static binary story is lacking.

It’s really sad, because OCaml with some of Go’s tooling qualities would be incredible. Maybe someone will step up and develop the next ML :-).

    ~
    matklad 4 hours ago | link | flag | 

Yeah, OCaml is frustratingly close, but I also have a few gripes with its inner workings:

    objects are redundant
    polymorphic equality/hash/comparison is awkward, which is a sign of a deeper ad-hoc polymorphism issue
    compilation model with a shared global namespace and without explicit DAG of dependencies is pretty horrible (ocamldep is one of the grossest hacks in my book)
    no mature multicore support
    ~
    zem edited 2 hours ago | link | flag | 

ocaml objects aren’t any more redundant than any feature in any language that has more than one way to do things. they may not be much used due to cultural/ecosystem choices, but every now and then they are the most ergonomic solution to what i’m trying to do.

the namespace issues are indeed a pain though :( after getting a day job working in python i have been dissatisfied with namespacing in both ruby and ocaml, which are otherwise two of my favourite languages. "

---

~ abarbu 6 hours ago

link flag

Pretty much all of these exist today. That doesn’t mean they’re good ideas.

    I also like the idea of modifying function definitions at runtime. I have these visions/nightmares of programs that take other programs as input and then let me run experiments on how the program behaves under certain changes to the source code.

Emacs. This is the worst aspect of Elisp.

    A language with semantic relations

That’s the goal of Haskell and in most cases it works extremely well. If the type is exact enough and the function is pure, you read the type, you know what it does. Replacing one function with another is fine as long as the types match. Of course, this depends on well your types model your problem.

    Everything is a Graph

We live in the wrong universe for this. Had RAM continued to be very fast relative to CPUs this would make sense. In our universe, it’s like shooting yourself in the foot.

    A serious take on a contract-based language … Clojure

Racket has far better contract support than Clojure. Arguably, Racket’s contracts are even better than Eiffel’s.

    A really dynamically-typed language … I want to write metaprograms dammit

Racket again, it’s all about metaprogramming.

---

~ JohnCarter? 1 hour ago

link flag

People are always so so confused about what asserts mean, and hence we miss so many opportunities for safety AND performance.

Wouldn’t it be nice if a compiler could warn us it there was any path on which a precondition check could fail?

Wouldn’t it be nice if the precondition checks informed the optimizer about characteristics and relationships of the variables involved?

Awhile back I said on this forum it would be interesting to have a doubly link list based language… and it took me quite some time to spot the obvious flaw. You can’t have (cheap) immutable data structures .

But yes, a digraph as a first class citizen would be very useful. It really is a pretty fundamental data structure.

I have lost my fear of metaprogramming. They’re just another program. And like any other program they the intermediate results need to be readable, inspectable, debugable and testable.

And yes, graphics on a ye olde 6502 based UK101 was much easier than anything today.

---

Ci looks pretty awesome, surprising that i havent heard of it until now: https://github.com/pfusik/cito/blob/master/ci.md

it's a lowest-common-denominator that can be transpiled to C, C++, C#, Java, JavaScript?, Python, Swift and OpenCL?

---

"Now that I’m a full-time step-dad, for the past couple of years my style has become noticeably more basic. I call it FASE: functions, arrays, structs, enums. " -- [10]

---

" We updated .NET SDK templates to use the latest C# language features and patterns. We hadn’t revisited the templates in terms of new language features in a while. It was time to do that and we’ll ensure that the templates use new and modern features going forward.

The following language features are used in the new templates:

    Top-level statements
    async Main
    Global using directives (via SDK driven defaults)
    File-scoped namespaces
    Target-typed new expressions
    Nullable reference types" -- [11]

---

https://www.foonathan.net/2022/07/carbon-calling-convention/ argues that Carbon, a new systems language, improves on the syntax/ABI for functions to take pointers in ways that makes the language safer and more efficient compared to C/C++

https://lobste.rs/s/dp9cup/carbon_s_most_exciting_feature_is_its https://outerproduct.net/boring/2021-05-07_abi-wrong.html

---

" Using std::shared_ptr in a performance-sensitive context (e.g. after startup completes) is code smell.

Using pointers as important elements of a data structure, such that cycles are possible at all, is itself code smell. A general graph is usually better kept as elements in a vector, deque, or even hash table, compactly, with indices instead of pointers, and favoring keeping elements used near one another in the same cache line. Overuse of pointers to organize data tends to pointer-chasing, among the slowest of operations on modern systems.

Typical GC passes consist of little else but pointer chasing.

But the original article is completely, laughably wrong about one thing: an atomic increment or decrement is a remarkably slow operation on modern hardware, second only to pointer chasing. " -- [12]

---

"

CyberDildonics? on Oct 24, 2016

parent context favorite on: Chicken Scheme

What is 'the MTA'. Everything I tried to find just talked about 'Cheney on the MTA' without ever defining Cheney or the MTA.

qwertyuiop924 on Oct 24, 2016 [–]

right. Brace yourself, you're in for a long ride.

If you want to get in-depth on Cheney on the MTA, I'd reccomend Peter Bex's article on the Chicken GC (http://www.more-magic.net/posts/internals-gc.html), and the original paper, "CONS should not CONS its arguments, part II: Cheney on the MTA". But I'll do my best to sum it up here.

Cheney on the MTA is a really neat hack that allows for cheap continuations, realtively efficient C compilation of Scheme code, and an effective garbage collection scheme.

Effectively, with CMTA, you have a generational GC, but the first generation is stack allocated. When the stack allocations run out of space, all the data is copied out to the heap, into the second generation of GC. This is super effective because most data is only allocated for a short time, so it never goes out to heap, and your accesses are closer together, and also on the stack, so there's less chance of a cache miss. But it gets better: In Scheme, a program's continuation (think basically its instruction pointer and stack state) can be reified as an actual data structure. However, since you're GCing the stack already, this doesn't causr any overheard, or at least not nearly as much as it does in, say, Guile, which has to copy the stack every single time a continuation is created.

The reason it's called Cheney on the MTA is because it uses Cheney's algorithm for GC, and because it's using the stack to hold data, popping the stack would invalidate program state, so it never returns.

What never returning has to do with the MTA is a cultural alusion that would take too long to explain here. But that's never stopped me before.

It's a reference to a song called "M.T.A", originally created for Walter O' Brian's campaign in 1949 (Boston's subway was known as the MTA at the time). It became a hit when recorded by The Kingston Trio in 1959. Looking up the song is left an excercise to the reader.

Unofficially, the song is known as "Charlie on the MTA," hence the title of the paper. And yes, this is where the CharlieCard? gets its name.

The song is fairly entrenched in the area: I grew up in Connecticut, and know the lyrics from memory. And on one of several trips to Boston, I've seen at least one person expect to have to pay on their way out of the subway system because of the song :-). "

---

"

lumost 10 days ago

root parent prev next [–]

It’s gotten better over the years. Java originally did not have generics, then there was the factory/uml everything crowd. Recently modern Java has been evolving towards ergonomics with streams/loom/guice/Lombok etc.

However we still don’t have something like auto/let from cpp/rust.

reply

brabel 10 days ago

root parent next [–]

Typical criticism of Java: outdated by several years...

Java has had "var" since at least Java 11 (current version is 18, with version increasing every 6 months, so you're talking about Java from 4 years ago).

With new features every 6 months, yes, 4 years ago is an eternity in Java world these days... 4 years in the future, almost certainly Java will already have virtual threads (like Go coroutines), full support for pattern matching on records (ADTs like OCaml), true value types (Project Vallhalla) and some other stuff that is making other languages stay behind Java in many aspects.

reply "

---

" I definitely don’t use C for an “aesthetic experience” (I do not enjoy aesthetics of the preprocessor, spiral types, tediousness of malloc or header files). I would consider C++ also a C replacement in the same technical sense as Rust (native code with minimal runtime, C ABI, ±same performance), but to me Rust addresses C’s problems better than C++ does. ... Sorry for the obvious bait, but if you don’t like C, why do you use it? :-). If you’re looking for a non OOP language that can replace C, well, there’s a subset of C++ for that, and it’s mostly better: replace malloc with smart pointers, enjoy the RAII, enjoy auto, foreach loops, having data structures available to you, etc. ... But I’m afraid that “C-like” and safe are at odds with each other. I don’t mean it as a cheap shot against C, but if you want the compiler to guarantee safety at compilation time, you need to make the language easy to robustly analyze. This in turn requires a more advanced static type system that can express more things, like ownership, slices, and generic collections. This quickly makes the language look “big” and not C-like. "

---

https://blog.metaobject.com/2022/08/native-gui-distributed-system-in-tweet.html

http://objective.st/

---

ufo on April 25, 2021

next [–]

A very interesting trick! In my opinion, the big takeaway here is that if you are willing to write your C code in tail call style, you can get a lot of control over which variables are stored in machine registers. In the x86-64 ABI, the first 6 function parameters are guaranteed to be passed via registers.

Obviously, this is architecture-specific. Other architectures may use a different calling convention that ruins this kind of manual register allocation. I'd imagine that it would be bad for performance in 32-bit systems, where function arguments are passed via the stack.


> I think it’s likely that all of the major language interpreters written in C (Python, Ruby, PHP, Lua, etc.) could get significant performance benefits by adopting this technique.

I know that at least Lua and Python use "computed gotos" in their inner loops, which also helps the compiler generate better code. The architecture-dependent nature of the tail-call trick could be a problem here. Some of these interpreters also need to work well on 32-bit systems.

-- https://news.ycombinator.com/item?id=26931581


https://buttondown.email/hillelwayne/archive/six-programming-languages-id-like-to-see/ https://news.ycombinator.com/item?id=32081808

---

megameter on May 18, 2021

parent next [–]

My current "VM" inspirations include...regular expressions, constraint logic, modular synthesizers, three-ring binders.

You can go really, really far away from a computing system being a Turing machine with arithmetic, if you work at it - like, that's the thing you already have in every PL. But I think getting away from that also entails getting down and dirty with your chosen benchmark of expressiveness, and accepting that the system does not do some things well, and if you want to have multiple things coordinated, then you need to be careful about expressing that, too, to avoid just making a Turing tarpit.

justanothersys on May 18, 2021

root parent next [–]

Yeah this is exactly why I came up with whistlegraphs. I was doing a lot of thinking about how to define a VM for dynamic graphics and drawing and then realized I could start to specify things at a much higher level of abstraction before ever needing the computer. (https://www.youtube.com/watch?v=YXUUCkqv2LY)

---

"Rust explored a lot of designs before 1.0. It tried to have GC pointers, Erlang-like tasks, Golang-like green threads. When people wonder “why won’t Rust just add feature X?”, surprisingly often the answer is “Rust 0.x had it, and decided not to keep it”." -- [13]

---

1 point by bshanks on Oct 24, 2013

parent context prev next [–]on: Deep C and C++ (2011)

I think this presentation is interesting from a programming language design point of view; if you were designing a new language, this presentation highlights various 'gotchas' in C and C++ that might be the sort of thing you would try to avoid creating in your new language.

Many of these fall under the heading of opportunities to apply the Pythonic design criterion "refuse the temptation to guess":

1) in C, according to one of the comments, it was claimed (i didn't check) that if you declare your own printf with the wrong signature, it will still be linked to the printf in the std library, but will crash at runtime, e.g. "void printf( int x, int y); main() {int a=42, b=99; printf( a, b);}" will apparently crash.

-- A new programming language might want to throw a compile-time error in such a case (as C++ apparently does, according to the slides).

2) In C, depending on compiler options, you can read from an uninitialized variable without a warning

-- A new programming language might want to not auto-initialize any variables, and to throw a compile-time error if they are used before initialization.

3) In C, code like "int a = 41; a = a++" apparently compiles but leaves 'a' in an undefined state because "you can only update a variable once between sequence points" or it becomes undefined, but on many compilers works anyway. A sequence point is "a point in the program's execution sequence where all previous side effects SHALL have taken place and all subsequent side-effects SHALL NOT have taken place".

-- A new programming language might want to throw a compile-time error in such a case

4) In C, the evaluation order of expressions is unspecified. so code like "a = b() + c()" can call b() and c() in any order. If they have side effects then this might matter, yet no compiler error is given. However, the evaluation order of a() && b() IS specified.

-- A new programming language might want to throw a compile-time error when side-effectful code is called in context in which the order of evaluation is unspecified.

Other miscellaneous gotchas:

5) In C, static vars (but not other vars) are initialized to 0 by default.

-- A new programming language might want to either auto-initialize all variables, or to not auto-initialize any variables,

6) The presentation says "C has very few sequence points. This helps to maximize optimization opportunities for the compiler.". This is a tradeoff between optimization vs. principal of least surprise.

-- A new programming language which wanted to make things as simple as possible would maximize 'sequence points', putting them in between practically every computation step. But some new programming languages would choose to minimize sequence points in order to allow the compiler to optimize as much as possible.

7) The presentation says that the standard says that source code must end with a newline.

-- Imo that's a bit pedantic and the ideal programming language would not care if code ended in a newline.

8) In one context (inside a function), the 'static' keyword is used to make a variable persist across calls to that function. But in another context (outside of any function), the same 'static' keyword is used as an access modifier to define visibility to other compilation units!

-- Using the same keyword for two different (albeit related) purposes is confusing. A new programming language might either drop one of those features entirely, or have a distinct keyword for it.

---

"

The Most Memory Safe Native Programming Language

    Generational References' Hidden Superpower
    FFI and Region Isolation
    Performance
    Tying it All Together
    Vale's Vision

Side Notes (interesting tangential thoughts) 0

Tracing garbage collection (like in Java) is a great solution for memory safety, but there are cases where more predictable performance is highly desirable, such as in embedded devices, games, or certain kinds of servers. 1

They're not just fast, but they could get even faster when we introduce regions and Hybrid-Generational Memory 2

For example, when Python code sends a Python object into C, if the C code doesn't correctly call Py_INCREF, it will corrupt Python's memory and cause some mysterious behavior later on in the Python code. 3

We could also protect against malicious code with sandboxing, via webassembly or subprocesses. This is a planned feature, see Fearless FFI for more on this! 4

We might even call it an unsafe block, if that doesn't cause confusion with other languages. Vale's Vision

Vale aims to bring a new way of programming into the world that offers speed, safety, and ease of use.

The world needs something like this! Currently, most programming language work is in:

    High-overhead languages involving reference counting and tracing garbage collection.
    Complex languages (Ada/Spark, Coq, Rust, Haskell, etc.) which impose higher complexity burden and mental overhead on the programmer.

These are useful, but there is a vast field of possibilities in between, waiting to be explored!

Our aim is to explore that space, discover what it has to offer, and make speed and safety easier than ever before.

In this quest, we've discovered and implemented a lot of new techniques:

    Generational Memory, for a language to ensure an object still exists at the time of dereferencing.
    Higher RAII, a form of linear typing that enables destructors with parameters and returns.
    Fearless FFI, which allows us to call into C without risk of accidentally corrupting Vale objects.
    Perfect Replayability, to record all inputs and replay execution, and completely solve heisenbugs and race bugs.

These techniques have also opened up some new emergent possibilities, which we hope to implement:

    Region Borrow Checking, which adds mutable aliasing support to a Rust-like borrow checker.
    Hybrid-Generational Memory, which ensures that nobody destroys an object too early, for better optimizations.
    Seamless concurrency, the ability to launch multiple threads that can access any pre-existing data without data races, without the need for refactoring the code or the data.
    Object pools and bump-allocators that are memory-safe and decoupled, so no refactoring needed.

We also gain a lot of inspiration from other languages, and are finding new ways to combine their techniques:

    We can mix an unsafe block with Fearless FFI to make a much safer systems programming language!
    We can mix Erlang's isolation benefits with functional reactive programming to make much more resilient programs!
    We can mix region borrow checking with Pony's iso to support shared mutability.

...plus a lot more interesting ideas to explore!

The Vale programming language is a novel combination of ideas from the research world and original innovations. Our goal is to publish our techniques, even the ones that couldn't fit in Vale, so that the world as a whole can benefit from our work here, not just those who use Vale.

Our medium-term goals:

    Finish the Region Borrow Checker, to show the world that shared mutability can work with borrow checking!
    Prototype Hybrid-Generational Memory in Vale, to see how fast and easy we can make single ownership.
    Publish the Language Simplicity Manifesto, a collection of principles to keep programming languages' learning curves down.
    Publish the Memory Safety Grimoire, a collection of "memory safety building blocks" that languages can potentially use to make new memory models, just like Vale combined generational references and scope tethering.

" -- [14]

---

I've been thinking about one remark within a comment that i got a few weeks ago:

"Temporal memory safety is a global property and so requires global synchronisation or some language properties that limit it" -- https://lobste.rs/s/qdtqi2/automatic_memory_management_with_short#c_huwazc

What are other global properties to consider during language design?

Especially concurrency-related ones. Are the following global?

The possibility of:

I feel like the way to organize the zoo of concurrency paradigms might be to organize them along axes of whether they do or don't provide various global properties.

Recall that some definitions of expressivity vs syntactic sugar are that the semantics of something that increases expressivity involves a global transformation of code, whereas syntactic sugar is a local transformation. So, even beyond concurrency, if some sort of "safety" (or simpler-to-reason-about-code) property is global, then we'd better focus on it more than if it's local, because if we want that property it's hard to bolt on later.

So, maybe a good way to organize programing language paradigms in general might be to organize them along axes of whether they do or don't provide various global properties.

This dovetails with the idea that different programing language paradigms are categorized by what they don't allow.

I should probably ask this as a question on lobste.rs, lambdatheultimate, hackernews.

---