Skip to content
WIP High-Level Functional Programming Language Focused On Efficiency
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
examples Added stdin example Jul 16, 2019
leafstd Added full module and import support Jul 16, 2019
src Small prettifying changes Jul 17, 2019
Cargo.lock Small prettifying changes Jul 17, 2019
Cargo.toml Small prettifying changes Jul 17, 2019
LICENSE.md Added LICENSE.md Jul 2, 2019
README.md Updated README.md Jul 17, 2019

README.md

Leaf - A high-level Interpreted functional programming language

Introduction

Leaf's a WIP programming language focused on efficiency of writing, readability and simplicity.

Full Design

|| This program tries to display all syntastic parts and features of the Leaf programming language
|| Please note that since the interpreter is WIP this program will >>>NOT<<< run yet. 
|| See all other examples for up-to-date examples that are runable with the current state of the project. 
type Person
    name :: String
    age  :: Int

convert (Person -> string)
    name <> "(" <> age <> ")"

|| picks the largest out of two integers
fn largest x y (int int -> int) 
    if x > y 
      then x
      else y

fn magic_number (int) 43251

fn is_twenty n (int -> bool)
    case n of
      20   -> true
      else -> false

fn main
    puts << into 
         << \person: { person | age = 21 } 
         << { Person | name = "some name", age = sum [10,10] }

Features

Syntax of Haskell

Even though the philosophy of Leaf and Haskell is drastically different, the syntax is relatively similar generally giving the same feel.
There's no variables, you heavily rely on lambda expressions, your program is built from small functions that grow outwards.

Simplicity

Removing features is often more desired than adding features. Because as the feature set increases, readability and simplicity decreases.

Efficiency

Maintaining simplicity is for the wrong cause if it hurts efficiency.
Leaf attempts to minimize boilerplate and generally reduce the parts of programming that is often found to be annoying by you describing behavior to add extra implicit functionality.

The << Pipe Operator

Data is most of the time separated by the << operator, and using the << operator should always be preferred over lisp-style parenthesis.\

If you're familiar with the <| operator from Elixir/Elm/F# or the $ operator from Haskell, then getting used to this new style of data flow will be no problem.

Concurrency

(not yet implemented) A great system for concurrency is the language's job to internally implement. Although a few draft designs exists it's to early to fully implement concurrency.

Error / Failure Handling

Safety and Purity are good things, but shouldn't be overdone to the point where it hurts usability.
Unlike Haskell, unsafe IO dependent functions are used the same way as any other function.

Any function who's unsafe code fails will return the Result<> enum to be handled by any of the unwrap functions. If a functions return argument is the built-in Result<> enum. The function body will implicitly return early if an Err variant is encountered anywhere in the body, to be handled at an upper level with unwrap methods such as:

fn unwrap_or r fallback (result<T> T -> T)
    case r of
      Ok  v: v
      Err _: fallback

As an example of the implicit result returns, take a look at the example below:

fn sum_as_num strings ([string] -> result<int>)
    sum << map strings << \s: toInt s

fn main
    puts << into
         << unwrap_or 0
         << sum_as_num ["5", "5", "5"]

toInt is unsafe here since a string cannot always be converted to int. So if toInt fails the function will fully implicitly return the Err(E) variant of Result. But if nothing fails the resulting int returned by sum is implicitly converted to the Ok(int) variant of Result

Currying is not being implemented

While it can shorten code greatly, the functional styling of leaf (and Haskel) already is short by nature, and the gains you do get from currying do not outweigh the impact it has on readability and simplicity. A simple lambda expression doesn't add much code and is a more easily understood and readable format of code.

Examples

Simply run them with ./leaf <leaf-file>.
If you don't want to install the leaf standard library to it's expected path you can use set the LEAF_PATH environment variable, making the fully portable command LEAF_PATH=leafstd/ cargo run --release examples/<leaf-file>
Remember to compile Leaf using the --release flag! cargo build --release

Status

The project is not yet in an usable state but we're getting there!

  • Design and implement the Tokenizer
  • Index declarations for ease of access
  • Design and implement the basic runner
  • Implement int/string
  • Create actually runnable Leaf programs
  • Add a TON of assertions to improve error messages
  • Implement all other primitive types (including lists)
  • Design and implement list mutation
  • Build a (Rust -> Leaf) bridge
  • Write a low-level standard library in Rust using the bridge
  • Implement conversions between the primitive types
  • Add logic operations (if, else)
  • Add logic operations (case-of, else-if)
  • Add lambda support
  • Implement custom types (structs/enums)
  • Implement Leaf conversions
  • Design and implement generics
  • Internally design and implement implicit result handling
  • Write Leaf's standard library abstracting over the Rust bridge
  • Add a proper CLI
  • Design / Decide whether to have traits
  • Design and Implement multithreadding (green-threadded concurrent mapping is an idea)
  • Implement stack-safe recursion

Known Bugs

  • Line numbers on errors are rarely correct, view them as rough estimates for now
  • Using operators right after a << pipe can cause index out of bounds crashes
  • Leaf reports internal errors when reaching unexpected tokens instead of pointing out the syntax errors

Philosophy

 Any philosophy, idea or gimmick should not be forcefully applied to a functionality if it hurts the end-user experience
 Implicity can be useful if resulting behavior is still user described
 A good language is a language that can expose advanced concepts in simple formats
 If back-end complexity reduces front-end complexity, it's probably worth it 
You can’t perform that action at this time.