Giter Club home page Giter Club logo

gluon's Introduction

gluon

Build Status crates.io Documentation Book std

Gluon is a small, statically-typed, functional programming language designed for application embedding. I

Features

  • Statically-typed - Static typing makes it easier to write safe and efficient interfaces between gluon and the host application.

  • Type inference - Type inference ensures that types rarely have to be written explicitly giving all the benefits of static types with none of the typing.

  • Simple embedding - Marshalling values to and from gluon requires next to no boilerplate, allowing functions defined in Rust to be directly passed to gluon.

  • UTF-8 by default - Gluon supports Unicode out of the box with utf-8 encoded strings and Unicode codepoints as characters.

  • Separate heaps - Gluon is a garbage-collected language but uses a separate heap for each executing gluon thread. This keeps each heap small, reducing the overhead of the garbage collector.

  • Thread-safe - Gluon is written in Rust, which guarantees thread safety. Gluon keeps the same guarantees, allowing multiple gluon programs to run in parallel (example)*

* Parallel execution of gluon programs is a recent addition and may still have issues such as deadlocks.

Examples

Hello world

let io = import! std.io
io.print "Hello world!"

Factorial

let factorial n : Int -> Int =
    if n < 2
    then 1
    else n * factorial (n - 1)

factorial 10

24

// # 24
//
// From http://rosettacode.org/wiki/24_game
//
// Write a program that randomly chooses and displays four digits, each from 1 ──► 9 (inclusive) with repetitions allowed.
//
// The program should prompt for the player to enter an arithmetic expression using just those, and all of those four digits, used exactly once each. The program should check then evaluate the expression.
//
// The goal is for the player to enter an expression that (numerically) evaluates to 24.
//
// * Only the following operators/functions are allowed: multiplication, division, addition, subtraction
// * Division should use floating point or rational arithmetic, etc, to preserve remainders.
// * Brackets are allowed, if using an infix expression evaluator.
// * Forming multiple digit numbers from the supplied digits is disallowed. (So an answer of 12+12 when given 1, 2, 2, and 1 is wrong).
// * The order of the digits when given does not have to be preserved.
//
//
// ## Notes
//
//     The type of expression evaluator used is not mandated. An RPN evaluator is equally acceptable for example.
//     The task is not for the program to generate the expression, or test whether an expression is even possible.


// The `import!` macro are used to load and refer to other modules.
// It gets replaced by the value returned by evaluating that module (cached of course, so that
// multiple `import!`s to the same module only evaluates the module once)
let io @ { ? } = import! std.io
let prelude = import! std.prelude
let { Result } = import! std.result
let array @ { ? } = import! std.array
let int = import! std.int
let string = import! std.string
let list @ { List, ? } = import! std.list
let random = import! std.random
let string = import! std.string

// Since imports in gluon returns regular values we can load specific parts of a module using pattern matches.
let char @ { ? } = import! std.char

let { (<>) } = import! std.semigroup
let { flat_map } = import! std.monad

let { (*>), (<*), wrap } = import! std.applicative

let { for } = import! std.traversable

type Op = | Add | Sub | Div | Mul
type Expr = | Int Int | Binop Expr Op Expr

let parse : String -> Result String Expr =
    // Gluon has a small parser combinator library which makes it easy to define an expression parser
    let parser @ {
        between,
        satisfy,
        satisfy_map,
        spaces,
        token,
        digit,
        skip_many1,
        recognize,
        lazy_parser,
        chainl1,
        (<?>),
        ? } = import! std.parser
    let { (<|>) } = import! std.alternative

    let lex x = x <* spaces

    let integer =
        // `do` expression provide a way to write monads in a way similiar to procedural code
        do i = lex (recognize (skip_many1 digit))
        match int.parse i with
        | Ok x -> wrap x
        | Err _ -> parser.fail "Unable to parse integer"

    let operator =
        satisfy_map (\c ->
            match c with
            | '*' -> Some Mul
            | '+' -> Some Add
            | '-' -> Some Sub
            | '/' -> Some Div
            | _ -> None)
            <?> "operator"

    rec
    let atom _ =
        parser.functor.map Int integer
            <|> between (lex (token '(')) (lex (token ')')) (lazy_parser expr)

    let binop _ =
        let op_parser =
            do op = lex operator
            wrap (\l r -> Binop l op r)
        chainl1 (atom ()) op_parser

    let expr _ = binop ()
    in

    // Gluon makes it possible to partially apply functions which we use here to scope all parser functions
    // inside the `let parse` binding above.
    let parse : String -> Result String Expr = parser.parse (expr () <* spaces)
    parse

