Coder Social home page Coder Social logo

asteroid-lang / asteroid Goto Github PK

View Code? Open in Web Editor NEW
40.0 4.0 9.0 4.65 MB

Asteroid is a modern, multi-paradigm programming language that supports first-class patterns.

Home Page: https://asteroid-lang.org

License: MIT License

Python 100.00%
programming-language pattern-matching scripting-language higher-order-functions object-oriented language

asteroid's People

Contributors

arielfff avatar carlstoker avatar christian7974 avatar davidhbrown avatar github-actions[bot] avatar ijchen avatar lutzhamel avatar olwmc avatar supurcalvinhiggins avatar timcolaneri avatar tteeoo 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

Watchers

 avatar  avatar  avatar  avatar

asteroid's Issues

precedence of 'not' is not correct

the following does not parse correctly:

while not input_stream @eof() do
....
end

The work around is:

while not (input_stream @eof()) do
...
end

Tuples are not correctly passed to member functions

Tuples don't correctly get passed as arguments to member functions.

Example 1:

load "io".

let entries = [(1,2), (3,4),(5,6)].

function foo
	with (x,y) do
		return (y,x).
		end

println(foo(entries @0)).   -- This works fine.
println(entries @map(foo)). -- This crashes

Example 2:

load "io".

let array = [1,2,3].

array @extend((4,5,6)). -- This crashes
println(array).

Discussion

This is seemingly because on line 1066 in asteroid_walk.py, when a member function has a tuple as an argument, it inserts the object reference at the beginning of the tuple like:

arg_val[1].insert(0, obj_ref)

That being said, though, changing this would clash with the way tuples are passed into hash.ast's insert function.

function insert
with (self,name,value) do escape

As we can see, insert expects a tuple with the self reference at the beginning, something in the form (self, "today", "tuesday") and not (self, ("today", "tuesday")).

The question now becomes do we want tuples to be passed like (self, (x,y,z)) OR (self, x,y,z). To me, the first option makes more intuitive sense. When you pass a tuple, you will be looking to pattern match a tuple, not individual arguments.

I have a fix lined up which not only resolves the issue but also converts the old libraries which used the old syntax to the new one.

What do you folks think?

precedence problem with the 'eval' function

The following program does not work,

load "io".
let (x,y) = ('1+1,'2+2).

let 6 = eval x + eval y.

The workaround is to put parens around the eval functions,

load "io".
let (x,y) = ('1+1,'2+2).

let 6 = (eval x)+(eval y).

nested inclusions of modules that attach behavior create multiple behaviors

Assume that the first module looks like this:

-- module1
load "standard.ast".
load "module2.ast".
-- computation for module1 starts here
...

and the second module looks like this:

--module2
load "standard.ast".
...

The problem is that by the time we reach "computation for module1 starts here" we have attached two versions of the "standard" behavior to the respective constructors -- this can lead to bugs.

Visibility of variables declared during a first-class pattern match

Visibility of variables declared during a pattern match when using non-first-class patterns is usually not a problem, consider,

load system "io".

let (x,y) = (1,2). -- we can immediately see that the pattern match introduces x and y
println x.
println y.

Now consider using first-class patterns,

load system "io".

let P = pattern with (x,y).

-- lots of intervening code here...

let *P = (1,2). -- no longer obvious that the pattern match introduces x and y
println x.
println y.

Should there be explicit syntax such as,

let  x,y in *P  = (1,2).

indicating that the pattern match introduces variables x and y?

REPL feature

We need to implement a read-evaluate-print-loop feature similar to what Python does.

The tricky part here is how to integrate that with the frontend. For example, in the Python REPL it knows exactly where you are in terms of syntax and will prompt you to complete the syntax for the current phrase.

is pattern matching does not work in conditional matching

This code should return false,

load "io".

function filter 
    with ([x:%string, y:%string, z:%string]) %if x is z do
        return true.
    orwith _ do 
        return false.
    end
    
print (filter ["a","b","c"]).

but it returns true so there is a problem with the conditional pattern match on strings. The workaround is to replace the is operator with ==.

keys for dictionaries

Currently Asteroid only allows string and integer typed keys for dictionaries. Should we allow for other type of keys? Perhaps anything that supports a comparison operator?

Test paths are absolute

Right now, the test files for redundant patterns, regression tests, and using asteroid all have hardcoded paths that need to be changed if someone else wants to run them. Instead, they should just be able to be run, regardless of the machine.

