Parenthesis and types are optional. The function is the main abstraction in Silicon. Due to this, function have a few superpowers.
Silicon grammar doesn't use parethesis for anything but grouping expressions, and S-expressions.
ALL types in Silicon are inferred. They can be optionally included after the identifier with :type
syntax.
Silicon has implicit returns. Rust
is a modern example of this. You can still use the @return
keyword for explicit returns. Silicon does NOT have implicit values. This means that a branch of code cannot return nothing to mean undefined
or null
like in JavaScript. We must explicitly return a value.
@fn foo = {
&if bar
$then 1
$else {
# error. We must explicitly return a value here
};
};
### Ambiguous function calls
We might see something like this in a C-like language
```c
fib(n - 1) + fib(n - 2)
```
Silicon uses parenthesis like LISP because the above could mean
`fib(n-1 + fib(n-2))` or `fib(n) - 1 + fib(n) - 2` etc...
Silicon can disambiguate with `()`
```lisp
(fib n - 1) + (fib n - 2)
Generally, this doesn't happen and removing parens in much easier to read. Sigil will warn about ambigous expressions like this.
Function Definition Pattern Matching means function parameters can be defined with values. Basically mapping inputs to pre-defined outputs. We can implement an if expression to work with booleans this way.
# ? means else is optional
@fn if true, then, else? = { #then; };
@fn if false, then, else? = { #else; };
# default parameters? Can't use '='. Use ';'?
# @fn if bool, then, else=null; = { }
#if true, { #print "I'm working"; }, {#print "Not functioning";};
NEW ADDITIONS
Instead of this
@fn fizzbuzz 0,0,_ = 'fizzbuzz';
@fn fizzbuzz 0,_,_ = 'fizz';
@fn fizzbuzz _,0,_ = 'buzz'
@fn fizzbuzz _,_,index:string = index;
We can do this.
@fn fizzbuzz three, five, index:string = [
0,0,_ = 'fizzbuzz';
0,_,_ = 'fizz';
_,0,_ = 'buzz';
_,_,m = m;
];
OR maybe
@fn fizzbuzz |= 0,0,_ = 'fizzbuzz'
|= 0,_,_ = 'fizz'
|= _,0,_ = 'buzz'
|= _,_,m = m;
The |=
operator would signify we are assigning multiple params-body pairs, sorta like switch case.
a switch case / match equivalent can be created by piping values into an anonymous function that pattern matches.
a,b,c | @fn _ |= 0,0,_ = 'fizzbuzz'
|= 0,_,_ = 'fizz'
|= _,0,_ = 'buzz'
|= _,_,m = m;
In the last example we defined a simple 'if' function. We can make 'if' work with more of a DSL by using
Silicon's named parameter syntax. $
Sigil defines atoms which are named parameters or types that have only one value, themselves i.e. $true
.
We could call if
with named parameters instead. They then act like keywords. I may default these parameters to be inside {}
implicitly since normally
just passing in code mean it runs immediately and it isn't a block.
Instead of
&@if $true,
then = #print "I'm working",
else = #print "Not functioning";
We can remove the =
and the ,
and just use $
for the parameter names. Notice that the first parameter condition
isn't named. Silicon will use position to map arguments to parameters if no explicit name is given.
&@if $true
$then #print "I'm working"
$else #print "Not functioning"
TODO
This is explained elsewhere but it is related to functions / methods. Silicon doesn't allow function/method overloading (Silicon dynamic aka Sulfur
does).
This may feel limiting, it is. So Silicon tries to make it a little better by fixing some cases.
Before defined our boolToString
function/method before but it has to have its own unique name. We couldn't make it just ToString
like we could if we had function/method overloading. We can simulate this with TDNR by allowing the type of the method reciever to dictate which module we use.
For example
@module bool = {
@fn ToString bool = {
&if bool
$then "true"
$else "false"
};
};
We can then use this function with its full name
&bool::ToString true # "true"
We could possibly run into an ambigous function call error if we used UFCS though.
@let foo = true;
&foo.ToString # Error: 'ToString' function is ambigous between 'bool::ToString' and 'number::ToString'...
NOTE The method syntax changed from '.' to |
(pipe). Nothing else changes in this context.
This can be fixed if Silicon looks at the type of the method receiver, foo
in this case, and uses that information
to look inside the correct module (namespace).
@name foo = true;
@name bar = 10;
# calls Boolean::ToString
&foo.ToString # "true"
# calls Number::ToString
&bar.ToString # "10"
So this makes the lack of proper function overloading a bit less painful. Again, if we really need true function overloading we could
use Sulfur
, Silicon's dynamic dialect.
@@dynamic {
@fn ToString bool:boolean = {};
@fn ToString Number:number = {};
@let x = true;
&x.ToString; # "true"
}
Basically, it dynamically dispatches all the proper calls depending on the type.
The current grammar supports this. Which also would support all inline definitions as parameters.
@fn foo x,@let y = 3 = {};
Without default parameter is would just be
@fn foo x,y = {};
Recent grammar changes now allow ()
around args/params
@fn foo (x,y = 3) = {};
@fn foo x,y = 3 {};
@fn foo x,y = 3; = {};
I prefer doing the `@@pre`` macro which runs just before a function recieves its arguments.
@@pre y = 3;
@fn foo x,y = {};