Programming Languages

1147 readers
1 users here now

Hello!

This is the current Lemmy equivalent of https://www.reddit.com/r/ProgrammingLanguages/.

The content and rules are the same here as they are over there. Taken directly from the /r/ProgrammingLanguages overview:

This community is dedicated to the theory, design and implementation of programming languages.

Be nice to each other. Flame wars and rants are not welcomed. Please also put some effort into your post.

This isn't the right place to ask questions such as "What language should I use for X", "what language should I learn", and "what's your favorite language". Such questions should be posted in /c/learn_programming or /c/programming.

This is the right place for posts like the following:

See /r/ProgrammingLanguages for specific examples

Related online communities

founded 1 year ago
MODERATORS
26
 
 

Abstract:

We present associated effects, a programming language feature that enables type classes to abstract over the effects of their function signatures, allowing each type class instance to specify its concrete effects.

Associated effects significantly increase the flexibility and expressive power of a programming language that combines a type and effect system with type classes. In particular, associated effects allow us to (i) abstract over total and partial functions, where partial functions may throw exceptions, (ii) abstract over immutable data structures and mutable data structures that have heap effects, and (iii) implement adaptors that combine type classes with algebraic effects.

We implement associated effects as an extension of the Flix programming language and refactor the Flix Standard Library to use associated effects, significantly increasing its flexibility and expressive power. Specifically, we add associated effects to 11 type classes, which enables us to add 28 new type class instances.

See also: the Flix programming language

27
28
 
 

Abstract:

The expression problem describes how most types can easily be extended with new ways to produce the type or new ways to consume the type, but not both. When abstract syntax trees are defined as an algebraic data type, for example, they can easily be extended with new consumers, such as print or eval, but adding a new constructor requires the modification of all existing pattern matches. The expression problem is one way to elucidate the difference between functional or data-oriented programs (easily extendable by new consumers) and object-oriented programs (easily extendable by new producers).

This difference between programs which are extensible by new producers or new consumers also exists for dependently typed programming, but with one core difference: Dependently-typed programming almost exclusively follows the functional programming model and not the object-oriented model, which leaves an interesting space in the programming language landscape unexplored.

In this paper, we explore the field of dependently-typed object-oriented programming by deriving it from first principles using the principle of duality. That is, we do not extend an existing object-oriented formalism with dependent types in an ad-hoc fashion, but instead start from a familiar data-oriented language and derive its dual fragment by the systematic use of defunctionalization and refunctionalization

Our central contribution is a dependently typed calculus which contains two dual language fragments. We provide type- and semantics-preserving transformations between these two language fragments: defunctionalization and refunctionalization. We have implemented this language and these transformations and use this implementation to explain the various ways in which constructions in dependently typed programming can be explained as special instances of the general phenomenon of duality.

Background:

Expression problem (wikipedia)

Defunctionalization (wikipedia)

Codata in action, or how to connect Functional Programming and Object Oriented Programming (Javier Casas)

29
 
 

Several months ago I gave a talk at work about Hindley-Milner type inference. When I agreed to give the talk I didn't know much about the subject, so I learned about it. And now I'm writing about it, based on the contents of my talk but more fleshed out and hopefully better explained.

I call this a reckless introduction, because my main source is wikipedia. A bunch of people on the internet have collectively attempted to synthesise a technical subject. I've read their synthesis, and now I'm trying to re-synthesise it, without particularly putting in the effort to check my own understanding. I'm not going to argue that this is a good idea. Let's just roll with it.

30
 
 

In this post, I will talk about the first version of the Intermediate Representation I designed for Kunai Static Analyzer, this is a dalvik analysis library that I wrote as a project for my PhD, also as a way of learning about the Dalvik file format and improving my programming skills.

Kunai source (Github)

Kunai paper

Shuriken (Kunai successor)

Dalvik is a discountinued VM for Android

31
 
 

Pre-Scheme is an interesting Scheme dialect which is being ported to modern systems. The language, why its being ported, and the porting effort are described in the linked post.

As described in the Pre-Scheme paper, the language offers a unique combination of features:

  • Scheme syntax, with full support for macros, and a compatibility library to run Pre-Scheme code in a Scheme interpreter. The compiler is also implemented in Scheme, enabling both interactive development and compile-time evaluation.
  • A static type system based on Hindley/Milner type reconstruction, as used in the ML family of languages (eg. Standard ML, OCaml, Haskell). Pre-Scheme supports parametric polymorphism, and has nascent support for algebraic data types and pattern matching, which are recently gaining popularity in mainstream languages.
  • An optimizing compiler targeting C, allowing for efficient native code generation and portable low-level machine access. C remains the common interface language for operating system facilities, and compatibility at this level is essential for modern systems languages.