A proper fix for this would probably use pythons pathlib to get relative paths for the test file.

line reporting on errors needs adjustment

The error in this program

load "io".
load "util".

structure Person with
  data name.
  data age.
  data gender.
end

-- define a list of persons
let people = [
  Person("George", 32, "M"),
  Person("Steve", 49, "M"),
  Person("Oliver", 21, "M")
].

function f
  with Person(name,age,gender) %if name > 10 do -- error should be reported here
    return 1.
  orwith Person(name,age,gender) %if name == "Steve" do
   return 2.
  end.

let x = f( people @1 ).  -- but error is reported here

println( "The output of function f() is: " + x ).

is reported at the call site rather than where the error occurs in the source file.

precedence clash between index (@) and function application (juxtaposition) operators

There is a priority clash between the index (@) and function application (juxtaposition) operators. The @ operator has the higher priority in order to make member function calls work. But this leads to a problem like so,

load "io".
load "util".
let number = 139487854.
let s = tostring number @explode().
println s.

The intention of the code is to convert a number into a list of character digits by first converting the number into a string assuming that function application has the higher precedence. But in Asteroid that assumption is not true and therefore the explode function is called in the number which of course fails. To make this work one has to insert parens as follows,

load "io".
load "util".
let number = 139487854.
let s = (tostring number) @explode().
println s.

How can this be resolved without breaking member function calls.

detaching from declared function names

Question: should the following code work?

load "standard".
load "util".
load "io".

function S 
    with n do
        return n + 1.
    end function

print (S(S(S(0))) + S(S(0))). 
detach from S.
print (S(S(S(0))) + S(S(0))). 

That means that function names act like constructors once the function behavior is detached from them.

associativity of @ is not correct with nested calls

The following code crashes,

load "io".
    
structure B with
    data b.
    function get with self do return self @b end
    end
    
let q = B([1,2,3]).

println (q @get() @length()).

the workaround is to put the first call in parens,

load "io".
    
structure B with
    data b.
    function get with self do return self @b end
    end
    
let q = B([1,2,3]).

println ((q @get()) @length()). 

slicing in patterns

Asteroid should support slicing in patterns like so,

load "io".

let a = [3,5,7,4].
let a@[2,3] = a@[3,2].
println a. 

assert (a is [3,5,4,7]).

confusing semantics with regards to dictionary id keys

There is an issue with regarding to id keys:

let a = "abc".
let d = [("a", 0), ("abc", 1), ("def", 2)].
print d@{a} --> prints out 1

but

let d = [("a", 0), ("abc", 1), ("def", 2)].
print d@{a} --> prints out 0

This is very confusing! Is there a nice way to fix this?

Bitwise Operations

Currently, Asteroid has no means of evaluating bitwise operations.

Should these operations be implemented as built-in expressions?
let x = 1 << 2.
assert(x == 4).
Or as a module?
load "bitwise".
let x = left_shift(1,2).
assert(x == 4).

Debugger

We need an interactive debugger for Asteroid. This should probably be modeled after ML or Haskell debuggers. How much of pattern matching should we expose? Should one be able to set a break point within a pattern match? How do first-class patterns affect debugging?

Pass an implicit object reference to object member functions.

Pass an implicit object reference to object member functions. That is instead of this:

   constructor A with arity 2.
   let a = A("Hello", (lambda with self do return self@[0].))

   print (a@[1]) a.

we do this:

 constructor A with arity 2.
 let a = A("Hello", (lambda with self do return self@[0].))

 print (a@[1]) none.

unify: variable unifies with a variable

The following code should not work:

load "io".

let (1,y) = '(1,x).
println y.

but it does and prints out

x

It should fail with something like 'cannot pattern match' on variables

Switching from a prototype object system to a trait based object system

I am envisioning a trait-based object system a la Rust,

load "io".

trait T with
    data t.
    function ft with self do return "got " + self @t end.
    end

structure A with
    trait T.
    data a.
    function fa with self do return "got " + self @a end
    function __init__ with self do let (self @t, self @a) = (1,2) end
    end
   
-- Note: default constructors with args do not work with object that have traits
let a = A().
println (a @ft()).
println (a @fa()).

Question, how do we deal with function prototypes? Currently the syntax is not conducive for function prototypes because of the dynamic dispatch functionality.

functions with no arguments

