submitted 3 days ago by armchair_progamer to c/programming_languages
submitted 4 days ago* (last edited 4 days ago) by armchair_progamer to c/programming_languages

This article presents and explains SMoL Tutor. In summary, "SMoL Tutor is a web app that corrects common misconceptions about basic concepts in modern programming languages." Also see the paper, Identifying and Correcting Programming Language Behavior Misconceptions.

[-] armchair_progamer 4 points 4 days ago* (last edited 4 days ago)

My general take:

A codebase with scalable architecture is one that stays malleable even when it grows large and the people working on it change. At least relative to a codebase without scalable architecture, which devolves into "spaghetti code", where nobody knows what the code does or where the behaviors are implemented, and small changes break seemingly-unrelated things.

Programming language isn't the sole determinant of a codebase's scalability, especially if the language has all the general-purpose features one would expect nowadays (e.g. Java, C++, Haskell, Rust, TypeScript). But it's a major contributor. A "non-scalable" language makes spaghetti design decisions too easy and scalable design decisions overly convoluted and verbose. A scalable language does the opposite, nudging developers towards building scalable software automatically, at least relative to a "non-scalable" language and when the software already has a scalable foundation.

submitted 4 days ago* (last edited 4 days ago) by armchair_progamer to c/programming_languages

Key quote IMO:

As organizations grow, one cannot depend on everyone being good at their job, or even average, if statistics are to be believed. With this I would like to talk about the scalability of a programming language, which I will define it as:

A programming language is more scalable if an engineer unfamiliar with a code base written in it produces correct code more quickly.

Scalability is often at odds with peak effectiveness, the maximum effectiveness of an engineer who is intimately familiar with the codebase, because the features driving peak effectiveness are often enabling abstractions tailored towards the specific use case at hand, like macros and support for domain-specific languages. These abstractions can make domain experts more effective, but present an additional barrier to entry to everyone else. At the extreme end of this spectrum sit code golf languages.

[-] armchair_progamer 51 points 5 days ago* (last edited 5 days ago)
public class AbstractBeanVisitorStrategyFactoryBuilderIteratorAdapterProviderObserverGeneratorDecorator {
    // boilerplate goes here
submitted 6 days ago by armchair_progamer to c/programming_languages

This essay says that inheritance is harmful and if possible you should "ban inheritance completely". You see these arguments a lot, as well as things like "prefer composition to inheritance". A lot of these arguments argue that in practice inheritance has problems. But they don't preclude inheritance working in another context, maybe with a better language syntax. And it doesn't explain why inheritance became so popular in the first place. I want to explore what's fundamentally challenging about inheritance and why we all use it anyway.

submitted 6 days ago by armchair_progamer to c/formal_methods
submitted 1 week ago by armchair_progamer to c/programming_languages

From the article.

I present the “Lambda Screen”, a way to use terms of pure lambda calculus for generating images. I also show how recursive terms (induced by fixed-point combinators) can lead to infinitely detailed fractals.

submitted 1 week ago by armchair_progamer to c/programming_languages

This is an old language (on the frontpage "designed in 2004"). The website says the latest release is 2016, but the GitHub repo has commits since last month.

It's part the Ecere SDK ("founded in 1997"), which has an IDE and set of libraries for 3D graphics, networking, and more. The site shows various programs like Chess and a media player that have been written in eC.

submitted 1 week ago* (last edited 1 week ago) by armchair_progamer to c/programming_languages


      In programming, protocols are everywhere. Protocols describe the pattern of interaction (or communication) between software systems, for example, between a user-space program and the kernel or between a local application and an online service. Ensuring conformance to protocols avoids a significant class of software errors. Subsequently, there has been a lot of work on verifying code against formal protocol specifications. The pervading approaches focus on distributed settings involving parallel composition of processes within a single monolithic protocol description. However we observe that, at the level of a single thread/process, modern software must often implement a number of clearly delineated protocols at the same time which become dependent on each other, e.g., a banking API and one or more authentication protocols. Rather than plugging together modular protocol-following components, the code must re-integrate multiple protocols into a single component.

      We address this concern of combining protocols via a novel notion of 'interleaving' composition for protocols described via a process algebra. User-specified, domain-specific constraints can be inserted into the individual protocols to serve as 'contact points' to guide this composition procedure, which outputs a single combined protocol that can be programmed against. Our approach allows an engineer to then program against a number of protocols that have been composed (re-integrated), reflecting the true nature of applications that must handle multiple protocols at once.

      We prove various desirable properties of the composition, including behaviour preservation: that the composed protocol implements the behaviour of both component protocols. We demonstrate our approach in the practical setting of Erlang, with a tool implementing protocol composition that both generates Erlang code from a protocol and generates a protocol from Erlang code. This tool shows that, for a range of sample protocols (including real-world examples), a modest set of constraints can be inserted to produce a small number of candidate compositions to choose from.

