macro operator binary ^$
ASTE $left bitxor $right
There are also operators for `and`, `or`, `xor` which act logically. `not` which does a boolean invert. So there are `bitand`, `bitor`, `bitxor`, `bitnot` which fit well as working bitwise instead of logically. let func-with-named-params({
id as String // TypeError if not a String
metadata // don't care about type, defaults to void
seed as Number = get-random-seed() // requires a Number, only calls function if not provided.
}) ->
You can do all sorts of things with object destructuring. refinement String
def repeat(count as Number)
if count < 1
""
else if count < 2
this
else if count bitand 0b1 // if it's odd
this->repeat(count - 1) & this
else
(this & this)->repeat(count bitrshift 1)
// and used as such:
"Hello "->repeat(5) == "Hello Hello Hello Hello Hello "
// would turn into this code:
String_$_repeat("Hello ", 5)
let unknown(x)
// This would throw a compile-time error, as x's type
// would be unknown. It would have to be a single
// named type that has a refinement defined, either at
// the top of the file or with an import.
x->repeat(5)
Also, refinements could have macros on them, so one could do "Hello"->for-code-point point, index
// This is a body in a macro defined as "for-code-point" on a String refinement.
do-something(point)
So, this would be a nice way to have refinements, but have the terrible downside of requiring the type to be statically known. refinement Array|{ length: Number }
// define a getter, why not?
def get median()
this[this.length \ 0]
macro loop
syntax item as Identifier, index as (",", index as Identifier)?, body as Body
index ?= @tmp \i
AST
let mutable $index = 0
while $index < @length, $index += 1 // it knows that `@length` is a Number, no need to type checking
let $item = this[$index]
$body
def each(callback)
// look, we're using the macro we just defined!
this->loop value, index
callback value, index
The refinement namespace would not conflict with the normal invocation namespace, so you could have `"Hello".same()` and have it be exactly as expected, but `"Hello"->same()` not, because it would turn into `String_$_same("Hello")` (or something like it). refinement Number
// automatically knows it returns a Number
def log(base as Number = Math.E)
Math.log(this) / Math.log(base)
let alpha = 1e100->log(10) // knows that 1e100 is a Number, obviously, and since ->log returns a Number, alpha is automatically known to be a Number.
let bravo = alpha->log(10) // knows alpha is a Number, etc.
let charlie = Math.pow(2, 10)->log(2) // since we know Math.pow returns a Number, we're good.
let delta as Number = some-library.some-unknown-method()
let echo = delta->log() // we specifically declared delta as a Number, so we can use ->log
let foxtrot = [1, 2, 3]->median // Array subtypes from { length: Number }
let golf = "hotel"->median // Hey, so does String!
let india = arguments->median // And so does Arguments
let juliet = { length: 4, 2: \kilo } // And so does this custom type
let lima as { length: Number } = some-outside-source()
let mike = lima->median
// assuming I come up with a "cast" operator, i.e. type-
// check with error or default value
// this would allow a String, Array, etc., would check
// at runtime (but not in production, with
// DISABLE_TYPE_CHECKING set to true)
let november = (some-outside-source() as { length: Number })->median
// require that the result is an array, otherwise
// evaluate to []. The default could be an expensive
// operation that is only executed when necessary.
let oscar = (some-outside-source() as Array = [])->median
// unknown is of the "any" type, representing all
// possible values, including null and void.
let unknown = some-library.some-other-method()
// Since unknown isn't at least { length: Number }, we
// don't know that ->median should map to our refinement
// { length: Number }->median and thus cannot be used
// without casting.
let wrong = unknown->median
I dunno, something to think about. Would people want to have this feature? let double(x as Number) x * 2
let repeat(x as String) x & x
let fun(x as Number|String)
if is-number! x
double x
else
repeat x
let fail(x as Boolean)
// this should fail at compile-time
double x
I could write the compiler to be a lot smarter and have it track types through conditional branches (which would be very hard, but possible), or allow it in those cases where it is possible that the types overlap. CAST(x, Number) // validate that x is a Number or throw a TypeError
CAST(x, Number|String) // validate that x is either a number or a string, otherwise throw TypeError
CAST(x, Number, 0) // validate that x is a Number, otherwise evaluate to 0.
Of course, in the cases where `x` could not fit the type requested, then that could error at compile-time.
If f were a string, how would I know I wanted to concat it instead of calling?