Due to the restrictions of static typing and the C runtime model, Pre-Scheme does not (currently) support many of Scheme's high-level features, such as garbage collection, universal tail-call optimization, heap-allocated runtime closures, first-class continuations, runtime type checks, heterogenous lists, and the full numeric tower. Even with these limitations, Pre-Scheme enables a programming style that is familiar to Scheme programmers and more expressive than writing directly in C.

Ironically, Pre-Scheme's original purpose was to implement the interpreter for another Scheme dialect, Scheme 48 (Wikipedia). But the lower-level Pre-Scheme is now getting its own attention, in part due to the popularity of Rust and Zig. Pre-Scheme has the portability and speed of C (or at least close to it), but like Rust and Haskell it also has a static type system with ADTs and parametric polymorphism; and being a LISP dialect, like most other dialects it has powerful meta-programming and a REPL.

32
 
 

GitHub

From README:

Lady Deirdre is a framework for incremental programming language compilers, interpreters, and source code analyzers.

This framework helps you develop a hybrid program that acts as a language compiler or interpreter and as a language server for a code editor's language extension. It offers the necessary components to create an in-memory representation of language files, including the source code, their lexis and syntax, and the semantic model of the entire codebase. These components are specifically designed to keep the in-memory representation in sync with file changes as the codebase continuously evolves in real time.

Key Features

  • Parser Generator Using Macros.
  • Hand-Written Parsers.
  • Error Resilience.
  • Semantics Analysis Framework.
  • Incremental Compilation.
  • Parallel Computations.
  • Web-Assembly Compatibility.
  • Source Code Formatters.
  • Annotated Snippets.
  • Self-Sufficient API.
33
34
 
 

Abstract from the ICFP 2024 paper:

Many abstraction tools in functional programming rely heavily on general-purpose compiler optimization
to achieve adequate performance. For example, monadic binding is a higher-order function which yields
runtime closures in the absence of sufficient compile-time inlining and beta-reductions, thereby significantly
degrading performance. In current systems such as the Glasgow Haskell Compiler, there is no strong guarantee
that general-purpose optimization can eliminate abstraction overheads, and users only have indirect and
fragile control over code generation through inlining directives and compiler options. We propose a two-stage
language to simultaneously get strong guarantees about code generation and strong abstraction features. The
object language is a simply-typed first-order language which can be compiled without runtime closures. The
compile-time language is a dependent type theory. The two are integrated in a two-level type theory.
We demonstrate two applications of the system. First, we develop monads and monad transformers. Here,
abstraction overheads are eliminated by staging and we can reuse almost all definitions from the existing
Haskell ecosystem. Second, we develop pull-based stream fusion. Here we make essential use of dependent
types to give a concise definition of a concatMap operation with guaranteed fusion. We provide an Agda
implementation and a typed Template Haskell implementation of these developments.

The repo also contains demo implementations in Agda and Haskell), and older papers/implementations/videos.

35
 
 

F is written in K, another small language. In fact, the entire implementation is in one file: http://www.nsl.com/k/f/f.k.

