Files
beginner-friendly-lang/spec.md

4.6 KiB

Arithmetic

1.2 * 4 + 1
# result: 5.8 

Few types to learn

Most important ones:

  • Num: big-decimal
  • List[T]: (linked-) list of T
  • Char: unicode codepoint
  • Unit: nothing
  • End: unreachable
  • Cell[T]: mutable cell of T

Advanced (uncommon) types:

  • Int8, Int16, ...
  • Uint8, ...
  • Flt32, Flt64

Functions

def msg(username: List[Char]) : List[Char] {
  "Hello, " ++ username ++ "!"   # last expr is returned
}

def main() {
  print("hi")
}
main()

Simple, forward type-inference

def zero ()  : Flt32 {
  3.1       # error: got Num, but expected F32
}

Bindings

let name = "Max"
let passw = "1234"

Mutability only via ref

let value = Cell.from(1)
print(Num.to_str(!value))     # !cell "observes" the cell
value := 2                    # `:=` mutates the cell
print(Num.to_str(!value))

no confusing function or operator overloading

all operators:

  • Num + Num (has overloads for fixed width number types)
  • Num - Num (has overloads for fixed width number types)
  • Num * Num (has overloads for fixed width number types)
  • Num / Num (has overloads for fixed width number types)
  • Num ^ Num: raise to the power (has overloads for fixed width number types)
  • List[t] ++ List[t]: list concatenation
  • value :: t (explicitly specify type of value, useful for down-casting structs, or just code readability; does not perform casting)
  • list[index]
  • !cell: "observe" a mutable value
  • cell := value: mutate a mutable value

non-nominal struct types

# `type` creates a non-distinct type alias
type User = { name: List[Char] }
type DbUser = { name: List[Char], pass: List[Char] }

def example(u: User) : DbUser {
  u with pass: "1234"
  (* has type { name: List[Char], pass: List[Char] } *)
}

def example2() : {name: List[Char], pass: List[Char]} { 
  {name:"abc", pass:"123"}
}

def example3() : User {
  example2()     # {name:.., pass:...} can automatically decay to {name:...}
}

(tagged) union types

type Option[t] =
  'Err        # If no type specified after tag, defaults to Unit
| 'Some t

# the tags of unions are weakly attached to the types, but won't decay unless they have to
def example(n: Num) : Num {
  let x = 'MyTag n  # type of x is 'MyTag Num
  x                 # tag gets removed because target type is Num
}

def example2(n: Num) : Option[Num] {
  'Some n
}

def example3-invalid() : Option[Num] {
  Unit   # error: can't convert type `Unit` into type `'Err Unit | 'Some Num`
         #        Either label the expression with 'Err,
         #        or change the return type to Option[Unit], and label the expression with 'Some
}

def exampe4(): Option[Num] {
  'Err Unit
  # type of this expression is: `'Err Unit`
  # enums can automatically cast, if all the cases from the source enum also exists in the target enum,
  # which they do here: `'Err Unit` is a case in `'Err Unit | Num`
}

def example5-error(): Option[Num] {
  let x = ( 'Err Unit ) :: Option[Unit]
  x
  # error: can't convert type `'Err Unit | 'Some Unit` into type `'Err Unit | 'Some Num`
  #        The case `'Some Unit` does not exist in the target `'Err Unit | 'Some Num`
}

def example6-error(): Option[Unit] {
  let x = 'Error Unit
  x
  # in this case, the enum tag does not decay, like in `example`,
  # because we are casting to an enum

  # error: can't convert type `'Error Unit` into type `'Err Unit | 'Some Num``
  #        1st possible solution: manually cast to just `Unit` (via `expr :: Unit`), so that it can convert to the second case of the target
  #        2nd possible solution: pattern match against the enum, to rename the tag from 'Error to 'Err
}

automatic return types

def add(a: Num, b: Num) -> _ {
  a + b
}

templated generics

def [a,b] add(a: a, b: b) -> _ {
  a + b
}

add(1,2)

add(1,"2")    # error: in template expansion of add[Num,List[Char]]: No definition for `Num + List[Char]`

pattern matching

type Option[t] = 'None | 'Some t

def [t] Match.`a++b`(
  # matching against this value
  value: List[t],
  # left hand side of operator
  l: List[t],
  # right hand side of operator
  r: MatchUtil.Var[List[t]]
) -> Option[{ r: List[t] }] {
  match List.remove_prefix(l, 'from value) {
    'Some rem -> 'Some { r: rem }
    'None -> 'None
  }
}

then you can do:

type Token = 'Public Unit | 'Private Unit | 'Err Unit;
def example(li: List[Char]) -> {t:Token,rem:List[Char]} {
  match li {
    "public" ++ rem  -> {t: 'Public Unit, rem:rem}
    "private" ++ rem -> {t: 'Private Unit, rem:rem}
    _ -> {t: 'Err Unit, rem: li}
  }
}

pattern 1: labelled arguments

def [t] List.remove_prefix(prefix: List[t], list: 'from List[t])