/// Validates that `expr` contains exactly the same integers as `digits`
let validate digits expr : Array Int -> Expr -> Bool =
    let integers xs expr : List Int -> Expr -> List Int =
        match expr with
        | Int i -> Cons i xs
        | Binop l _ r -> integers (integers xs l) r
    let ints = integers Nil expr

    list.sort (list.of digits) == list.sort ints

let eval expr : Expr -> Int =
    match expr with
    | Int i -> i
    | Binop l op r ->
        let f =
            // Operators are just functions and can be referred to like any other identifier
            // by wrapping them in parentheses
            match op with
            | Add -> (+)
            | Sub -> (-)
            | Div -> (/)
            | Mul -> (*)
        f (eval l) (eval r)

do digits =
    let gen_digit = random.thread_rng.gen_int_range 1 10
    do a = gen_digit
    do b = gen_digit
    do c = gen_digit
    do d = gen_digit
    wrap [a, b, c, d]

let print_digits = for digits (\d ->
        seq io.print " "
        io.print (show d))
seq io.print "Four digits:" *> print_digits *> io.println ""

let guess_loop _ =
    do line = io.read_line
    // Exit the program if the line is just whitespace
    if string.is_empty (string.trim line) then
        wrap ()
    else
        match parse line with
        | Err err -> io.println err *> guess_loop ()
        | Ok expr ->
            if validate digits expr then
                let result = eval expr
                if result == 24
                then io.println "Correct!"
                else io.println ("Incorrect, " <> int.show.show result <> " != 24") *> guess_loop ()
            else
                io.println
                    "Expression is not valid, you must use each of the four numbers exactly once!"
                    *> guess_loop ()

guess_loop ()

Source

Getting started

Try online

You can try gluon in your browser at https://gluon-lang.org/try/. (Github)

Install

Gluon can be installed by using one of the prebuilt executables at Github or you can use Cargo in order to install the gluon_repl crate:

cargo install gluon_repl

REPL

Gluon has a small executable that can be used to run gluon programs directly or in a small REPL. The REPL can be started by passing the -i flag to the built repl executable which can be run with cargo run -p gluon_repl -- -i.

REPL features:

  • Evaluating expressions (expressions of type IO will be evaluated in the IO context).

  • Bind variables by writing let <pattern> <identifier>* = <expr> (omitting in <expr> from a normal let binding) Example:

       let f x = x + 1
       let { x, y = z } = { x = 1, y = 2 }
       f z
    
  • Printing help about available commands with :h

  • Loading files with :l path_to_file the result of evaluating the expression in the loaded file is stored in a variable named after the filename without an extension.

  • Checking the types of expressions with :t expression

  • Printing information about a name with :i name.
    Example:

    :i std.prelude.List
    type std.prelude.List a = | Nil | Cons a (std.prelude.List a)
    /// A linked list type
    
  • Tab-completion of identifiers and record fields repl completion

  • Exit the REPL by writing :q

Tools

Language server

Gluon has a language server which provides code completion and formatting support. Installation is done with cargo install gluon_language-server.

Visual Studio Code Extension

The gluon extension for Visual Studio Code provides syntax highlighting and completion. To install it, search for gluon among the extensions. (Github)

example

Vim plugin

vim-gluon provides syntax highlighting and indentation.

The gluon language server has been tested to work with https://github.com/autozimu/LanguageClient-neovim and https://github.com/prabirshrestha/vim-lsp.

Example configuration (autozimu/LanguageClient-neovim)

let g:LanguageClient_serverCommands = {
    \ 'gluon': ['gluon_language-server'],
    \ }

" Automatically start language servers.
let g:LanguageClient_autoStart = 1

nnoremap <silent> K :call LanguageClient_textDocument_hover()<CR>
nnoremap <silent> gd :call LanguageClient_textDocument_definition()<CR>

Documentation

The Gluon Book

Gluon Standard Library API Reference

Rust API Docs

Usage

Rust

Gluon requires a recent Rust compiler to build (1.9.0 or later) and is available at crates.io. It can easily be included in a Cargo project by adding the lines below.

[dependencies]
gluon = "0.18.2"

Other languages

Currently, the easiest way to interact with the gluon virtual machine is through Rust but a rudimentary C api exists which will be extended in the future to bring it closer to the Rust API.