      As we increasingly build software interacting with many programs and subsystems, this new perspective gives a foundation for improving software quality via protocol conformance in a multi-protocol setting.

Related: session types: types which enforce protocols, so that communication that violates the protocol is ill-typed.

submitted 2 weeks ago by armchair_progamer to c/programming_languages

[term-lisp] does not support variables, and its functions are rules that describe how to replace a given term with another one.

For example, consider how the booleans are defined:

(true = Bool True)
(false = Bool False)

This means "when you see the term "true", replace it with the term "Bool True"


term-lisp supports first-class pattern matching. This means that you can have functions that return patterns.

For example, consider how the if expression is defined:

(if (:literal true) a b = a)
(if (:literal false) a b = b)

Here the terms true and false are automatically expanded, so the expressions above are a shorthand for:

(if (:literal (Bool True)) a b = a)
(if (:literal (Bool False)) a b = b)


To prevent unwanted execution, expressions in term-lisp are evaluated lazily i.e. only when they are needed.


What happens if we construct an expression that looks like function application, but the function being applied is not defined? In most languages, this would result in error, but in term-lisp we will create a new datatype/constructor, e.g. the expression Pair foo bar would evaluate to... Pair foo bar i.e. we will save a new object of type Pair, containing the values of foo and bar.

Fledgling Languages List (fll.presidentbeef.com)
submitted 2 weeks ago* (last edited 2 weeks ago) by armchair_progamer to c/programming_languages

This site exists to promote languages which are in development or have not yet reached a wider audience. This includes languages that do not ever intend to reach a wider audience, but are being developed for fun, learning, academdia or for no good reason at all.

See also:

submitted 3 weeks ago* (last edited 3 weeks ago) by armchair_progamer to c/programming_languages

The author wrote a tiny AST and bytecode interpreter to test which is faster (related to the paper "AST vs. Bytecode: Interpreters in the Age of Meta-Compilation").

Tl;dr; like in the paper, the AST interpreter is faster.

Keep in mind the "meta-compilation": the AST or bytecode interpreter itself runs in a JIT interpreter, in the author's tests V8 or JSC.


submitted 3 weeks ago by armchair_progamer to c/programming_languages

Verified compiler specifically means that, besides the compiler itself, the authors have written an interpreter for the source language (here, PureLang) and target language (assembly). They then proved that, for any valid program P, the observable behavior (like side-effects but not quite) produced by source_interpreter(P) is identical to the observable behavior produced by target_interpreter(compiler(P)).

Note that the source interpreter and compiler can both be buggy. Or compiler and target interpreter, or source and target interpreters, or proof kernel, or language the interpreters are written in, or the CPU itself, or probably other things. See The Trusted Computing Base of the CompCert Verified Compiler.

CompCert was the first large-scale formally-verified compiler, publicly released in 2008 and still being improved today. It compiles C99 to a variety of CPU architectures. More recently, CakeML is a formally-verified compiler for a functional language, specifically a subset of Standard ML. This is harder because ML's semantics are further from assembly than C's; for example, the developers of CakeML also had to formally verify a garbage collector. And lastly, PureCake itself is a compiler for an even higher-level language: it similar to Haskell, and like Haskell, everything is lazy and purely functional, and side effects are through monads (example).

Thesis: Verified compilation of a purely functional language to a realistic machine semantics. Part 1 is about PureCake, part 2 is about converting an official ARM assembly specification into the proof language partly automatically (making "the target interpreter is buggy" less likely, although still possible).

[-] armchair_progamer 8 points 1 month ago* (last edited 1 month ago)

I believe the answer is yes, except that we’re talking about languages with currying, and those can’t represent a zero argument function without the “computation” kind (remember: all functions are Arg -> Ret, and a multi-argument function is just Arg1 -> (Arg2 -> Ret)). In the linked article, all functions are in fact “computations” (the two variants of CompType are Thunk ValType and Fun ValType CompType). The author also describes computations as “a way to add side-effects to values”, and the equivalent in an imperative language to “a value which produces side-effects when read” is either a zero-argument function (getXYZ()), or a “getter” which is just syntax sugar for a zero-argument function.

The other reason may be that it’s easier in an IR to represent computations as intrinsic types vs. zero-argument closures. Except if all functions are computations, then your “computation” type is already your closure type. So the difference is again only if you’re writing an IR for a language with currying: without CBPV you could just represent closures as things that take one argument, but CBPV permits zero-argument closures.

[-] armchair_progamer 6 points 1 month ago

Go as a backend language isn’t super unusual, there’s at least one other project (https://borgo-lang.github.io) which chosen it. And there are many languages which compile to JavaScript or C, but Go strikes a balance between being faster than JavaScript but having memory management vs. C.

I don’t think panics revealing the Go backend are much of an issue, because true “panics” that aren’t handled by the language itself are always bad. If you compile to LLVM, you must implement your own debug symbols to get nice-looking stack traces and line-by-line debugging like C and Rust, otherwise debugging is impossible and crashes show you raw assembly. Even in Java or JavaScript, core dumps are hard to debug, ugly, and leak internal details; the reason these languages have nice exceptions, is because they implement exceptions and detect errors on their own before they become “panics”, so that when a program crashes in java (like tries to dereference null) it doesn’t crash the JVM. Golang’s backtrace will probably be much nicer than the default of C or LLVM, and you may be able to implement a system like Java which catches most errors and gives your own stacktrace beforehand.

Elm’s kernel controversy is also something completely different. The problem with Elm is that the language maintainers explicitly prevented people from writing FFI to/from JavaScript except in the maintainers’ own packages, after allowing this feature for a while, so many old packages broke and were unfixable. And there were more issues: the language itself was very limited (meaning JS FFI was essential) and the maintainers’ responses were concerning (see “Why I’m leaving Elm”). Even Rust has features that are only accessible to the standard library and compiler (“nightly”), but they have a mechanism to let you use them if you really want, and none of them are essential like Elm-to-JS FFI, so most people don’t care. Basically, as long as you don’t become very popular and make a massively inconvenient, backwards-incompatible change for purely design reasons, you won’t have this issue: it’s not even “you have to implement Go FFI”, not even “if you do implement Go FFI, don’t restrict it to your own code”, it’s “don’t implement Go FFI and allow it everywhere, become very popular, then suddenly restrict it to your own code with no decent alternatives”.

[-] armchair_progamer 23 points 5 months ago

It's funny because, I'm probably the minority, but I strongly prefer JetBrains IDEs.

Which ironically are much more "walled gardens": closed-source and subscription-based, with only a limited subset of parts and plugins open-source. But JetBrains has a good track record of not enshittifying and, because you actually pay for their product, they can make a profitable business off not doing so.

[-] armchair_progamer 9 points 6 months ago* (last edited 6 months ago)

Rust isn't good for fast iteration (Python with GPU-accelarated is still the winner here). Mojo could indeed turn out to be the next big thing for AI programming. But there are a few issues:

  • The #1 thing IMO, Modular still hasn't released the source for the compiler. Nobody uses a programming language without an open-source compiler! (Except MATLAB/Mathematica but that has its own controversies and issues. Seriously, what's the next highest-ranking language on Stack Overflow rankings with only closed-source compilers/interpreters? Is there even any?)

    • Even if Mojo is successful, the VC funding combined with the compiler being closed-source mean that the developers may try to extract money from the users. e.g., by never releasing the source and charging people for using Mojo or for extra features and optimizations. Because VCs don't just give money away. There's a reason nearly every popular language is open-source, and it's not just the goodness of people's hearts...not just individuals, but big companies don't want to pay to use a language, nor risk having to pay when their supplier changes the terms.
  • Modular/Mojo has lots of bold claims ("programming language for all of AI", "the usability of Python with the performance of C") without much to show for them. Yes, Mojo got 35,000 speedup from Python on a small benchmark; but writing a prototype language which achieves massive speedups without compromising readability on small benchmark, is considerably easier than doing so for a large, real-world ML program. It's not that I expect Mojo to already be used in real-world programs, I don't, I get that it's in the early-stage. It's that Modular really shouldn't be calling it the "programming language for all of AI" when so far, none of today's AI is written in Mojo.

    • What makes the Mojo developers think they can make something that others can't? The field of programming languages is dominated by very smart, very talented people. So many, it's hard to imagine Mojo developing anything which others won't be able to replicate in a few months of dedicated coding. And if Mojo doesn't open-source their compiler, those others will do so, both with funding from competing companies and universities and in their spare time.

With all that being said, I have decent hopes for Mojo. I can imagine that the bold claims and toy benchmarks are just a way to get VC funding, and the actual developers have realistic goals and expectations. And I do predict that Mojo will succeed in its own niche, as a language augmenting Python to add high-performance computing; and since it's just augmenting Python, it probably will do this within only one or two years, using Python's decades of development and tooling to be something which can be used in production. What I don't expect, is for it to be truly groundbreaking and above the competition in a way that the hype sometimes paints it as.

And if they do succeed, they need to open-source the compiler, or they will have some competitor which I will be supporting.

[-] armchair_progamer 62 points 7 months ago* (last edited 7 months ago)

I’m not involved in piracy/DRM/gamedev but I really doubt they’ll track cracked installs and if they do, actually get indie devs to pay.

Because what’s stopping one person from “cracking” a game, then “installing” it 1,000,000 times? Whatever metric they use to track installs has to prevent abuse like this, or you’re giving random devs (of games that aren’t even popular) stupidly high bills.

When devs see more installs than purchases, they’ll dispute and claim Unity’s numbers are artificially inflated. Which is a big challenge for Unity’s massive legal team, because in the above scenario they really are. Even if Unity successfully defends the extra installs in court, it would be terrible publicity to say “well, if someone manages to install your game 1,000 times without buying it 1,000 times you’re still responsible”. Whatever negative publicity Unity already has for merely charging for installs pales in comparison, and this would actually get most devs to stop using Unity, because nobody will risk going into debt or unexpectedly losing a huge chunk of revenue for a game engine.

So, the only reasonable metric Unity has to track installs is whatever metric is used to track purchases, because if someone purchases the game 1,000,000 times and installs it, no issue, good for the dev. I just don’t see any other way which prevents easy abuse; even if it’s tied to the DRM, if there’s a way to crack the DRM but not remove the install counter, some troll is going to do it and fake absurd amounts of extra installs.

[-] armchair_progamer 5 points 8 months ago* (last edited 8 months ago)

Multiple ways you can do this. Most of these should also extend to multiple arguments, and although the constant is promoted to type level, you can pass it around nested functions as a type parameter.

With generics

In Java (personally I think this approach is best way to implement your specific example; also Kotlin, C#, and some others are similar):

interface HashAlgo<Options> {
    String hash(String password, Options options);

class Bcrypt implements HashAlgo<BcryptOptions> { ... }
class Argon2 implements HashAlgo<Argon2Options> { ... }
record BcryptOptions { ... }
record Argon2Options { ... }

In Haskell without GADTs (also Rust is similar):

class HashAlgo opts where
  hash :: String -> opts -> String

data BcryptOptions = BcryptOptions { ... }
data Argon2Options = Argon2Options { ... }

instance HashAlgo BcryptOptions where
  hash password BcryptOptions { .. } = ...

instance HashAlgo Argon2Options where
  hash password Argon2Options { .. } = ...

In C (with _Generic):

typedef struct { ... } bcrypt_options;
typedef struct { ... } argon2_options;

char* hash_bcrypt(const char* password, bcrypt_options options) { ... }
char* hash_argon2(const char* password, argon2_options options) { ... }

#define hash(password, options) _Generic((options), bcrypt_options: hash_bcrypt, argon2_options: hash_argon2)(password, options)

In TypeScript, inverting which type is parameterized (see this StackOverflow question for another TypeScript approach):

type HashAlgo = 'bcrypt' | 'argon2'
type HashOptions<H> = H extends 'bcrypt' ? BcryptOptions : H extends 'argon2' ? ArgonOptions : never

function hash<H>(password: string, algorithm: H, options: HashOptions<H>): string { ... }

With constant generics or full dependent types

This way is a bit more straightforward but also way more complicated for the compiler, and most languages don't have these features or they're very experimental. Dependent types are useful when your constant is non-trivial to compute and you can't even compute it fully, like vectors with their length as a type parameter and append guarantees the return vector's length is the sum. In that case generics aren't enough. Constant generics aren't full dependent types but let you do things like the vector-sum example.

In Haskell with GADTs AKA Generic Algebraic Data types (also works in Idris, Agda, and other Haskell-likes; you can simulate in Rust using GATs AKA Generic Associated Types, but it's much uglier):

data BcryptOptions = BcryptOptions { ... }
data Argon2Options = Argon2Options { ... }
data Algorithm opts where
    Bcrypt :: Algorithm BcryptOptions
    Argon2 :: Algorithm Argon2Options

hash :: String -> Algorithm opts -> opts -> String
hash password algo opts =
    case algo of
    | Bcrypt -> ... -- opts is inferred to be BcryptOptions here
    | Argon2 -> ... -- opts is inferred to be Argon2Options here

In Coq (also flipping the parameterized types again):

Inductive algorithm : Set := bcrypt | argon2.
Inductive algorithm_options (A: algorithm) : Set := bcrypt_options : ... -> algorithm_options bcrypt | argon2_options : ... -> algorithm_options argon2.

Fixpoint hash (password : string) (algo : algorithm) (opts : algorithm_options also) : string := ... .
[-] armchair_progamer 7 points 9 months ago
[-] armchair_progamer 6 points 10 months ago

Not weird and not super obscure, but if you haven't already seen this blog: https://ciechanow.ski/. Each post explains some random concept, but with interactive animations which make it intuitive and interesting.

view more: next ›


joined 10 months ago