Future
All the syntax here are thoughts on language design that may or may not be interesting to implement in a language. Most of the syntax here came from a quick though, is bad and needs to be bikeshed.
Modern programming languages should optimize for readability.
Reading
-
Left to Right Programming (Programs Should Be Valid as They Are Typed)
When writing a fragment of code, the sooner you give context information, the sooner the language server is able to help you back. That is by giving type information, methods and functions discovery, etc.
Syntax Samples
Many of the tests shown here are usual constructs in programming but tried in a left-to-right syntax. I would like to explore the LTR design space.
is operator
-
Motivation: LTR programming
Would work especially well with path inference.
Equiv. (Rust) if let <pattern> = <expr>
let option = Some(42);
if option is Some(num) { }
if option.is Some(num) { }
not postfix operator
- Motivation: LTR programming
Would replace the current not prefix operator.
if not vec.is_empty() { }
// becomes
if vec.is_empty().not { }
if vec.is_empty().not() { } // keep as a boolean method?
postfix deref operator
- Motivation: LTR programming
let num = 10;
let myref = #
let mynum = myref.*;
bar.mac_call().*;
bar.field.*;
LTR Loop statements
let stmts = [];
let stmts_iter = stmts.iter();
while stmts_iter.next() is Some(stmt) { }
in stmts for stmt { }
in stmts the stmt { }
for stmts each stmt { }
for stmts be stmt { }
all stmts be stmt { }
loop stmts for stmt { }
stmts.for |stmt| { }
stmts.iter().enumerate().for |i, stmt| { }
LTR Trait implementation
for Struct impl Trait { }
Struct::impl Trait { }
Struct.impl Trait { }
Struct.impl { }
Postfix try control-flow operator
let res = Ok(98);
let num = res.?;
Type ascription
- Motivation: better type inference ergonomics
// annotate functions as such
let myexpr = f() : uint;
let myvec = (
(1..=100)
.into_iter()
.collect() : Vec<_>
)
.into_iter()
.map((|x| x+1):fn(uint) -> uint);
Generics
Which syntax to adopt?
< >collide with comparison operators and need special handling (e.g. Rust’s turbofish)
fn forget[T](v: T);
fn substring['a](s: &'a str) &'a str;
fn substring(s: &str) &str;
fn foo<T>(arg: T) T {
arg
}
Path
Which syntax to adopt?
use std.arith.Plus; // can be confused with field access
use std/arith/Plus; // no
use std::arith::Plus;
Alternative keywords
My rationale for using verbs instead of nouns for keywords is that they are less likely to conflict with user variable names. On the other hand, it’s difficult to have a wide range of verbs with similar semantic or just a verb for too many things. Using one or the other would be more coherent.
- Verb keywords:
deffor values (functions, constants)decl/dclfor type-level constructs (structs, enums, etc.)letfor local values (variables)
- Noun keywords:
fn/functype/struct/enumvar/valcst/const
def func() { }
def constant = 42;
trait Add {
// dcl → declare instead of `fn`
dcl Output;
// def → define instead of `type/struct/enum`
def add(self, other: Self) Self::Output;
}
Callsite code
- Motivation: Avoid excessive monomorphization
fn as_ref_str(s: impl AsRef<str>) {
@callsite let s = s.as_ref();
// snip
}
// alternative vvv?
fn into_string(s: can Into<String>) {
let s = s.into();
// snip
}
fn callsite() {
let my_str = "...";
as_ref_str(my_str);
into_string(my_str);
// instead of the following to avoid generics
as_ref_str(my_str.as_ref());
as_ref_str(&my_str);
into_string(my_str.into());
}
Arbitrary prefixed string literals
This would could integrate well with a good comptime system. Is it worth?
comptime fn regex(lit: &str) { }
let regex = regex""
// could lower to
let regex = comptime { regex("") }
// e.g. c"im a cstr"
Enforce purity and capabilities
Enforce purity through effects, doc_effects, capability system or another system that suits.
Track
- writes to stdio
- calls to unsafe
- hidden interaction with randomness (e.g. Rust HashMap)
- allocations
This is one of the largest goals here
Require value binding
Can be simply implemented as a lint. Acts like if everything was (Rust) #[must_use]
Rational number type
Motivation: experiment with floats as a last resort construct and work with rational numbers
no semicolon in statements, functional syntax?
- OCaml syntax?
fn foo(arg: Ty) ->
a = make_foo();
b = make_bar(a);
()
Concern: how to know to return or not last value
- depend on the type system? consider it a return if type matches, seems rebust to errors
fn boo(arg: Ty) {
_ = make_blah()
}
Hot-reloading
Would be fun to play with that. Kind of like dotnet watch.
Path inference
enum InferKind {
Infer,
NoInfer,
}
fn foo(qrcode: InferKind);
foo(.Infer);
match InferKind.Infer {
.Infer -> {}
.NoInfer -> {}
}
Pipe operator/construct
Motivation: avoid nesting calls or let repetition
Question: how to tell where are going the arguments
Related: See Uniform function call syntax
let value =
compute_value(my_looooooooog_value_name)
|> transform_value1()
|> transform_value2()
|> transform_value3();
// reuse closure syntax?
let value =
compute_value(my_looooooooog_value_name)
|> transform_value1
|> |v| transform_value2(v)
|> |v| transform_value3(v, 2);
// semantically equivalent to
let value = transform_value3(transform_value2(transform_value1(compute_value(my_looooooooog_value_name))), 3);
// which may format as
let value = transform_value3(
transform_value2(transform_value1(compute_value(
my_looooooooog_value_name,
))),
3,
);
// or chaining the same name to avoid nesting and keeping clarity
let value = compute_value(my_looooooooog_value_name);
let value = transform_value1(value);
let value = transform_value2(value);
let value = transform_value3(value, 3);
Operators
@ pattern
| pattern, bit-or
& pattern, bit-and
_ pattern
~ pattern?, bit-not?
# attributes
% bitop? do not use for rem
* bitop
= bitop
+ bitop
- bitop
/ bitop
? unary try
! unary not
\ escape
'' char, lifetime?
"" string
`` ?
() delim value param
[] delim index
{} delim stmts, group
<> delim type param
, punct param
: type
; stmt end
. field
^ ???
$ raw ident?
struct Fields {
foo: uint,
$let: uint,
}
fields.$let