Example program (defines factorial and computes fac(6), from http://www.nsl.com/k/f/f/fac.f):

[[1=][][dup!pred!fac!*]cond!]fac

The main site, "no stinking loops", is full of links including to other languages (scroll down) and resources on K. Although many are broken 🙁.

36
 
 

Discuss.

37
 
 

A blog post on the evolution of method syntax in Ruby.

The author (Victor Shepelev AKA zverok) lives in Ukraine and is part of the Armed Forces. See his other posts (about Ruby, the war, and more) here.

38
 
 

NumPy-style broadcasting = being able to write code like

[[1, 2, 3],
 [4, 5, 6],  + [10, 11, 12]
 [7, 8, 9]]

and have it evaluate the same as

              [[1, 2, 3],
map (map (+))  [4, 5, 6],  (rep 3 [10, 11, 12])
               [7, 8, 9]]

(which evaluates to [[11, 13, 15], [14, 16, 18], [18, 19, 21]]).

numpy docs. Numpy and most other languages do this at runtime, which is typically easier. But Futhark is statically-typed, so the type of the result must be known at compile time, and this means the broadcasting must also be done at compile time.

39
 
 

Adding Rust FFI into Vale, in particular the part where the Vale compiler determines the signature and proper overload of a Rust function.

Our ultimate goal is to write this Vale code:

import rust.std.vec.Vec;

exported func main() {
  vec = Vec<int>.with_capacity(42);
  println("Length: " + vec.capacity());
}
Length: 42

...which would successfully make a Rust Vec and call capacity on it.

Previous

40
 
 

vvvv is a node-based visual programming environment whose programs run on the CLR (same virtual machine as C#). It has hot reloading (compilation happens in the background) and can be extended with custom nodes written in C#. It has built-in libraries for 2D and 3D rendering, image recognition, machine learning, networking, and IO for various devices (Kinect and cameras). It's been used for a lot of interactive art installations, as seen in the "showcase" section on the website.

41
3
submitted 2 months ago* (last edited 2 months ago) by [email protected] to c/[email protected]
 
 

See also: earlier years.

42
 
 

A notable case study for anyone designing a type system for their own language.

Swift's compiler can infer ambiguous expressions like enum case names based on their type, inferred from surrounding context. This makes the syntax less verbose, but at a cost; some seemingly benign expressions take several seconds to type-check, after which the compiler ultimately gives up with an unhelpful error message: "the compiler is unable to type-check this expression in reasonable time".

The linked post goes into more detail.

43
13
submitted 2 months ago* (last edited 2 months ago) by [email protected] to c/[email protected]
 
 

Forsp has:

  • An S-Expression syntax like Lisp
  • Function abstraction like Lisp
  • Function application like Forth
  • An environment structure like Lisp
  • Lexically-scoped closures like Lisp (Scheme)
  • Cons-cells / lists / atoms like Lisp
  • A value/operand stack like Forth
  • An ability to express the Lambda Calculus
  • A Call-By-Push-Value evaluation order
  • Only 3 syntax special forms: ' ^ $
  • Only 1 eval-time special form: quote
  • Only 10 primitive functions need to self-implement
  • Ability to self-implement in very little code

It's evaluator is very simple. I suspect simpler than a McCarthy Lisp eval() function, but I haven't defined a "simplicity function", so you can be the judge.

In contrast to Lisp, apply() is trivial in Forsp, and instead we have a core function called compute()

GitHub

44
 
 

In this post I will go through the evolution of the tools we use for testing the Futhark compiler. Along the way, these also became futhark test, the user-facing tool for testing Futhark programs, and although rather convenient, it is still pretty crude compared to the testing tools you will find in other languages. This post is perhaps most interesting to other language implementation hobbyists.

For reference, Futhark is an up-and-coming language for writing efficient parallel code, sort of like a more functional (Haskell-like) version of CUDA or OpenCL.

45
 
 

Paper

Demo GIFs

Abstract (emphasis added):

This paper introduces TypeLoom, a tool utilising a novel approach to add gradual, optional typing into legacy code bases of dynamically typed languages. TypeLoom leverages the Language Server Protocol (LSP) to provide in-editor type information through inlay hints and collect subsequent through code actions to type information. This approach differs from the ones that exist in so far as it requires no syntactical changes to add type hints (like in Python, TypeScript) and it does not require syntactically correct comments to provide type information (like in @ts-docs and Ruby). TypeLoom utilises a graph based data structure to provide type inference and type checking. This graph-based approach is particularly effective for gradual typing as it allows flexible representation of type relationships and dependencies.

46
 
 

Full Paper

Excerpt:

Typically, math library functions are written in a low-level language like C or raw assembler to maximize performance. But general purpose languages (like these) don't help developers avoid semantic errors in mathematical code.

How many times has this happened to you: you're writing some math computation, and you accidentally write a plus sign instead of a minus sign, or put in the wrong constant? Your programming language can't catch these bugs for you because its types, like float and double, don't distinguish between x and -x or between different constants.

But numerical code could really benefit from compiler assistance with precisely this task, especially since we expect the user to test out several different implementations of some mathematical expression and compare them for accuracy and performance. Numerical errors really throw a wrench in that process (through misleading performance or accuracy numbers) and MegaLibm therefore aims to prevent them.

47
 
 

A language which was used by Figma until recently. It compiles to JavaScript or C#.

GitHub

Why and how Figma migrated from Skew to Typescript.

Differences between Skew and TypeScript

Example from the homepage:

# Execution starts at the entry point
@entry
def fizzBuzz {
  for i in 1..101 {
    var text = mod(i, 3, "Fizz") + mod(i, 5, "Buzz")
    document.write((text == "" ? i.toString : text) + "\n")
  }
}

# The compiler inlines this function in release mode
def mod(i int, n int, text string) string {
  return i % n == 0 ? text : ""
}

# Imported declarations are just for type checking
@import
namespace document {
  def write(text string)
}
48
 
 

Scrapscript is a small, pure, functional, content-addressable, network-first programming language.

fact 5
. fact =
  | 0 -> 1
  | n -> n * fact (n - 1)

My previous post introduced the language a bit and then talked about the interpreter that Chris and I built. This post is about the compiler that Chris and I built.

49
 
 

This is about Rust, but it's a great post and likely relevant to languages with Rust-style ownership. It describes four features planned for Rust to make its borrow checker more expressive and permissive:

  • Allow a function to return a reference in one branch of a conditional and not consider it "borrowed" in the other branch (this is supported by the new borrow checker, Polonius, which should be in stable Rust very soon).
  • Allow "place" lifetimes; that is, lifetimes that refer to other variables or their fields (e.g. 'foo.bar is a lifetime that, when attached to a reference, means the reference is borrowing foo.bar or some data within it).
  • View types and inter-procedural AKA "partial" borrows: define a function that can only borrow specific fields in a structure, so that the function can be called with a structure whose other fields (at the call-site) are mutably borrowed or moved.
  • Self-referential structures: structures where one field borrows another owned field (e.g. Foo { foo: String, bar: &'self.foo str }, foo is owned and bar borrows from it). Crates like self_cell and ouroboros provide this feature, but the goal is to have it in safe Rust with a cleaner syntax.
50
 
 

From the page:

My aim is to produce a superset of C++ that has a rigorously safe subset. Start a new project, or take an existing one, and write safe code in C++. Code written in the safety context exhibits the same strong safety guarantees as safe code programmed in Rust. Indeed, lifetime safety is enforced statically with borrow checking, the signature safety technology first introduced in Rust

...

What properties characterize Safe C++?

  • A superset of C++ with a safe subset. Undefined behavior is prohibited from originating in the safe subset.
  • The safe and unsafe parts of the language are clearly delineated, and users must explicitly leave the safe context to use unsafe operations.
  • The safe subset must remain useful. If we get rid of a crucial unsafe technology, like unions or pointers, we should supply a safe alternative, like choice types or borrows. A perfectly safe language is not useful if it's so inexpressive you can't get your work done.
  • The new system can't break existing code. If you point a Safe C++ compiler at existing C++ code, that code must compile normally. Users opt into the new safety mechanisms. Safe C++ is an extension of C++. It's not a new language.
#feature on safety
#include "std2.h"

int main() safe {
  std2::vector<int> vec { 11, 15, 20 };

  for(int x : vec) {
    // Ill-formed. mutate of vec invalidates iterator in ranged-for.
    if(x % 2)
      vec^.push_back(x);

    unsafe printf("%d\n", x);
  }
}
$ circle iter3.cxx
safety: iter3.cxx:10:10
      vec^.push_back(x);
         ^
mutable borrow of vec between its shared borrow and its use
loan created at iter3.cxx:7:15
  for(int x : vec) {
              ^

A couple other languages aiming to create a "C++ successor" which is fully interoperable with C++ (analogous to TypeScript and JavaScript):

  • Carbon: an entirely new language which compiles straight to LLVM, but still strives to be highly interoperable with C++. Its goal is to add "modern" features: new syntax, parametric polymorphism, as well as removing C++'s backwards-compatibility quirks.
    • It has a section on memory safety which mentions a "safe Carbon subset". But this is a longer-term goal, whereas it's the main goal of Circle.
  • Cpp2: an alternate syntax for C++. It compiles to C++, but makes new features such as modules and concepts less verbose, while hiding or removing backwards-compatibility quirks (e.g. lists are std::array and string literals are std::string).
    • 100% memory safety is a non-goal: AFAIK it doesn't do static analysis and syntactic rewrites alone aren't powerful enough to enforce it.

Compared to the other languages, Circle remains closest to C++. The other languages try to fix other C++ "problems"; Circle's only goal is to fix memory unsafety, and otherwise keep syntax and semantics the same for maximum interoperability.

view more: ‹ prev next ›