Writing1 July 202310 min read
Dreaming in Code: Envisioning the Perfect Programming Language — DreamLang

Introduction
Programming languages are much like instruments; each has a unique voice, character, and purpose. Over the years, I’ve had the opportunity to compose symphonies with many such instruments, from the versatile chords of Python and JavaScript to the robust and precise notes of Rust and Go. The experience was enlightening, revealing the inherent strengths and weaknesses of each language.
Yet, every so often, I found myself yearning for something different. I dreamt of a language that combined the simplicity and ease-of-learning of Go, the robustness and safety of Rust, the flexible dynamicity of JavaScript, and the functional elegance of Haskell and Elixir.
In this article, I aim to present a detailed exposition of DreamLang, drawing from the best of existing languages and composing it into a symphony of clean, efficient, and delightful coding.
Roots in Go
Our journey into DreamLang begins with Go — a language celebrated for its straightforwardness and efficiency. A key aspect of Go that sets it apart is its small set of keywords, with only 25 in total. This minimalist approach reduces the cognitive load on the programmer, making the language quicker to learn and simpler to use.
Go’s simplicity doesn’t hinder its ability to handle complex problems, though. For instance, consider the power of Go’s goroutines, which allow for lightweight thread management for concurrent tasks. Here’s a simple example of a goroutine in action:
package main
import (
“fmt”
“time”
)
func hello() {
fmt.Println(“Hello, world!”)
}
func main() {
go hello()
time.Sleep(1 * time.Second)
fmt.Println(“main function”)
}
In this code, go hello() starts a new goroutine. Then, the main function waits for one second and prints its message. We can run functions concurrently with minimal syntax, contributing to Go’s reputation for simplicity and power.
Rust’s Enums and Pattern Matching
As we build DreamLang on the solid foundation of Go, we turn to Rust for our next inspiration — the power of its Enums and pattern matching.
In Rust, unlike many other languages, Enumerations or Enums can contain data in variants. This ability offers an expressive language feature known as Algebraic Data Types (ADTs) allowing us to model our data more accurately. Consider the following example in Rust:
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
Here, Message is an enum that represents different kinds of messages that could be sent in a hypothetical application. Each variant contains different types and amounts of information, making this structure far more expressive than what is offered by traditional enums in many other languages.
Alongside Rust’s enums, pattern matching elevates the language’s safety and expressiveness. Pattern matching in Rust works hand in hand with enums to provide a strong guarantee that all possibilities are handled in the code. Here’s an example that uses pattern matching with our previously defined Message enum:
fn process_message(msg: Message) {
match msg {
Message::Quit => {
println!(“The Quit variant was passed in.”);
}
Message::ChangeColor(r, g, b) => {
println!(“Change the color to red {}, green {}, and blue {}”, r, g, b);
}
Message::Move { x, y: new_name } => {
println!(“Move in the x direction {} and in the y direction {}”, x, new_name);
}
Message::Write(text) => {
println!(“Text message: {}”, text);
}
}
}
In terms of pattern matching, DreamLang would keep the safety and intuitiveness of Rust’s approach. Here’s an imagined example of how the process_message function might look in DreamLang:
func process_message(msg: Message) {
match msg {
Message.Quit => {
fmt.println(“The Quit variant was passed in.”);
}
Message.ChangeColor(r, g, b) => {
fmt.println(“Change the color to red {}, green {}, and blue {}”, r, g, b);
}
Message.Move { x, y } => {
fmt.println(“Move in the x direction {} and in the y direction {}”, x, y);
}
Message.Write(text) => {
fmt.println(“Text message: {}”, text);
}
_ => {
fmt.println(“Received an unknown message variant.”);
}
}
}
By integrating Rust’s Enums and pattern matching, DreamLang aims to provide developers with the tools to write expressive, type-safe, and intuitive code, harmoniously blending the simplicity of Go with the rich expressiveness of Rust.
Adopting Rust’s Way of Error Handling
In developing DreamLang, we look to other languages for features that lead to robust, maintainable, and efficient code. One such feature is Rust’s approach to error handling, which strikes a balance between transparency and usability.
Rust uses a Result type and the ? operator for error management. Functions that may fail return a Result type, which is an enum with variants Ok(value) for success and Err(err) for failure. The ? operator used after such a function call unwraps the Ok variant or immediately returns the Err variant, effectively propagating the error upwards. This approach requires developers to handle errors explicitly, enhancing code reliability.
For instance, in Rust:
use std::fs::File;
fn open_file(filename: &str) -> std::io::Result<()> {
let f = File::open(filename)?;
Ok(())
}
Here, the ? operator simplifies error handling by propagating the error if File::open fails.
Compared to Go’s explicit error handling, where an if err != nil check follows each error-possible function:
file, err := os.Open(“file.txt”)
if err != nil {
log.Fatal(err)
}
Rust’s approach minimizes boilerplate and enhances readability, especially in scenarios with multiple error-possible calls. This is why we adopt it in DreamLang. Let’s look at how DreamLang might handle file opening:
import “io”
func open_file(filename: String) -> Error {
let f = io.File.open(filename)?; // this will return error if open fails
let f = io.File.open(filename)?[ErrorType]; // this will return ErrotType and wrap error if open fails
let f = io.File.open(filename)??; // this will painc if open fails
let f = io.File.open(filename)???; // this will abort program if open fails
}
With the ? operator, DreamLang combines Rust's efficient error handling and Go's simplicity. This approach makes error handling transparent, encourages developers to address potential errors, and leads to robust, resilient code. In DreamLang, we aspire not only to ease and efficiency in writing programs but also to strength and resilience in handling errors.
Incorporating Elixir’s Pipe Operator
In our quest to build DreamLang, we borrow Elixir’s pipe operator, another powerful tool that adds elegance and readability to functional programming. The pipe operator |> takes the output of one operation and passes it as the input to the next.
In Elixir, code like this:
“HELLO”
|> String.downcase()
|> String.reverse()
reads like a series of transformations. First, "HELLO" is transformed into lowercase, then the result of that operation is reversed. The output of one function seamlessly becomes the input of the next, creating a pipeline of operations that's easy to follow.
The incorporation of the pipe operator in DreamLang will lead to cleaner code, where transformations are easily traceable, enabling a smooth flow of data between functions. We envision this to be particularly useful in scenarios involving data transformations, where a value goes through a series of changes before reaching its final form. This Elixir-inspired feature aims to make DreamLang code more readable, maintainable, and expressive.
In DreamLang, the pipe operator would operate similarly, transforming a series of function calls into a readable sequence of operations. Here’s how it might look:
import “strings”
*func transform_and_display(input: String) {
let result = input
|> strings.trim()
|> strings.toLower()
|> strings.split(“ “)
|> strings.join(“-”)
|> strings.reverse();
fmt.println(result);
}*
transform_and_display(“ HELLO WORLD “);
This longer pipeline of operations, enabled by the pipe operator, demonstrates how one can compose complex transformations in a readable and maintainable manner. By borrowing Elixir’s pipe operator, DreamLang enhances code readability and allows for elegant composition of functions, which is a core tenet of functional programming. This makes DreamLang a more expressive and efficient language for developers to work with.
Embracing Function Composition and Currying from Haskell
As we shape DreamLang, our quest to bring the best programming features together takes us to the realm of Haskell — a language renowned for its strict adherence to functional programming principles. Two essential aspects of Haskell that we adopt in DreamLang are function composition and currying.
Function composition allows for chaining functions, where the output of one function serves as the input of another. In Haskell:
let f = negate . abs
f (-5) — This would output 5
Here, negate and abs are composed to form a new function f.
Currying, another powerful concept, transforms a function that takes multiple arguments into a sequence of functions with single arguments. In Haskell:
let add = (+)
let addTwo = add 2
addTwo 3 — This would output 5
add is a curried function that takes two arguments. By providing one argument (2), we create a new function addTwo.
To enhance code readability and reusability, DreamLang brings function composition and currying to its syntax. Here’s what this looks like:
import “math”
// Function Composition
var negate_abs = math.negate . math.abs;
negate_abs(-5); // This would output 5
// Currying
func add(a: Int, b: Int) Int {
return a + b;
}
var addFive = add(5, _);
addFive(3); // This would output 8
Through function composition and currying, DreamLang encourages modular and reusable code, improving readability and adhering to the principles of functional programming.
Borrowing JSON Support from JavaScript
In the ever-evolving world of programming, JSON (JavaScript Object Notation) has emerged as a lingua franca for data exchange. Given its ease of use for both humans and machines, it is widely employed in web services, databases, and more. It’s only natural, then, that DreamLang, aspiring to be a practical and modern language, should incorporate seamless JSON support.
In JavaScript, JSON manipulation is extremely intuitive:
let student = {
“name”: “John”,
“age”: 30,
“city”: “New York”
};
student.age = 40; // Change a property
Here, a JSON object student is created, and its properties can be accessed and modified directly.
DreamLang goes a step further, treating JSON as a native data type, allowing for direct creation and manipulation without the need for explicit parsing or stringifying:
var student : JSON = {
“name”: “John”,
“age”: 30,
“city”: “New York”
};
student.age = 40; // Change a property
For increased safety and predictability, DreamLang introduces strong typing for JSON objects based on JSON Schemas:
type Student struct {
“name”: String,
“age”: Int,
“city”: String
};
var john : Student = “{
“name”: “John”,
“age”: 30,
“city”: “New York”
}”
This schema-based approach enforces type checking for JSON properties, leading to fewer runtime errors and more reliable code. By seamlessly integrating JSON and adding robust type checking, DreamLang enhances the ease of handling JSON data, making it ideal for modern programming tasks.
Implementing Rust’s Macro System and Metaprogramming
Rust’s macro system and metaprogramming capabilities are impressive tools for code generation and manipulation. They allow developers to define reusable code patterns and automate tasks that would be tedious and error-prone to do manually. The power of Rust’s macros can be harnessed to create domain-specific languages (DSLs), providing a level of abstraction that enhances readability and maintainability.
Simple DreamLang Macro:
macro html {
($($body:tt)*) => {
let mut _html = “<html>\n”;
_html += $($body)*;
_html += “\n</html>”;
_html
};
(head $($body:tt)*) => {
“<head>\n” + $($body)* + “\n</head>\n”
};
(body $($body:tt)*) => {
“<body>\n” + $($body)* + “\n</body>\n”
};
(title $title:expr) => {
“<title>” + $title + “</title>\n”
};
(p $text:expr) => {
“<p>” + $text + “</p>\n”
};
}
var markup = html! {
html! { head html! { title “Hello, DreamLang!” } }
html! { body html! { p “Welcome to the DreamLang macros tutorial.” } }
};
println!(markup);
In this DreamLang example, we have a html! macro that can generate different HTML elements based on the provided tokens. The html!, head, body, title, and p constructs all contribute to building the final HTML document. This way, we can effectively create a DSL for generating HTML, which improves readability and can make handling such tasks more intuitive in DreamLang.
This is a key example of the flexibility and power a macro system can provide, making it a valuable addition to DreamLang.
Conclusion: DreamLang — Bringing the Best of Many Worlds
DreamLang represents the ideal in programming language design, combining the simplicity and pragmatic design of Go with powerful features from Rust, Elixir, Haskell, and JavaScript. DreamLang offers the robustness of Rust’s enums, pattern matching, error handling, and macros; Elixir’s pipe operator; Haskell’s function composition and currying; and JavaScript’s native JSON support.
Notably, DreamLang maintains backward compatibility with Go, allowing developers to use existing Go libraries, thereby ensuring a rich ecosystem from the start.
While DreamLang remains a concept, it embodies the evolution of programming languages — a perfect blend of simplicity, power, and versatility. It’s a vision of the future where the strengths of different languages come together to shape the landscape of software development.