evincarofautumn / kitten

A statically typed concatenative systems programming language.
http://kittenlang.org/
Other
1.08k stars 39 forks source link

Wonderful! #219

Open Geordi7 opened 3 years ago

Geordi7 commented 3 years ago

I commend you on making a beautiful little language. I have long suspected that what you call "concatenative" style is where PLs should be heading, and you've gone and done it!

Thoughts:

1: Did you really mix {} enclosed scope and whitespace sensitive scope following : ?

2: In a concatenative language, every enclosed expression is also a function:

define play_game (-> +IO +Exit):
    do (while) { play_round maybe_play_again }

{play_round maybe_play_again} has the type (-> bool), and this function could be written:

{do (while) {play_round maybe_play_again}}
(-> +IO +Exit) -> play_game

3: In a concatenative language, type expressions (like (-> +IO +Exit) above) are just another function which the compiler can decide whether to resolve at compile-time or run-time

You propose this syntax for matching:

define read_x_y (Char, Char -> Optional<Move>):
    match (dup2 try_read_x_y)
    case some: \drop2 dip some
    case none: swap try_read_x_y

But this dilutes your syntax IMO, it could look like this:

define read_x_y (Char, Char -> Optional<Move>):
    dup2 try_read_x_y
    (some) {\drop2 dip some}
    (none) {swap try_read_x_y}

Here we are applying a strict rule that parens () denote type constraints, and braces {} denote scopes/control flow. When the compiler encounters a type constraint, it decides whether it can be resolved at compile time (this is valid or invalid code), or at run time (some kind of branch is taking place).

If statements are also just run-time dependent-type constraints, and similarly, loops are constraints that get reevaluated at the end of the activated scope:

define parse_move (List<Char> -> Optional<Move>):
    -> chars;
    do (with (+Fail)):
        if (chars length = 2):
            (chars 0 get from_some)
            (chars 1 get from_some)
            read_x_y
        elif (chars length = 1 && { chars 0 get from_some = 'q' }):
            forfeit some
        else:
            none

could look like this:

define parse_move (List<Char> -> Optional<Move>):
    -> chars;
    do (with (+Fail)) {
        chars length
        (= 2) {
            chars 0 get from_some;
            chars 1 get from_some;
            read_x_y
        } (= 1) {
            chars 0 get from_some;
            (= 'q') {forfeit some} {none}
        } {none}
    }

The implication here is that

(constraint1) {scope1} (constraint2) {scope2} {scope3} behaves like

if (constraint 1) {scope1} else if (constraint2) {scope2} else {scope3}

I don't know if you like this... but I do :D

Of course, if you follow this approach, you will run into problems scoping other expression types without the use of () my suggestion would be to exchange () and {} such that parens denote scope, and braces denote constraints.