functions without arguments can be constructed in two ways:

  1. lambda with () do return 1.
  2. lambda with none do return 1.

However there is a semantic difference. In case 1 we are still requiring that the function is being called with an empty list as an argument. In case 2 we require that the function is truly called with 'none' as the argument. You cannot mix these two modes!

Is there a nice way to allow for mixing these two modes?

structure member function call does not dispatch correctly

The first print statement in the following program does not work, but the second one does:

load "io".

structure A with
    data a.
    function hello with self do return "hello" end.
    end
    
structure B with
    data b.
    end
    
let q = B([1,2,3]).
let p = B(A(1)).

println q @b @length().
println p @b @hello().

The big difference is that the first print statement calls a member function of builtin data structure where as the second one calls a true structure member function.

Work around is placing the first object deference into parens: println (q @b) @length().

fix precedence of 'is' operator

The following code should work without the parens around the 'is' operator in the assert function,

load "io".
load "util".

class A with

    data x.
    data y.

    function __init__
      with self, a, b do
        let self@x = a.
        let self@y = b.
      orwith self do
        let self@x = 1.
        let self@y = 2.
      end function
    end class

let obj1 = A("hello","world").
print obj1.
let obj2 = A(none).
print obj2.

assert((obj1@x is "hello") and (obj1@y is "world")).
assert((obj2@x is 1) and (obj2@y is 2)).

overlapping patterns in 'with' and 'orwith'

If you have overlapping patterns in the 'with' or 'orwith' clauses of a function definition and the patterns are not order from the most specific to the most general pattern matching will lead to unexpected results. Consider,

load "io".

class A with

    data x.
    data y.

    function __init__
      with self do
        let self@x = 1.
        let self@y = 2.
      orwith self, a, b do
        let self@x = a.
        let self@y = b.
      end function
    end class

let obj1 = A("hello","world").
print obj1.
let obj2 = A(none).
print obj2.

In the function init the first pattern is more general than the second and will match anything that the constructor is called with. This leads to unexpected results.

The current work around is to make sure that your with clauses are order from the most specific to the most general.

Disambiguate unary constructors that take a list as their only argument

Disambiguate unary constructors that take a list as their only argument:

   constructor A with arity 1.
   let a = A([1,2,3]).  --> ERROR: viewed as constructor with arity 3

this is due to the fact that A([1,2,3]) == A(1,2,3). The following code is correct with respect to the semantics of parenthesized expressions - (a): parenthesized expression (a,): list expression

   constructor A with arity 1.
   let a = A([1,2,3],)

Notice the comma after the list!

implement dot and basic vector/tuple arithmetic

implement dot and basic vector/tuple arithmetic so that the following program works

load "io".
load "math".

let x = (10, 3).
let y = (9, 1).
let delta = x-y.
let dist = sqrt(dot(delta,delta)).
println dist.

conditional pattern matching in functions

Conditional pattern matching in a function's with/orwith blocks exposes variables used in the pattern match in the surrounding scope. Consider,

load "io".

function foo with n %if n > 1 do
    return 1.
end

println (foo 2).
println n.

Here the variable n is exposed in the surrounding scope.

redundancy check fails with first-class patterns

The following program fails in the redundancy check due to the presence of first-class patterns,

load "io".
load "util".

let POS_INT = pattern with (x:%integer) %if x > 0.
let NEG_INT = pattern with (x:%integer) %if x < 0.

function fact
    with 0 do 
        return 1
    orwith n:*POS_INT do
        return n * fact (n-1).
    orwith n:*NEG_INT do
        throw Error("factorial is not defined for "+n).
    end

println ("The factorial of 3 is: " + fact (3)).
assert (fact(3) == 6).

patterns returned by functions used in pattern-matching contexts

Asteroid should support something like the following,

function foo
   with *g() do -- g returns a pattern that we use in the with clause.
      <something>
  end

where patterns returned from functions should be able to be used in pattern matching contexts.
Another example,

let v = x is *goo().

The variable v is either true or false depending if the term stored in x matches the pattern returned
by goo or not.

The __str__ function for structures

The idea is to give users the ability to represent objects as strings for printing purposes for example. Given the following code,

load system io.

structure Foo with
     data a.
     data b.
     function __str__ with none do return "Foo with " + this @a + " and " + this @b end.
     end

let o = Foo(1,2).
io @println o.

Here println would automatically invoke the __str__ function (if it exists) in order to print out the object o. The output would be,

