asteroid-lang / asteroid Goto Github PK
View Code? Open in Web Editor NEWAsteroid is a modern, multi-paradigm programming language that supports first-class patterns.
Home Page: https://asteroid-lang.org
License: MIT License
Asteroid is a modern, multi-paradigm programming language that supports first-class patterns.
Home Page: https://asteroid-lang.org
License: MIT License
split the standard module into a standard and math module
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 don't correctly get passed as arguments to member functions.
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
load "io".
let array = [1,2,3].
array @extend((4,5,6)). -- This crashes
println(array).
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.
The following code throws an exception on the function call in the print argument:
load "io.ast".
constructor A with arity 2.
let a = A("Hello", (lambda with self do return self@[0].))
print ((a@[1]) a).
Work around:
let str = (a@[1]) a.
print str.
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).
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 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?
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.
Example:
load "io".
structure A with
data x.
end
println(A(0) is %A). -- This works fine
println(3 is %integer). -- This also works fine
println(3 is %A). -- This causes an error
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 ==.
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?
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.
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.
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.
all list/string member functions should return a value
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.
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()).
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]).
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?
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).
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. 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.
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
We need to extend Asteroid in order to support scientific notation for floating point values, e.g.
let v = 1.234E20.
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 without arguments can be constructed in two ways:
lambda with () do return 1.
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?
The unary minus pattern match fails under standard model,
load "standard".
let -1 = -1 .
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().
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)).
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:
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 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.
let dog = Dog(("name",'name),
("tricks",["roll over"|'_]),
'_).
The quote in the head-tail operator leads to an error.
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.
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).
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 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
Pattern match on whole classes of constructors such as the strings or integers.
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.
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 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
The following code should construct a list of a list generated by a list comprehension
let l = [[1 to 10]].
but it doesn't.
The current workaround is inserting a single comma after the list comprehension
let l = [[1 to 10],].
The following program fails.
assert(true == true)
Note: need to extend the typechecker to include booleans for relational operators.
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.
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.
let w = [-2, 1].
creates a syntax error.
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)
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.
The Asteroid print function ignores the \t and \n characters.
print("hello\nworld").
prints
hello\nworld
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.