FreePalestine.
Justcode
no cruft: no build systems, no null, no exception handling, no ORMs, no OOP, no inheritence hierarchies, no async/await, no compilation, no dev environments, no dependency hell, no packaging, no git, no github,
no devops: no yaml, no config files, no docker, no containers, no kubernetes, no ci/cd pipelines, no terraform, no orchestrating,
no infrastructure: no sql, no nosql, no connection poolers, no sharding, no indexes, no servers, no serverless, no networking, no load balancers, no 200 cloud services, no kafka, no memcached, no unix, no OSes
Darklang puts everything in one box, so you can build CLIs and cloud apps with no bullshit, justcode.
Also it's a really enjoyable language to use!
In development - here’s how things will look shortly
$ curl https://darklang.com/download | bash
Darklang installed in ~/.darklang/bin/darklang
Add to PATH via .bashrc [y, n, ?]: y
✔Added to .bashrc.
Next you can:
# Try the tutorial
darklang tutorial
# Run some code from the package manager
darklang @paul.fizzbuzz 3
# Generate some code
darklang prompt "Find ts scripts with more than 600 lines which use the commonjs format"
# See available command line options
darklang help
$ darklang @paul.fizzbuzz 3
1
2
Fizz
$ darklang deploy @paul.fizzbuzz /fizzbuzz
Deployed to https://furry-squirrel-3562.darklang.io/fizzbuzz in 0.135s
$ curl -sSO https://furry-squirrel-3562.darklang.io/fizzbuzz/3
1
2
Fizz
$ darklang prompt "Find ts scripts with more than 600 lines which use commonjs format"
We need your AI credentials, which will be stored locally in ~/darklang/secrets/
[1] Login to use Darklang AI service
[2] Enter GitHub copilot credentials
[3] Enter OpenAI (GPT 3.5/4) credentials
[4] Use local model
Enter [1-4]: 1
Login or register [L, r]? L
Username: paul
Password: **************
✔Logged in.
Saved script in ./find-large-ts-cjs.dark in 43.8s
find-large-ts-cjs.dark
let findLargeTypescriptCommonJSFiles (path : String) =
Directory.traverse (fun path ->
if not (List.oneOf (File.extension path) [".ts", ".mjs", ".cjs"]) then
print $"Skipping - wrong file type: {path}"
else
let contents = File.readString path
let lines = String.splitNewlines contents
if (List.length lines) <= 600 then
print $"Skipping - too short: {path}"
else
let isCommonjs =
lines |> List.any (fun line ->
line |> Regex.matches "/const .* = require(.*)/"
)
if isCommonjs then
print $"Found one: {path}"
)
findLargeTypescriptCommonJSFiles "./"
Simple types using Records and Enums
Dynamic languages are great, allowing great flexibility. But now that they've matured and projects have gotten larger, static typing has been layered on top of very dynamic languages, and the intersection is not pleasant. Functional static languages like Darklang have simple Record and Enum types that can model nearly everything with much less complexity.
- Abstract Data Types made of Record and Enums can model nearly anything, like in Rust, Elm, OCaml and F#
// Record
type Url = {
scheme : HttpScheme
domain : String
port : UInt16
path : String
query : Option<String>
}
// Enum (aka Variant, Sum Type, or Abstract Data Types)
type UrlError =
| InvalidScheme(String)
| EmptyDomain
| InvalidPort(Int64)
| Unparseable(msg:String, context:String)
// Aliases are just shorthands
type UrlParseResult = Result<Url, UrlError>
We believe Object Oriented programming is a terrible way to model programs, and inheriting is a terrible misfeature. Languages like OCaml, Elm, and F# have shown that nearly all problems can be modeled using ADTs.
Option and Result types instead of null and exceptions
I think it's widely accepted that null is a mistake, and that an Option type (aka Optional, Maybe, etc) makes it significantly easier to program.
We further believe that Exceptions — which can in most languages be thrown at any point — makes it very frustrating to know that a function actually works. There are no exceptions in Darklang, and we use
Resulttypes to manage error cases?and!operators (similar to Rust, TypeScript, and Clojure) provide ways to ergonomically handle errors without being too annoying (see also Gradual Static Typing)We will admit we have some RuntimeErrors which cannot be caught, but we are working to remove them almost entirely from the language (see Gradual Static Typing for what we're keeping)
Garbage Collected
It can be fun to satisfy the borrow checker, or manage allocations individually, but it can also be fun to just get working programs immediately. We believe that run-time garbage collection is one of the greatest programming language features, and we're all in.
Unicode First
Languages created last millennium typically use Strings made of bytes, or worse, UTF16 characters, or even worse, Unicode Codepoints! Like Swift, we believe that Characters should represent screen readable characters, like 👨👩👦👦. All characters in Darklang represent Extended Grapheme Clusters: one character that you see on screen. Naturally, working on Unicode Codepoints and normal bytes is also well supported.
Instantly run any package from the CLI: darklang @username.functionName arg1 arg2
Darklang has a new model of sharing programs. Any function in the package manager can be called directly from the command line (so long as we can figure out how to coerce the command line arguments correctly)
$ darklang @paul.fizzbuzz 10
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
Next-gen package management
Darklang has a rather unique package manager, where functions and types are individually versioned and immutable, taking a lot of the hassle out of package management.
- Only download the specific package items you use
- Only upgrade the specific package items you use
- Automated dependency upgrades, as we track deprecation status, and know what functions are pure and safe to update.
- Different packages can rely on different versions of other packages
- Use multiple versions of the same package item at once: allows testing new versions without having to change an entire package version, lowering risk.
- The package manager functions as a source repository.
Gradual Static Typing
Gradual Static Typing allows running incomplete programs so you don't need to ensure everything type checks when you're getting one path working
- While prototyping, just run code until you hit a type error
- After prototyping, run the full type checker to gain confidence your whole program works
- Full type-checking hints in VSCode or in LSP editors
!and?operators allow easy error handling while you prototype- Automatic refactoring converts
!into proper error handling
Fully asynchronous runtime
Darklang has a fully asynchronous runtime, so making an HTTP call or reading a file doesn't block the runtime.
No async / await
Adding async and await keywords to every language was a mistake. It exposes the complexity of concurrency and multi-threading to languages which were originally designed for simplicity.
Concurrent and parallel execution via data-dependencies
When you make an async request, it first waits for any arguments that are async, and starts when they're done. If another function call needs to use the result, it will wait for it before starting.
Since darklang values are immutable, there won't be any race conditions from this.