Foo with 1 and 2

negating a return value from a function does not work

bug the following does not work

load "io".
function ident with i do return i end
let p = - ident(1).
println p.

The workaround is to multiply the return value by -1,

load "io".
function ident with i do return i end
let p = -1 * ident(1).
println p.

First-class conditional patterns do not evaluate the conditional expression

First-class conditional patterns do not evaluate the conditional expression.

Consider the following code:

load "io".
load "util".

let POS_INT = pattern with (x:%integer) %if x > 0.
let NEG_INT = pattern with (x:%integer) %if x < 0.

function fact
    with 0 do 
        return 1
    orwith n:*NEG_INT do
        throw Error("factorial is not defined for "+n).
    orwith n:*POS_INT do
        return n * fact (n-1).
    end

println ("The factorial of 3 is: " + fact (3)).
assert (fact(3) == 6).

This code will always throw an error for any integer value, besides 0. This is because the first-class patterns which have been declared, NEG_INT and POS_INT, will only have their patterns evaluated, and not their accompanying conditional expressions, when attempting to unify the functions clauses with the actual arguments.

Additionally, after examining the symbol table, it appears that these first-class patterns were stored into the symbol table without any conditional expressions associated with them.

NEG_INT : ('quote', ('named-pattern', ('id', 'x'), ('typematch', 'integer')))
POS_INT : ('quote', ('named-pattern', ('id', 'x'), ('typematch', 'integer')))

repeated variable names in a pattern

Repeated variable names in a pattern should express a constraint in that the pattern should only match if both variables instances match the same term. Consider:

load "io".

let (x,x) = (1,2).
println x.

semantically this equivalent to the code

load "io".

let (x,y) %if x==y = (1,2).
println x.

and should fail. However, currently the pattern with the two variables instances of the same name succeeds where the second instance match silently overwrites the first and prints out

2

outermost variable for pattern

Allow an outermost variable to match a whole structure if the pattern matches.

Example,

constructor Person with arity 3.

let people = [
    Person("George", 32, "M"),
    Person("Sophie", 46, "F"),
    Person("Oliver", 21, "M")
    ].

for p:Person(_,_,"M") in people do
    print p.
end for

Here the variable 'p' in the 'for' loop holds the whole matched structure.

precedence of the "if then else" EXPRESSION

In order for the following program to work correctly it seems that the precedence of the "if then else" expression need to be higher than the precedence of arithmetic operators.

program = \
'''
load "standard".
load "io".

constructor Person with arity 3.

let people = [
    Person("George", 32, "M"),
    Person("Sophie", 46, "F"),
    Person("Oliver", 21, "M")
    ].
    
let Person(name,age,sex) = people@1.
print (name + " is " + age + " years old and is " + "male" if sex is "M" else "female" + ".").
'''
interp(program, tree_dump=False, symtab_dump=False, exceptions=False, do_walk=True)

If you put parens around the "if then else" expression this program works.

constructor call on patterns containing _ not correct

The last line of the program,

load system io.

structure op with
    data lc.
    data rc.
    end

let cl = pattern op(_,_).
let cr = pattern 3.
let p = pattern op(*cl,*cr).

io @println ((op(op(1,2),3)) is *p).
io @println (eval p).

should fail because you cannot call a constructor on an anonymous variable. However, the call succeeds and prints out,

op(op(_,_),3)

Problematic dynamic scoping with patterns as constructors

When using patterns as constructors it turns out that they behave like dynamically scoped functions. This is not very desirable due to the difficulty of predicting runtime behavior and not very comprehensible program text.

Consider,

load system io.

let P = pattern (x, y).

let x = 1.
let y = 2.
let z = eval P.

io @println z.

The evaluation of pattern P simply captures the variables x and y from the current environment. This has all the well documented pitfalls of dynamically scoped functions.

One solution might be to provide an explicit argument list to the eval operator using the concept of our binding lists from pattern matching,

let z = eval P with [x = 1, y = 2].

which would be semantically equivalent to something like this,

let z = (lambda with (Ptn,x,y) do eval Ptn)(P,1,2).

This means that instead of having eval draw from the environment in an uncontrolled fashion we are now parameterizing the operation.

fix precedence of member function calls

fix precedence of member function calls, let

    constructor A with arity 2.
    let a = A("Hello", (lambda with self do return self@[0].))

then we have

   print (a@[1]) a.

and it should be:

   print a@[1] a.

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.