Skip to content

A pure functional programming language with structural typing inspired by Haskell and TypeScript

License

Notifications You must be signed in to change notification settings

probeiuscorp/language

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tilly Logo

A pure, functional programming language inspired most by Haskell, but a bit by TypeScript.

Notable features and differences from Haskell:

  • Complement types, as the (¬) function. For example:
data Errors = NotProvided + NotInteger + NotInRange
replaceLeft = f. match
  (Left e) = f e
  right = right
defaultToZero: ∀e. Either e Int -> Either (e & ¬NotProvided) Int
defaultToZero = replaceLeft $ match
  NotProvided = Right 0
  left = left

From this function and the intersection (&), the type checker is sound and axiomatic.

  • Subtyping:
getRight: ∀a. Either ⊥ a -> a
getRight = match
  Left e = e  // Since `e` is ⊥, it is compatible with `a`
  Right a = a
  • Expression oriented and point-free. No special syntax in declarations. Anonymous function expressions are focused, with concise syntax for functions as well as for pattern matching:
id = x. x
type id = x. x
S = x y z. x z $ y z
type S = x y z. x z $ y z
mapMaybe = f. match
  (Some x) = Some $ f x
  (None) = None

Notable features kept from Haskell:

  • Lazy evaluation
  • Typeclassing
  • Curried by default

Some changes from Haskell:

  • Emphasis on pipe operator | (type ∀a b. a -> (a -> b) -> b)
  • List types are List a instead of [a]
  • Do notation dropped

Non-Goals

Respecting precedent

For example, Maybe's constructors are Some and None. I like Maybe more than Option since it is more distinctive (more searchable, filters down autosuggest more). I like Some and None and I dislike Just and Nothing.

Structural typing

Add structurally typed records inspired by TypeScript.

  1. Syntax
person = {
  name = 'Alex Generic'
  birthdate = Date 1821 4 20
  customerStatus = {
    joined = Date 2024 02 10
    loyaltyPoints = 140
    balance = 12310
  }
}
  1. Reading and updating

The value-level & operator can be used to combine the fields of two records into one. Right side values are preferred over left side values.

An update function can be derived, which takes a record with the same fields but with each property as a function.

ageWhenJoined = person.customerStatus.joined - person.birthdate
withNewAge = person & {
  name = "Alex Generic I"
  birthdate = person.birthdate + 1
  customerStatus = person.customerStatus & {
    loyaltyPoints = person.customerStatus.loyaltyPoints + 30
  }
}
withNewAgeUpdate = person | update {
  name = K $ "Alex Generic I"
  birthdate = (+1)
  customerStatus = update {
    loyaltyPoints = (+30)
  }
}
  1. Typing
type Person = {
  name: String
  birthdate: Date
  customerStatus: {
    joined: Date
    loyaltyPoints: Int
  }
}
  1. Destructuring
type Quadratic = { a: Double, b: Double, c: Double }
y: Quadratic -> Double -> Double
y = { a, b, c } x. a * x ** 2 + b * x + c

Time complexity for property access is currently undefined.

Concerns

  1. Recommended practice for orphan instances
  2. Optional fields in records
  3. Type union operator. | is taken for the pipe function, and union is not divisible as + would imply.

Ideas

  1. Algebraic effects

About

A pure functional programming language with structural typing inspired by Haskell and TypeScript

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published