I noticed this didn't seem to get any responses, so I figured I'd throw some
ideas in:

> The concept of a statement is a little bit arbitrary. There are
> languages that do not have such a concept, but they're few; most
> languages have *some* concept of a form shorter-than-a-program that
> represents a single "chunk" of declaration-or-computation.

I thought Ruby, Smalltalk, Scheme, Lisp, and Io all lacked statements, so it
doesn't seem that uncommon to me. I'm not sure but maybe even the ML
languages fall into this?

Personally, I dislike statements so I'd be in favor of Rust or any other
language ditching them entirely. I'm working on a little language, magpie,
that's imperative but doesn't have statements. So far, it's been working
fine.

>  In a language with a first class unit-value like rust, the concept
becomes blurrier:
> any "just for side-effects" execution can also be considered as a
unit-value expression.

Yup. Isn't that what most ML languages do? The only tricky part is handling
expressions that do non-local flow control, like "return", "break", and
"throw". For magpie, I just defined another special type "Never" that is the
return type for those expressions. The type-checker then validates that that
type is never used in a place where it doesn't make sense. That lets you
prevent things like:

    foo(return 123); // meaningless since "foo" can never be called
    // (hence the name "Never" for the return expression's type)

That lets me do basic reachability analysis without having to add statements
to the language.

> - Blocks

Those can just become a "sequence" expression that evaluates a series of
expressions, discards intermediate results, and returns the last. Pretty
much "begin" in Scheme, if I remember right.

> - All 'movement' operations (assignment, send, receive) that have a
guaranteed side-effect.

It can be nice to be able to compose assignment expressions: if (foo =
bar()) { /* use foo */ }, but it's also a source of errors. <shrug>

Send can be a unit-valued expression, and receive seems like you'd want it
to be an expression anyway, unless its semantics are fixed to always receive
into some variable?

> - All branches (if, alt, alt type, etc.)

I like being able to do:

    let foo = if bar then 123 else 345

It reduces the number of times I need to declare a variable without an
initializer, which is always a good thing in my book. I prefer the former
over:

    let int foo
    if bar then foo = 123 else foo = 345

If you do go down this path, ad-hoc union types can be useful to allow the
branches to return different types:

    let foo = if bar then 123 else "string"

Normally, that would be a compile error. If you have unions, it could just
be that foo's type is "int | string". This is what Typed Scheme does as well
as other languages that try to do static analyis of dynamic languages.

> - All loops (while, do-while, for, for-each)

You could always just make them unit-valued expressions. If you add 'else'
clauses to loops (which could be neat!) then they could actually have a real
return type.

> - All non-local jumps (ret, put, break, cont).

I'm handling that with a "Never" type (essentially a bottom type). Seems to
work OK and lets you do some interesting things like:

    fn alwaysThrows(-> Never) // throw an exception...

    fn foo() {
        alwaysThrows();
        bar(); error: unreachable
   }

In other words, users can define their own abstractions that may also do
non-local flow and still be able to do reachability analysis on it for free.

> part of it was my own bias against
> programs that nest too deeply rather than just using more lines of text.

I agree completely. From my experience, ditching statements hasn't led to
painfully nested Lisp-style mega-expressions in my code. For the most part,
I still treat it as if many things are statement-like. I just have more
flexibility if I want it, and it makes the interpreter simpler to implement.

It also plays nicely with metaprogramming: it's easier to have things like
macros that expand to a chunk of code inserted in the middle of an AST if
you don't have to worry about distinguishing between whether a statement or
an expression is expected there.

> auto x = break + type t = int;

Reachability analysis and a bottom or never type would address that for you.
That would be a compile error since you can statically tell that the "+"
will never be evaluated so it's unreachable.

> I think it *might* be painting us into some corners syntactically; but it
is plausible.

One concern would be C-style declarations where a type name is the only
indicator that you're declaring a variable. Since Rust is using 'let', I
think it's OK there. I've found in magpie that making most expressions start
with a keyword ('let', 'if', etc.) keeps the grammar fairly comprehensible.

Anyway, hopefully that's helpful for you. Keep up the good work, I'm really
excited about the language.

Cheers,
- bob
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to