Contributing

There are many ways to contribute to gluon. The two simplest ways are opening issues or working on issues marked as beginner. For more extensive information about contributing, you can look at CONTRIBUTING.md. Contributing also has details on running/getting-started-with tests for gluon.

Goals

These goals may change or be refined over time as I experiment with what is possible with the language.

  • Embeddable - Similiar to Lua - it is meant to be included in another program that may use the virtual machine to extend its own functionality.

  • Statically typed - The language uses a Hindley-Milner based type system with some extensions, allowing simple and general type inference.

  • Tiny - By being tiny, the language is easy to learn and has a small implementation footprint.

  • Strict - Strict languages are usually easier to reason about, especially considering that it is what most people are accustomed to. For cases where laziness is desired, an explicit type is provided.

  • Modular - The library is split into its parser, type checker, and virtual machine + compiler. Each of these components can be used independently of each other, allowing applications to pick and choose exactly what they need.

Inspiration

This language takes its primary inspiration from Lua, Haskell and OCaml.

gluon's People

Contributors

bors[bot] avatar bostelk avatar brendanzab avatar caulagi avatar dependabot[bot] avatar dwnusbaum avatar etherian avatar frehberg avatar ftilde avatar human154 avatar jason-cooke avatar jgreene avatar laegluin avatar leperdu avatar leshow avatar marwes avatar memoryruins avatar mikeyhew avatar mluszczyk avatar mxmurw avatar oooooba avatar ordovicia avatar rowanfr avatar salpalvv avatar shalokshalom avatar shuenhoy avatar traviscross avatar vbarrielle avatar zjhmale avatar zummenix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gluon's Issues

Add proper scoping for types

Currently worked on in the https://github.com/Marwes/embed_lang/tree/associated_types branch.

The idea is that record types can have both value and type fields so the prelude module might have the type defined as (making records similiar to first class modules in OCaml):

{ Int, Float, Option, Result, id, map, /* etc */ }

Types can then be brought into scope in the same way as variables

let { Int, num_Int = { (+), (-), (*) } } = prelude
and fac n : Int -> Int = ...

There is certainly going to be some issues with this approach which hasn't been considered yet but I think the basic idea is sound.

Add F#/OCaml/Elm-style piping operators

This seems to be what all the cool kids are doing! It is also much easier for people to understand vs Haskell's (.) and ($):

/// Backward function application, where `f <| x == f x`
let (<|) f x : a -> (a -> b) -> b = f x

/// Forward function application, where `x |> f == f x`
let (|>) x f : (a -> b) -> a -> b = f x

/// Function composition, where `f >> g == (\x -> f (g x))`
let (>>) f g : (a -> b) -> (b -> c) -> a -> c = \x -> f (g x)

/// Function composition, where `f << g == (\x -> g (f x))`
let (<<) f g : (b -> c) -> (a -> b) -> a -> c = \x -> g (f x)

Seeing as we are thinking in higher kinds, I wonder if we could define these in terms of Applicative and Category?

Wildcard pattern in records

As let bindings also serve as an import mechanic it would be nice if record patterns allowed for unpacking all fields avoiding the need to explicitly list every field.

Example

//Experimental syntax
let { * } = prelude.num_Int in 1 + 2 * 3  - 4

Formalize the type system

It is probably a far-future issue to actually formalize the type system, however, more pressing is that there is not a good way of determining whether a program should typecheck or not. This is mainly because the fact that type aliases can be used as type lambdas which makes the current typechecking undecidable.

I want to investigate whether it is possible to restrict type aliases in a way that avoids the need of higher order unification (which is not decidable) while not going as far as Haskell does when restricting aliases (as that would require the ability to define new distinct types, something I think would be interesting to avoid TODO explain reasoning).

--allowed to use partially
type Error = Either String
--Not allowed to use partially
type IntEither e = Either e Int

Allow nested block comments

Nested block comments (e.g. /* /* foo */ */) are my favorite feature from Rust. It would be nice if Gluon supports them too.

Add more precise locations to type errors

Currently the location of a type error is reported as being in the same place as the expression it occured in. This location can be rather misleading in many common cases.

Type errors in function calls should be reported at the argument rather than the call.

Current:
map 1 Nil
^~~~~~~~
Better version:
map 1 Nil
    ^

Type errors in patterns should be reported at the patterns (or expressions) location rather than the enclosing expression

