Skip to content

Instantly share code, notes, and snippets.

@aulisius
Last active January 25, 2024 04:04
Show Gist options
  • Save aulisius/63e4f878d97eb5979224db7a364a85fd to your computer and use it in GitHub Desktop.
Save aulisius/63e4f878d97eb5979224db7a364a85fd to your computer and use it in GitHub Desktop.
Beagle Programming Language

This is the documentation for Beagle, a hypothetical programming language.

Beagle has scalar primitives.

int number = 0;
double money = 4.694;
bool is_hypothetical = true;
string hello = 'world';
void;

It also provides list, record and json types for collections, structs, and dicts respectively.

Only record is immutable.

record person(string name, int age);
person p = person("Person", 21);

list(int) numbers = [1,2,3,4];
numbers[1];

json my_response = { "name": p.name() };
my_response["age"] = p.age();

To work with these types, Beagle provides function. Beagle uses in/out parameters similar to Ada.

An example function to add 2 numbers is defined as

fn add {
  in int a = 0;
  in int b = 0;
  out int sum = a + b;
}

A function can have any number of IN/OUT parameters. All IN parameters are read-only in the scope of the function and do not allow re-assignment or modifications. IN and OUT parameters can share the same name and can be differentiated in code by use of a namespace such as in::name or out::name.

Function execution

Functions in Beagle are forever. A given function doesn't complete execution unless it's explicitly requested. What does that mean? Consider the above function add, in JS, you'd execute it by

add(1,2) // 3

Trying to do the equivalent in Beagle does not give the same result,

add(1,2) // typeof 'add'

So when does the function execute? Accessing an OUT parameter completes an execution. Do note that, the function is still very much alive, you only get the value of the computation with the given IN/OUT parameters.

add(1,2)
  .sum() // 3
  .sum() // 3
  .sum() // 3

Beagle supports partial application, so you can partially update the IN parameters at any point in time and trigger the computation.

add(1)
  .sum() // 1, as b defaults to 0
  .b(2)
  .a(5)
  .sum() // 7
  .b(4)
  .sum() // 9

IN parameters can be given through use of a Fluent style API.

You can kill the function through use of the ! operator.

add(1,2).sum()! // 3
add(1,2).sum()!.a(2) // TypeError: `int` is not a function

Contracts

It is possible to define contract (much like a type or interface). A contract for the add function can be defined as,

contract add {
 :in(int a, int b);
 :out(int sum)
}

The names given for the IN/OUT parameters are part of the contract as well. Keep in mind, all IN/OUT parameters in Beagle are bound to a contract. Every function gets a default contract that acts as the namespace for the IN/OUT parameters. The explicit notation for in string hello would be default::in string hello.

A function uses contracts.

fn numbers uses add {
  // This can be shortened to add::in if both the variable and contract use the same name.
  add::in::a int a;
  add::in int b;
  add::out int sum = a + b;
}

Here int a marked as add::in is an IN parameter bound to the add contract.

Note

To invoke a particular contract against a function,

numbers.add(1,2).sum()
numbers.add.a(1).b(2).sum()

A function can uses any number of contracts and variables can be bound to multiple contracts.

contract add {
 :in(int a, int b);
 :out(int sum);
}

contract mul {
 :in(int a, int b);
 :out(int product);
}

fn numbers uses add, mul {
  add::in mul::in int a;
  add::in mul::in int b;
  add::out int sum = a + b;
  mul::out int product = a * b;
}

numbers.add(4,6).sum() // 10
  .mul.product() // 24
  .mul.a(3)
  .add.sum() // 9

As a,b were bound as IN parameters for add, mul contracts, it was possible for either contract to use them freely.

Some more example code

fn greet {
  in string name = "Greg!";
  out string name = `Hello + ${in::name}`;
}

::print.stdout(greet.name('Casey!')!)!;
::print.stdout(greet('John')!)!;

fn sum {
  in list(int) numbers = [0];
  out int sum = 0;
  for (int i = 0; i < ::length.of(numbers); i++) {
    numbers += ::index.of(numbers).at(i);
  }
}

::print.stdout(sum([1,2,3,4]))!
    
contract is_even {
  :in(int number);
  :out(bool is_even);
}

contract is_odd {
  :in(int number);
  :out(bool is_odd);
}

fn number_checker uses is_even, is_odd {
  is_even::in is_odd::in int number = 0;
  is_even::out bool is_even = number % 2 == 0;
  is_odd::out bool is_odd = !is_even;
}

number_checker.is_even(3);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment