Par

An experimental programming language bringing the expressive power of linear logic into practice.

Playground.par
module Playground

import {
  @core/Int
  @core/Nat
}

def Describe = [n: Int] if {
  n < 0       => "Negative",
  n == 0      => "Zero",
  0 < n < 100 => "Small",
  else        => "Incomprehensible",
}

dec Factorial : [Nat] Nat
def Factorial = [n]
  Nat.Range(1, n).begin.case {
    .end!       => 1,
    .item(x) xs => x * xs.loop,
  }

dec Fibonacci : iterative choice {
  .next  => (Nat) self,
  .close => !,
}

def Fibonacci = do {
  let a = 0
  let b = 1
} in begin case {
  .next => do {
    let (a, b)! = (b, a + b)!
  } in (a) loop,
  
  .close => !,
}

Totality

Programs should finish, or keep productively communicating.

Par rules out broad classes of errors before the program even runs: no runtime panics, no deadlocks, and no accidental infinite loops.

No panics Errors are represented in types and handled explicitly.
No deadlocks Communication follows a tree-like structure.
No accidental infinite loops Recursion and corecursion use begin and loop.
Downloader.par examples
def Main =
  let rootPath = Os.Path(".").append("Downloads") in
  YieldDownloadRequests("127.0.0.1:3000", rootPath)
    ->List.Map(type List<Event>, box DownloadFile)
    ->FanIn
    ->ShowDownloadProgress(rootPath)

Automatic Concurrency

Whatever can run concurrently, runs concurrently.

Blocking is local and induced by data dependencies. In Par, even ordinary-looking structures like lists can carry asynchronous work as their elements become available.

One Foundation

Several styles emerge from the same logic.

Par is Curry-Howard isomorphic to classical linear logic. At its core it is a process calculus: everything is a channel, and every operation is communication. Even lists and maps are communication channels. With syntax sugar, familiar paradigms emerge naturally as sub-languages of this single foundation.

Functional

Functions are not everything, but...

Higher-order functions, algebraic data, and list combinators sit naturally in Par.

Functional.par
let answer = Int.Range(1, 100)
  ->List.Filter(box [i] i->Int.Mod(2) == 0)
  ->List.Map(type Int, box [i] i * i)
  ->List.Sum

Object-Oriented

Objects are iterative choices.

Choice and iterative types give Par an object-like style where methods are branches of a communication protocol.

ObjectOriented.par
let builder = String.Builder
builder.add("Hello")
builder.add(", ")
builder.add("world!")
let answer = builder.build

Process Syntax

Duality governs interaction.

Par is ultimately a process language. A chan expression lets a value be constructed by talking to its consumer from the other side.

ProcessSyntax.par
// = *(1, 2, 3)
def Numbers: List<Int> = chan yield {
  yield.item(1)
  yield.item(2)
  yield.item(3)
  yield.end!
}
Protocol.par process syntax
let map = Map.New(type String, type Int)
// Even a map has a protocol.
// Calling `.entry` advances it.
map.entry("A")[default(0) count]
// The entry "A" is activated. Now we must call
// - `.put` to insert a value, or
// - `.delete` to remove the entry.
map.put(count + 1)
// Only now can we call `.entry` again.

Session Types

Types are communication protocols.

All values in Par are communication channels. For example, a map is not just a passive container; it has a protocol, and each operation advances that protocol.

When you call .entry, the map moves into the part of the protocol for resolving that key. The type then guides what must happen next before the map is available as a map again.

Explore Par

A language from another planet.

Par is named after the linear-logic connective , pronounced "par". It is an attempt to bring the expressive power of classical linear logic, session types, and process calculi into a practical language.

It is not really possible to make this paradigm feel completely familiar. Take your time with it. If the direction speaks to you, stars, feedback, discussion, and sponsorship all help.