Expression:
let { x, y } = 1 in ...
Current:
let { x, y } = 1 in ...
^~~~~~~~~~~~~~~~~
Better version:
let { x, y } = 1 in ...
    ^~~~~~~~

Compile the pattern matching of large structures more efficiently

Currently all patterns always compiled as Split which means that large objects such as the prelude push a lot of values to the stack when they are pattern matched. Often though only a few of them are actually used. For these cases the compiler should produce code which only takes the necessary arguments from the object.

Improve sharing of types and kinds

Anytime Type::int, Type::float, Kind::star, is called a new reference count type or kind is allocate which is rather wasteful. As these types are quite common they could be cached in the typechecker (and else where) so that the same pointer could be reused. The kindchecker already does some of this but in the typechecker this is not done.

https://github.com/Marwes/embed_lang/blob/master/check/src/kindcheck.rs

PrimitiveEnv might be possible to extend for this (though it was originally created to retrieve a canonical boolean).

Fix the Type::Function variant

Currently the Function variant has holds a Vec<Type> for function arguments but it is always assumed that the vector only has one element as functions are curried and are always treated as 1-argument functions.

The variant should either just hold one argument type, mirroring how the type checker sees it, or the code should take advantage of the Vec to store multiple arguments (possibly for efficency's sake).

Store arrays more efficiently

Currently arrays are just stored as Array<Value> which wastes a lot of space as the tag information in the enum is duplicated. A better way would be to have multiple array types (Array, Array, Array and maybe even Array if a byte type were added).

Though the idea is straightforward the implementation may have some subtleties. For instance consider the following code

let twice x = [x, x]
twice 1
twice { x = 1 }

When constructing an array in the twice function the tag of x needs to be inspected so the correct array can be constructed. A similar problem comes when indexing an array where the code has to check the type of the array to construct the correct Value variant. Theses issues aren't to complicated but there may be other problems hiding as well.

Make it easier to use (+), (-), (==), etc

As no form of overloading currently exist these basic functions require explicit importing like let { (+), (-), (*) } = prelude.num_Int in 2 + 2. This is obviously not so nice so some sort of resolution for this problem will be needed. Following is a list of possible ways to resolve this.

  • Implementing overloading with traits/typeclasses/"Ocaml implicit modules"

    Pros

    • Well explored territory

    Cons

    • Adds significant complexity to the compiler
  • Use differently named functions for different types (like OCaml)

    Pros

    • No additional complexity added to the compiler

    Cons

    • Inconvenient for users
    • Does not solve (==) well as it is often implemented for lots of types. Ocaml has a single (==) operator which takes any any two values as long as they have the same type which works most of the time but then throws errors when comparing functions which is not very friendly.

Only use the (->) operator for function type constructors

At the moment the Gluon's syntax overloads the (->) operator for three things:

  • as a function type constructor
  • to separate arguments from the body in lambdas
  • to separate the pattern from the body match arms

It would be nice to use (=>) for the latter two cases to disambiguate this, and to leave us open to unifying the type and value syntaxes in the future.

For example:

 type Functor f = {
     map : (a -> b) -> f a -> f b
 }

 let functor_Option : Functor Option = {
-    map = \f x ->
-        match x with
-            | Some y -> Some (f y)
-            | None -> None
+    map = \f x =>
+        match x with
+            | Some y => Some (f y)
+            | None => None
 }

Make the library compile under stable rust

Currently the following features are needed to build the library.

  • core_intrinsics
  • heap_api - Used to allocate memory for the gc. Could likely be removed using Vec for memory allocation
  • raw - Used for transmutes into &[T]
  • slice_bytes
  • test - Only for needed for testing (benchmarks)

Rename `*` to `Type`

This seems to be the general movement of programming languages of late. It's also probably clearer, and easier for newcomers to understand.

Favor giving names to functions over operators

It might be nice to provide named functions for interfaces first, then have the operators as shorcuts. So for example, (>>=) would become flat_map (I personally prefer this over bind, heh), and make_Monad would define the operator based on that.

Consider having a special syntax for macros

Macros currently just look like functions import "test.glu"which can easily lead to confusing behaviour as Macros override any other bindings (essentially making macros reserved key words). Rust ends all macros with a ! which makes macros distinct and does essentially namespaces macros. Copying this behaviour may be a good idea and shouldn't require to much effort to implement

Surround operators with parens when pretty-printing

For example:

> { (+) = \x y -> "squiggly squiggle" }
<top>:Line: 1, Column: 1: Expected the following types to be equal
Expected: { +: 1476 -> 1477 -> String }
Found: { ord: std.prelude.Ord 1479, +: 1479 -> 1479 -> 1479, -: 1479 -> 1479 -> 1479, *: 1479 -> 1479 -> 1479, /: 1479 -> 1479 -> 1479, negate: 1479 -> 1479 }
1 errors were found during unification:
Types do not match:
    Expected: { +: 1476 -> 1477 -> String }
    Found: { ord: std.prelude.Ord 1479, +: 1479 -> 1479 -> 1479, -: 1479 -> 1479 -> 1479, *: 1479 -> 1479 -> 1479, /: 1479 -> 1479 -> 1479, negate: 1479 -> 1479 }
{ (+) = \x y -> "squiggly squiggle" }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Nested patterns

Nested patterns are not implemented but due to how verbose code can be otherwise this would probably be necessary.

Consider removing the IO monad

From writing the REPL I get the feeling that a strict language does not work that well with monadic action once a few actions are chained. Adding better syntax for monadic actions could maybe make it convenient enough but as the language isn't pure anyway it is likely better to remove the IO monad for now.

No GC

A new functional language with a GC is not a earth shattering. If you can do static analysis to figure out the lifetimes without having to specify them at all, this would be a breakthrough. Also see: https://github.com/carp-lang/Carp

Consider renaming case ... of to match ... with

case ... of expressions are a hold over from the language's roots in Haskell but currently the syntax has more in common with F# and Ocaml which uses match ... with. Rust also uses match but that could be both an argument for (to make it more similiar) or against (the syntax still differs so having case instead could be a good distinction.

Add functional record update

Currently there is no good way of creating a new record which has most of the fields of an existing record with a few of them modified. Adding functional record update ala Haskell or Rust would make this easier in some cases.

Functiona record update may be a bit limiting in some/many cases so it may be worth investigating a more general way of updating data structures (something like lenses).

Extend the AST with spans instead of just locations

Currently only a location is stored for each AST node indicating the first character of the expression. If instead a span where stored for each expression the error messages could be significantly be improved.

The current way of storing a location for each node is probably not needed as well as for many expressions the location/span could be calculated from the data it contains.

Allow use of non-Sync data in the interpreter

To allow the interpreter to be used safely in multiple threads the interpreter needs to implement Sync and in turn this means that all values it owns also needs to be Sync. This can be problematic though as this requires mutable data to either just use atomic operations or using Mutex/RwLock which has more overhead than Cell and RefCell.

A possible way of allowing this is to create two ways of spawning gluon threads. A sync thread would work exactly as it does now. A non-sync thread on the other hand is allowed to contain non-sync data but it has some additional restrictions to make it safe.

// Thread does not implement `Sync` or `Send by default`
impl Thread {
    fn new_sync_thread() -> RootedSyncThread;
    fn new_thread() -> RootedThread;

    fn new_child_thread(&self) -> RootedThread;
    // A `&Thread` can be converted into a thread implementing `Sync` if it was spawned as a sync thread
    fn to_sync_thread(&self) -> Result<RootedSyncThread>;
    // It is always possible to convert a `Thread` into a `RootedThread` same as now
    fn to_rooted_thread(&self) -> RootedThread;
}

impl RootedSyncThread {
    fn new_child_sync_thread(&self) -> RootedSyncThread;
}

Restrictions:

  • Non sync threads can only spawn other non sync threads - Necessary as any child threads can access data from a parent
  • Non sync threads can only send sync data upwards to a parent thread - The data would of course still need to be copied but care also needs to be taken that non non-sync data is created.

I don't feel confident about this being all restrictions necessary so I will leave this as a tracking issue in which I can consolidate more data before actually starting on an implementation.

Suggest improvements to the documentation

There is still a lot of parts of gluon which lack documentation and while I will do my best to recognize and improve it where I can, it would be very helpful to get suggestions for which part I should prioritize. Opening a new issue for any needed documentation improvements is often still a good idea but consider this issue a place to add suggestions which do not seem to warrant a separate issue.

Add a bytecode loader

This would allow the vm crate to be included and used separately though limited to only loading pre-compiled bytecode. For starters it doesn't need any checking for bytecode validity but eventually it would be useful to have safe loading as well.

Type/identifier search in the repl

Would be super groovy if you could search for functions in-scope with :s. Here is what you can do in the Idris repl:

Idris> :search Monad f => f a -> f b -> f a
< Prelude.Applicative.(*>) : Applicative f => f a -> f b -> f b


< Prelude.Applicative.(<*) : Applicative f => f a -> f b -> f a


> Prelude.List.intercalate : List a -> List (List a) -> List a
Given a separator list and some more lists, produce a new list
by placing the separator between each of the lists.

> Prelude.List.(++) : List a -> List a -> List a
Append two lists

> Language.Reflection.Elab.Prim__Try : Elab a ->
                                       Elab a -> Elab a


> Prelude.List.(\\) : Eq a => List a -> List a -> List a
The \\ function is list difference (non-associative). In the
result of xs \\ ys, the first occurrence of each element of ys
in turn (if any) has been removed from xs, e.g.,

> Prelude.List.merge : Ord a => List a -> List a -> List a
Merge two sorted lists using the default ordering for the type
of their elements.

> Prelude.List.union : Eq a => List a -> List a -> List a
Compute the union of two lists according to their Eq
implementation.

> Prelude.while : IO' l Bool -> IO' l () -> IO' l ()
Loop while some test is true

Make it possible to test the REPL

As more features gets added to the REPL it would be nice to improve the testing for it. The REPL can however be tricky to test as one needs to be able to send commands to the terminal programmatically. In python I found the pexpect library which seems to be able to do this. It is a bit of a heavy weight solution to take on a python dependency just for some tests though and it would really nice if a library like pexpect could be ported to RUST. I don't have a good estimate to how much work such a port would be but it might be an interesting project to do (which would likely be useful outside of gluon as well)!

Precedence of operators

Currently the precedence for common operators such as + are predefined in the parser which makes it possible to at least write arithmetic without any surprises. This won't scale for user defined operators however so language construct needs to be added which binds precedence to an operator.

The current line of thinking is to attach fixity to bindings and let them propagate where they are used as long as it is not ambigious.

let { (+) } = 
    let lassoc 3 (+) = ....
    in { (+) }
in /* assoc is still 3 for + here */ 

Debugging

List of some things that are needed to make it easier to debug programs.

  • Stacktraces
  • Source maps
  • Breakpoints

Simplify the Type representation and reduce its heap usage

Currently there are quite a lot of different representation in the Type enum this leads to complications in unification (and elsewhere) as types that should be equivalent can have multiple representations. To some extent these different representations are necessary either for convenience or for efficiency (Function(Int, Float) is a lot nicer to work with than App(App(->, Int), Float)).

Currently the App variant only exists to avoid allocation a one element vector for single argument constructors which are very common but with smallvec (or equivalent) the App variant could be completely superseded by Data.

Other simplifications may be to replaceFunction(a, b) with Data(-> [a, b]) and Array(a) with Data([], [a].

Marking this as beginner as this change (though maybe a bit tedious) should just involve replacing all uses of Type::App(a, b) with Type::Data(a, [b]).

Checklist

  • Remove Type::App (#52)
  • Remove Type::Function (#55)
  • Remove Type::Array (#62)
  • Switch to using smallvec for Type::App
  • Remove Type::Builtin

Merge duplicate calls of make_Monad, make_Applicative etc

As mentioned in #56 each call to make_Monad or similiar functions make a completely new set of functions for each invocation. These could be removed with optimizations but that is a long way away of being a solution. An alternative solution may be to add a bake macro which functions as follows.

This:

let prelude = import "std/prelude.glu"
...
let monad = bake (prelude.make_Monad prelude.monad_IO)

Becomes this:

// This expression is evaluated first and stored in a global location
let prelude = import "std/prelude.glu"
prelude.make_Monad prelude.monad_IO

Then this expression can access the cached value.

let prelude = import "std/prelude.glu"
...
let monad = make_Monad_IO_global)

The bake macro would function a bit like import in that it evaluates its argument once and caches the result in some globally accessible place. It becomes far trickier though as it would need to track from where all identifiers come from to be able to reconstruct a valid expression.

I may have been a bit to optimistic about this solution as taking it to the limit is more or less as much work as implementing a proper optimization to do this. It might be that this handles 90% of the cases though

Make the garbage collector safer

Running a garbage collect requires that all roots are traversed to make it safe to use. This is not enforced currently and I only rely on being very careful when working directly with GcPtr<T>.

Servo has some ways of combating this so it may be worth copying some of that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.