Giter Club home page Giter Club logo

Comments (64)

rain-1 avatar rain-1 commented on May 25, 2024 7

My suggestion would be stick with the current s-expression syntax

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024 4

Still wrestling with my own opinion on grouping-by-reader versus grouping-by-binding. I've claimed that grouping-by-binding may need more tool support, but the more I think about it, I'm not sure what that entails.

Maybe the problem is that I don't currently rely enough on tools. I need parenthesis matching and I use operations that traverse S-expressions and transpose them, but that's about it. I rely on almost the same things when editing C; the transpose operation applies less often, but I navigate a lot by skipping over groups. I can imagine that operations that take hints from indentation would work about as well, and I can imagine that automatic indentation might work well enough as heuristic based on indentation and grouping operators like & (if that turns out to be a good idea). Maybe other kinds of tools (besides editors) are relevant, but I haven't yet remembered one.

To help bring out the tradeoffs, can others comment on the way they see grouping-by-reader versus grouping-by-binding affecting the kinds of tools they use?

from rhombus-prototype.

jackfirth avatar jackfirth commented on May 25, 2024 4

@gus-massa I've routinely wished that forms could have some compile-time information attached saying how they should be indented so I wouldn't have to keep adding ad-hoc indentation rules to drracket's preferences.

from rhombus-prototype.

rocketnia avatar rocketnia commented on May 25, 2024 4

@jeapostrophe I'd bet it's pretty tough to match parens by eye in that style. Do you find there are techniques that help? (Or do you use tools that help?)

With Lispy code like...

(abc (abc abc)
  (abc abc
    abc)
  (abc abc
    (abc)))

I find it's pretty easy to determine how many closing parens to write on a line. I zig-zag leftward and upward until I hit the left margin, and whenever I horizontally pass under a paren, I know that's one of the parens I need to close.

Indentation like this can mess with my groove...

#hash(
  (a . 1)
  (b . 2))

...because as I'm zig-zagging along the left margin, I never pass directly under that first opening paren. Generally, as soon as I see an opening paren at the end of a line, I have to slow down and pay much more meticulous attention to parens.

Indentation like this helps with the zig-zagging technique but drifts way too far to the right:

#hash(
      (a . 1)
      (b . 2))

Because of this, I prefer syntaxes like (hash ...) that begin with an opening paren, not those like #hash(...) that have an opening paren somewhere in the middle. At times I've suggested that a reader syntax for hashes should be something like (##hash ...) so that it can have this property. For the same reason, I've also built markup languages not unlike TeX or Scribble, but which use [foo ...] instead of TeX's \foo{...} or Scribble's @foo{...}.

In languages like JS, core syntaxes like while (...) { ... } are designed with opening parens/brackets somewhere in the middle. This would put a wrench in this zig-zagging technique, but in these languages, there's a completely different established convention that makes it easy to match brackets by eye:

abc (abc abc) {
  abc abc {
    abc
  }
  abc abc {
    (abc)
  }
}

Or the more diffuse style:

abc (abc abc)
{
  abc abc
  {
    abc
  }
  
  abc abc
  {
    (abc)
  }
}

Using these styles, it's easy to determine how many closing brackets to write because each one is written on its own line, decreasing indentation until they finally hit the left margin. In a way this is much easier to keep track of than the Lisp zig-zagging method since it doesn't require examining anything other than the nearest few lines of code.

From your examples, it looks like even when you're using JS-style syntax, you still use Lisp-style stacks of closing brackets. I don't wanna tell you it doesn't work, 'cause it's obviously working for you, but I'm curious if you could articulate any techniques or advantages you encounter using that style.


Sort of unrelatedly, the }); }); }); } }; patterns remind me of similar bracket combinations I've encountered writing continuation-passing style code in JS. These patterns have led me to think that it's usually a mistake for a language to use more than one kind of paren. It'd be way easier to write } } } } } } } }, wouldn't it? :)

More generally, rightward drift is a pretty big annoyance in continuation-passing style JS. One technique I saw emerging a few years ago was to use generator function syntax as a way to tame this nesting, not to mention async/await for the specific case of asynchronous CPS computations, so maybe it isn't so bad now. For my purposes at the time, I was regularly finding myself with stacks of like 45 closing brackets. One reason moving from JS to Racket was a breath of fresh air was 'cause it meant I could cut down on the parens. :)

from rhombus-prototype.

bzyeung avatar bzyeung commented on May 25, 2024 3

The first thing that jumps out at me when I look at those examples is a fair number of patterns of alternating ; and }, somewhat reminiscent of the profusion of closing parens we have now. That got me thinking that both c-expressions and s-expressions make sense to people who are familiar with those types of syntax, but can seem quirky to newcomers. From there, it occurred to me that one of the most commonly encountered differences between programming languages and natural languages is that natural languages generally use periods to end statements. Maybe that could be a "distinctive feature" of the syntax? More generally, perhaps a syntax that takes relatively more cues from natural language conventions than is typical?

Some of these exchanges also got me thinking on a slightly more meta/wants kind of level:

Are we including lowering barriers for "potential programmers" or "first-time programmers" when we aim for "programmers generally"?

Hearing the tradeoffs about grouping (more flexible, cleaner, dependent on tooling), would it be helpful to start work towards compiling a list of criteria that we use to communicate about decisions? I don't think we're at the stage where we could put together a discussion framework per se, but maybe writing down in a central location some of the ad hoc things people have mentioned would help? Kind of a "checklist of things to consider" or some such, which would of course be very fluid right now.

(Yes, I am volunteering to kick off or contribute to some of these threads if they are deemed helpful and no one more qualified has the time.)

from rhombus-prototype.

Nyanraltotlapun avatar Nyanraltotlapun commented on May 25, 2024 3

See the syntax-considerations.md file. Additionally, see these two paragraphs from the Racket2 possibilities

Why big changes in syntax is a consideration at all?

Removing hypothetical small obstacles for some hypothetical future users in the cost of current users? Students complaint reduction? Change to something without studying this something as being good in some metric?
This just makes no sense at all.
You cannot reduce students complaint by changing syntax of any sort, you just need stop demanding from them to learn.

That said, lisp have one of the most simple syntax, brackets are not the only thing that creates "barriers", maybe we should first study what barriers do C syntax have?
I can tell as person who have experience of teaching C python C# and java that students tend to complain on all sorts of things for no god reason. Student cannot understand roots of their difficulties, this is why they a students after all.

- A language that doesn't affect the way you think about programming, is not worth knowing
Alan Perlis.

And what about professionals demands?

I just want to show here that this considerations just really strange and bold statements.
And we already try to discuss concrete syntax changes?

S-expressions is not the best possible, but they works pretty good, at least somehow somewhere better then other languages I known. So to make decision about moving from them we must have some evidence of actual sufficient improvements as well as evidence of lack of comparable drawbacks.
At least then one can say that this is for the good of future of programming languages.

For example:
https://quorumlanguage.com
https://www.youtube.com/watch?v=uEFrE6cgVNY
This guys at least do some research, lets at least analyze their work.
For example their study shows that static typing of some sort have major positive impact on productivity for professionals and only small and temporal drawback for beginners.

I'm working on a document to help with that, in a similar vein to the list of syntax considerations, but from a broader perspective.
I think it would be worthwhile to put together a proposal much like the C-expression proposal for S-expression syntax. It might help better define objectives, wants, and tradeoffs in general.
I want to push perception science and Quoruml lang approach to syntax design. Somehow. At leas make people aware that there is such research.

I also want to point this videos
https://www.youtube.com/watch?v=R3zEOsh8AnQ
https://www.youtube.com/watch?v=Ps3mBPcjySE
And this
https://www.youtube.com/watch?v=Sg4U4r_AgJU

from rhombus-prototype.

sorawee avatar sorawee commented on May 25, 2024 3

I really hate the standard JS/C style where {}s are on their own lines. It feels like such a big waste of vertical space and inhibits reading the code.

I sympathize with that, but there are also arguments against the Lisp style.

  • Asymmetry: cases (or expressions in the body) should look "identical". The Lisp style breaks that for the last case/expression. This results in

    • Awkward diff

        match x {
          1 => 2
          2 => 3
      +   3 => 4
        }

      vs

        match x {
          1 => 2
      -   2 => 3 }
      +   2 => 3 
      +   3 => 4 }
    • Inability to comment on the last line (I just raised this issue in Slack a few days ago)

      match x {
        1 => 2 ; I can comment here
        2 => 3 ; and also here
      }
      

      vs

      match x {
        1 => 2 ; I can comment here
        ; I need to comment here :(
        2 => 3 } ; Or here? What if this is also in another match?
      
    • Awkward editing:

      • Extra step: I'm not sure how people usually edit this code, but my in-process-of-editing Racket code is always in this position:

        (match x
          [1 2]
          [3 4]| << caret
          )
        

        It's only when I'm done with all cases that I need to do the extra step to join lines to

        (match x
          [1 2]
          [3 4])
        

        I wouldn't need the extra step in JS/C-style.

        (Yes, the JS/C style looks horrible on code in S-expression, so I have been tolerating this problem, but for non-S-expression, I think we can do better)

      • Adding a case: I need to go to the position before [3, jump to the matching bracket, hit enter to add a new case. This contrasts with the JS/C-style where I can just go to the end of line and hit enter right away.

      OK, probably the editing problem won't be an issue if I use paredit or some fancy Emacs mode, but it's really not beginner friendly (which as I understand is one of the reasons why we want to explore non-S-expression syntax in the first place). Do we really need to teach students in intro to programming various keybindings first to make them edit code effectively?

  • I've also heard some of my friends criticize the Lisp style as "too dense". The "waste of vertical space" gives them space to pause and think about code they just read. I personally don't have this problem, but some do.

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024 2

In Elixir, Remix, and Honu, there are a number of syntactic patterns that are effectively built into the reader layer. The reader converts patterns like infix syntax, dot notations, function calls into S-expressions that effectively record the original shape. See https://elixir-lang.org/getting-started/meta/quote-and-unquote.html for Elixir.

I forget exactly what Remix and Honu do, but you could imagine that <expr>(<expr>, ...) is grouped by the reader into #%app, <expr>[<expr>, ...] is grouped by the reader into #%index, <expr>.<expr> is grouped by the reader into #%dot, and so on. To avoid bad puns, instead of identifiers like #%dot that are potentially written literally in the source, the S-expression encoding could take a form that cannot be represented in the source except by actually using <expr>.<expr>.

I find this direction appealing because, like S-expressions, it provides a shared vocabulary for constructing terms without necessarily imposing a meaning on the terms. I also like the way it tends to distinguish syntactic form names from variable names. By that last statement, I have in mind the way that function application is a kind of fallback interpretation of parentheses in Lisp/Scheme/Racket. The fallback interpretation allows function calls to have a lighter syntax instead of putting #%app (or something shorter) on every application, but it makes it harder for someone approaching a new set of syntactic extensions to know which identifiers correspond to syntactic forms are which are just expressions that have a function value.

That idea is implicit in the examples I wrote in the previous comment. You have surely assumed that is_one is a function and not a syntatic form. In fact, is_one might be a macro, maybe because it is bound to a function with a contract — but if the author of is_one has any taste, then it's going to behave as an expression. Meanwhile, there's no question that match or generator was meant as a syntactic form.

from rhombus-prototype.

gus-massa avatar gus-massa commented on May 25, 2024 2

I think it would be good to not have operator precedence, or reduce it to a very small number of categories like in https://github.com/racket/racket2-rfcs/pulls

I'd prefer that (x + y * z) were an error, I never can remember how (x && y || z) should be interpreted, but I'd like to make it possible to write (x + y - z) and (x * y / z).

What about operator families that can be grouped? But with no precedence order between most of the families?

Also, I'd like to write (x <= y < z) too, but that I still can't imagine how (x <= y < z) can make any sense, unless the result is #f or z instead of #f or #t.

from rhombus-prototype.

Nyanraltotlapun avatar Nyanraltotlapun commented on May 25, 2024 2

Can we at least know the goals of syntax changes?
And then, after this, find scientific evidence on what changes more or less fulfills the goals?

For now it looks like discussion without any goal.
If there is no good reason and no scientific evidence I propose to stay with s-expressions.

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024 1

That matches my expectations @lojic --- In particular, both of those do what you think in C, which is obviously the best place to get advice from.

from rhombus-prototype.

jackfirth avatar jackfirth commented on May 25, 2024 1

At Google, the Java autoformatter (run automatically over all code, which is in the billions of lines) used to be reader-based, with no knowledge of what specific methods the formatted code was calling. But it was changed to be binding-aware because there were too many cases where the semantics of methods changed how people liked to format them. The most prevalent use case was Java 8 stream pipelines. The old formatter would write getSomeMap(...).entrySet().stream().map(...).filter(...).collect(...) like this:

getSomeMap(...)
    .entrySet()
    .stream()
    .map(...)
    .filter(...)
    .collect(...)

The new autoformatter writes it like this:

getSomeMap(...).entrySet().stream()
    .map(...)
    .filter(...)
    .collect(...)

...the logic being that people find it much easier to read stream pipelines if they have a clearly separated beginning, middle, and end. And it's impossible to know where the beginning of a stream pipeline is without knowing which methods return a Stream<T>. There were many similar use cases related to fluent test assertions, mocking frameworks, and builder objects.

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024 1

Here's a new syntax proposal, ya'll: #109

from rhombus-prototype.

spdegabrielle avatar spdegabrielle commented on May 25, 2024

is this right?

  • familiar function notation: function(arg1, arg2, ...)
  • infix notation: width*height
  • brackets for grouping: (a + b)

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

I'd say it was parentheses for grouping, i.e. you can always put in parens and it doesn't turn things into some other mode

from rhombus-prototype.

AlexKnauth avatar AlexKnauth commented on May 25, 2024

I think the three things were:

  • infix notation: a + b with spaces like Pyret
  • algebra-style function notation: function(arg, ...)
  • parens for grouping: (a + b) where wrapping an expr in parens doesn't mean an extra function call

from rhombus-prototype.

dedbox avatar dedbox commented on May 25, 2024

Would parentheses still be used to delimit individual expressions?

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

@dedbox My interpretation of Matthew is that you can always add ()s, unlike some non-() Sexprs where adding ()s drastically changes what something means

from rhombus-prototype.

rocketnia avatar rocketnia commented on May 25, 2024

It's a minor point, but I don't think grouping parens "always" work in languages that have them. Some only let you add them around expressions.

Grouping parens serve the purpose of disambiguating expressions that use ambiguous (or in any case easily misunderstood) combinations of syntactic extensions. Therefore, I think an infix language with as much syntactic extension as #lang racket will likely find a need for grouping parens around each of the extensible grammar nonterminals: Expressions; assignment targets; match and syntax/parse patterns; require and provide clauses; and anything else I'm overlooking here. (Rename transformers? syntax/parse ellipsis-head patterns?)

On the other hand, maybe infix syntax doesn't have all the same benefits in every one of those places. Although infix syntax and grouping parens are familiar from the way people learn to compute with desktop calculators, I haven't heard of people punching in require clauses into those calculators, so there may not be much familiarity benefit to using infix there.

Edit: At first I talked about "anywhere," but then I realized the word you used was "always," so I patched it up to refer to that instead.

from rhombus-prototype.

lojic avatar lojic commented on May 25, 2024

With respect to function calls, one of the things I love about Scheme is:

((get-my-fun a b) c d)

So, hopefully we'd still be able to do this. For example: (get-my-fun(a, b))(c, d) or get-my-fun(a, b)(c, d)

Is that an expectation shared by others?

from rhombus-prototype.

spdegabrielle avatar spdegabrielle commented on May 25, 2024

I like that the idea that adding parenthesis doesn't change meaning ( Thank you @jeapostrophe )
this means that both have the same meaning;
(get-my-fun(a, b))(c, d)
get-my-fun(a, b)(c, d)
or (adopting scribble at-exp notation)
(@get-my-fun(a, b))(c, d)
@get-my-fun(a, b)(c, d)

from rhombus-prototype.

AlexKnauth avatar AlexKnauth commented on May 25, 2024

Could/should we achieve point (2), algebra-style function notation function(arg, ...), the same way Haskell does with tuples?

Make function expr1 expr2 ... mean function application of multiple arguments, so naturally grouping it with (function expr1 expr2 ...) would be just as valid
Make function (expr1, expr2, ...) mean a function application passing arguments in a single tuple

from rhombus-prototype.

lojic avatar lojic commented on May 25, 2024

I do like how Haskell handles this i.e. functions either being defined to accept a tuple, or to be defined as curried functions which can be partially applied.

Although Matthew seemed to have a strong opinion about including the commas. I also wonder if supporting the two approaches, especially in a dynamically typed language, may add confusion to the folks we're trying to appeal to. In other words, having folks differentiate between add x y z and add (x, y, z), and knowing when one is preferable to the other.

IIRC in Haskell, add x y z is equivalent to ((add x) y) z - would we also support that grouping?

from rhombus-prototype.

AlexKnauth avatar AlexKnauth commented on May 25, 2024

No, I don’t think auto-currying function application like ((add x) y) z is a good idea for Racket since Racket has variable-arity functions.

add x y z should mean the same thing (add x y z) does now

from rhombus-prototype.

lojic avatar lojic commented on May 25, 2024

I misunderstood your suggestion. If we're not allowing curried function definitions, then it seems neither add x y z nor (add x y z) are keeping with the spirit of providing a syntax that people are used to from middle school algebra i.e. won't they be expecting f(x, y, z) ?

from rhombus-prototype.

AlexKnauth avatar AlexKnauth commented on May 25, 2024

They will be able to write f(x, y, z) because they will be able to write f arg with arg as a tuple (x, y, z), if f is defined to accept a tuple. However, add x y z looks better in many situations where x, y, or z is a complex expression that spans multiple lines, and also works better with existing (add x y z)

from rhombus-prototype.

rocketnia avatar rocketnia commented on May 25, 2024

It sounds like that would lead to some friction in the module ecosystem, where some modules' functions would expect tuples and some would expect multiple arguments.

Sometimes fragmentation like that is inevitable if different languages have vastly different notions of what functions do. But I think if we want Racket 1 and Racket 2 to use mostly unified docs and to have mostly seamless interop, then if we do end up going for a syntax resembling func(a, b, c) in Racket 2, it should have the semantics Racket 1 functions expect.

(I've got a lot of ifs in there, because a lot of the questions of what Racket 2 is for, what kind of syntax it will have, and what role Racket 1 will play in the future are still subject to discussion.)

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024

One of the choices in an overall syntax is how to handle grouping. Many alternative notations for Lisps involve the same sort of grouping as parentheses, but written some other way (e.g., newlines, a : to indicate that parens wrap the following terms).

Elixir and Honu (and maybe other languages) move away from that. Syntactic transformsations are still triggered by a leading identifier, but the macro implementing the syntactic form consumes some number of terms that follow. In other words, you have to know some bindings to know where a grouping ends. In Elixir, it seems that the number of terms consumed by a macro is fixed statically, and do: ... end is used as an alternative to parentheses or braces to group as needed into a single term for a macro. Honu does not have that constraint.

In the spirit of brainstorming as Neil suggested on the mailing list, here's another idea along those lines. It fits within the mechanisms of Honu, but it may have a more precedent that I haven't yet seen.

Idea: Instead of grouping terms by start and end tokens, group them by separator between the pieces that should be joined.

One concrete instantiation of that idea is to use & as separator for "begin"-like and "and"-like sequences and | as a separator for "match"-like and "or"-like sequences.

Some examples that show how Racket-style things might be written in a language that uses separators:

  (define (f s)
    (printf "hi ~a\n" s)
    "hi")
 =>
  ;; Sequence is less implicit and directly supported by `define`,
  ;; which continues when it sees `&`
  define f(s) =
    printf("hi ~a\n", s)
  & "hi"

  (cond
    [(one?) "one"]
    [(two?) "two"]
    [else "many"]
 =>
  ;; A sequence of `cond` clauses is "or"-ish, and
  ;; we neeed a `|` even on the first clause if we
  ;; want to allow 0 clauses
  cond e
  | is_one() "one"
  | is_two() "two"
  | else "many"

  (and (f) (g) (h))
 =>?
  ;; Looks redundant to have both `and` and `&`, but
  ;; maybe worth the consistency. Skipping a leading
  ;; `&` means that `and` needs at least one subform.
  and f() & g() & h()
 =>?
  ;; Maybe it's better to just use `&&` as an operator, anyway
  f() && g() && h()
 
  (or (f) (g) (h))
 =>?
  or f() | g() | h()
 =>?
  f() || g() || h()
 
  (let ([x 1]
        [y 2]
        [z 3])
     (+ x y z))
 =>?
  ;; A sequence of bindings seems "and"-ish
  let x = 1
    & y = 2
    & z = 3
    x+y+z
 =>?
  ;; Or is the "=" close enough to a separator already?
  let x = 1
      y = 2
      z = 3
    x+y+z

  ;; Example recently posted on Slack
  (define (powerset s)
    (generator ()
               (cond [(null? s) (yield '())]
                     [else
                      (for ([e (in-producer (powerset (cdr s)) (void))])
                        (yield (cons (car s) e))
                        (yield e))])
               (void)))
 =>
  ;; Note that parentheses are needed around `for` so that `& yield(e)`
  ;; belongs to the `for` body while `& void()` belongs to `generator`.
  define powerset(s) =
    generator ()
      cond
      | is_empty(s) yield(empty)
      | else (for e = in_producer(powerset(rest(s)))
                 yield(cons(first(s), e))
               & yield(e))
    & void()

Just to emphasize: relying on separators this way does not lead to an automatic parenthesization independent of binding, because the transformation would require knowing that match is bound to a syntactic form that uses | as a separator after the first form, and so on.

from rhombus-prototype.

rmculpepper avatar rmculpepper commented on May 25, 2024

One issue seems to be that finding the extent of an expression seems to require predicting the shape associated with identifiers inside the expression. That seems like a problem for local bindings forms (especially let-syntax but even let and lambda) and a bigger problem for a hypothetical where-like binding form.

  1. The problem with let-syntax:
cond | one() let-syntax m = m_transformer() in m 1 | two() 2 | else 3

Does the two() 2 belong to cond or to m? What if m was named case? It seems that in order for cond to determine the extent of its first clause, it must predict the shape that m gets from m_transformer.

  1. The problem with let:
cond | one() let case = add1 in case 1 | two() 2 | else 3

If we assume that case originally has a cond-like shape (except an expression before the first |), then when let shadows case, it should have the default variable/function shape instead. This is easier than (1), if let always binds names with the default variable/function shape, but it demonstrates that just eliminating let-syntax doesn't solve the problem.

  1. The problem with where: It has the same problem, except the uses occur before the binding, so we probably can't even look at the binding to determine its new shape.

One solution might be to disallow local shadowings that change the shape of an identifier (where unbound identifiers have a default shape compatible with variable references and maybe function calls).

How did Honu deal with that?

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024

@rmculpepper Great questions. For 1 and 2, I think probably we didn't notice that problem, because we didn't get to a concrete syntax for macro definitions in Honu (so we didn't get to the question of local macros. One possible answer is that parentheses are needed around the let body in that case. For 3, if I recall correctly, Honu couldn't handle where at all, since it stuck with macro dispatch based on a leading identifier (and trying to treat where as an infix operator would not work in Honu, I think).

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

I really like the Elixer/Remix/Honu principle of identifying a set of patterns that get special treatment in the reader. I like the creative thinking of @mflatt wrt & and |. I want to see us come up with a small set of such things that add up to a buttery C-like syntax.

When I look at beautiful, well-formatted C, I read it like my dream language where

  • Macro applications start with a leading identifier, like while, switch, and typedef and consume units until a ;. (C doesn't enforce this, but I also write my C code with these terminals ;s.)
  • Function and variable definition is so important that #%module-begin is made up purely of these definitions, plus module imports.
  • Variable definition is very important in #%block, so that it starts with a sequence of =s that are parsed as defining variables and then switches to something else. return and == help to differentiate definition from expressions.
  • Inside of parens and blocks, ; and , serve to continue the sequence, depending on what kind of sequence it is. , are for "expression sequences" and ; are for "statement sequences".
  • . and -> are special infix operators that are bound to macros

What I tried to do a little bit with Remix and want to push more in Racket 2 is to have code that looks almost exactly like C, but has a little bit more regularity and the full power of an amazing Racket-style macro system.

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

I just posted a proposal in #88

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024

It's great to see a concrete proposal to help discussion!

I remain torn between the alternatives of grouping in a binding-insensitive way (e.g., always until a semi-colon, always parentheses, always by indentation) and the grouping by enforestation approach of Honu. The former is simpler, but it tends to be more noisy, at least in the context examples I've seen. The latter is more flexible and can support cleaner looking syntax (IMO), but it requires more tool support.[*] Maybe more concrete proposals will help.

Although I'm not inherently opposed to a syntax that looks like C, one of the ideas that I found found most interesting in the discussion at RacketCon was from John Clements: we should consider picking a syntax for Racket2 that is intentionally distinct, so that when people see they code, they instantly recognize it as Racket2. Just as a straw man, swapping the role of curly braces and square brackets compared to C could have that effect, and it could also lesson the discomfort of semi-colons that look unnecessary to eyes trained to read C. Of course, a swap like that could also look gratuitous.

[*] When I think about tool support, it reminds me that we want better indentation support in DrRacket, and maybe that's essentially the same problem as dealing with enforestation. Even if so, concluding that we need certain tool support in DrRacket is not the same as concluding that it's an acceptable requirement for all parts of the ecosystem.

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

I don't think I can say anything new about the virtue or not of binding-insensitive grouping. I agree with you on the advantages and disadvantages. I am very pessimistic about good tool support and think it is very important to easily support a large number of editors on day 1.

As far as the distinctiveness, I agree that something distinctive would be nice. I think it would very gratuitous to base it on search&replace. I am not creative enough right now to think of something radically different.

However, I think that C-expressions can lead to very distinct styles. Here are five programs that I believe are all C-expressions and are also real programs in five different languages. I may have added parens or other little tokens not found in the wild, but I bet you could guess what language each is.

If you can, then you should believe that C-expression-based languages are not all the same; in the same way that it is very clear if a program is written in R5RS, Racket, Common Lisp, or Clojure. Here are the five programs:

Example 1:

const express = require("express");
const app = express();
const port = 3000;

app.get("/", (req, res) => res.send("Hello World!"));

app.listen(port, () => console.log("Example app listening on port ", port, "!"));

app.get("/", function (req, res) {
  res.send("Hello World!");
});

Example 2:

int main () {
  int i;
  const char* s[] = { "%d\n", "Fizz\n", s[3] + 4, "FizzBuzz\n" };
  for (i = 1; i <= 100; i++)
    printf(s[(!(i % 3)) + (2 * (!(i % 5)))], i);
  return 0; };

Example 3:

int main() {
 int a[] = { 1, 3, -5 };
 int b[] = { 4, -2, -1 };
 
 std::cout 
  << std::inner_product(a, (a + sizeof(a)) / sizeof(a[0]), b, 0)
  << std::endl;
 
 return 0; };

Example 4:

public final class FlattenUtil {

 public static List<Object> flatten(List<?> : list) {
  List<Object> : retVal = new LinkedList<Object>();
  flatten(list, retVal);
  return retVal; };
 
 public static void flatten(List<?> : fromTreeList, List<Object> : toFlatList) {
  for (Object : item = fromTreeList) {
   if (item `instanceof` List<?>) {
    flatten(List<?> : item, toFlatList);
   } else {
    toFlatList.add(item); }; }; }; };

Example 5:

fn factorial_recursive (n: u64) -> u64 {
 match n {
  0 => 1,
  _ => n * factorial_recursive(n-1) }; };
 
fn factorial_iterative(n: u64) -> u64 {
    (1..n+1).fold(1, |p, n| p*n); };
 
fn main () {
    for i in 1..10 {
        println!("{}", factorial_recursive(i)); };
    for i in 1..10 {
        println!("{}", factorial_iterative(i)); }; };

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

@bzyeung I think you should make such a thing.

I agree that }; pattern is ugly. I think that's what Matthew and I meant by "noisy". I think it is actually there because the C-style is not normally functional so it looks odd to have so much nesting. It's definitely a trade-off to get the tree-shape. I think there are a lot of little things that could be done taking a cue from some of the S-expression alternate notations.

For example, we could use a symbol like MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET as a "giant right parenthesis" that closes everything that is open. If that's too absurdly Unicode, then taking a cue from you, a . as the last non-comment character on a line could mean the same thing. I think it would be useful inside of functions, but not really inside of "classes" because you may have more functions afterwards. So the "Rust" example would look like:

fn factorial_recursive (n: u64) -> u64 {
 match n {
  0 => 1,
  _ => n * factorial_recursive(n-1).
 
fn factorial_iterative(n: u64) -> u64 {
    (1..n+1).fold(1, |p, n| p*n).
 
fn main () {
    for i in 1..10 {
        println!("{}", factorial_recursive(i)); };
    for i in 1..10 {
        println!("{}", factorial_iterative(i)).

But the "Java" example is

public final class FlattenUtil {

 public static List<Object> flatten(List<?> : list) {
  List<Object> : retVal = new LinkedList<Object>();
  flatten(list, retVal);
  return retVal; }; // <--- No .
 
 public static void flatten(List<?> : fromTreeList, List<Object> : toFlatList) {
  for (Object : item = fromTreeList) {
   if (item `instanceof` List<?>) {
    flatten(List<?> : item, toFlatList);
   } else {
    toFlatList.add(item).

A similar idea would be to have a combination of indentation and newlines be relevant: An indented line would imply a { and a blank line would imply a };. So, the "Java" version could look like:

public final class FlattenUtil
 public static List<Object> flatten(List<?> : list)
  List<Object> : retVal = new LinkedList<Object>();
  flatten(list, retVal);
  return retVal;
 
 public static void flatten(List<?> : fromTreeList, List<Object> : toFlatList)
  for (Object : item = fromTreeList)
   if (item `instanceof` List<?>) {
    flatten(List<?> : item, toFlatList); }
   else {
    toFlatList.add(item); };
// close for

// close flatten

// close class

It's a bit awkward to have those three blank lines at the end, so I think stylistically, we'd encourage people to include the }; for the for and class.

I don't think I'm creative to come up with a new syntax. I really like tree shapes and I really like the different notational textures in C-style code. When I actually program in C, I am annoyed that it is not more tree like. In Racket, I am annoyed it is so uniform. When I program in Haskell and ML, I am really annoyed at how not tree-like and fiddly they are. (I find the Haskell indentation rules excruciating.)

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024

Another brainstorming variant: use separators for grouping — but do that always, so there's no dependence on binding to group terms.

https://gist.github.com/mflatt/f7313e1561210be56313903fd75c6607

As you'll immediately notice, this direction seems to need precedence(!) to work nicely for simple examples. And for complex examples, like the one at the end, just use parentheses when the precedence gets too hard.

We may want to reserve colon for a more type-like meaning, so maybe it's not a good choice for the operator to use after a syntactic form and it's distinct kinds of parts. While the : immediate after a syntactic form has its appeal, the extra : to separate let bindings from the body or a match expression from the clauses is less appealing.

from rhombus-prototype.

gus-massa avatar gus-massa commented on May 25, 2024

Something that I'm wondering is if it is good that the first clause in a cond is special. For example instead of:

cond :
  is_one() => "one"                               // <-- without "|"
| is_two() => "two"
| else => printf("else\n"), "many"

one possibility is:

cond :
| is_one() => "one"                               // <-- with "|"
| is_two() => "two"
| else => printf("else\n"), "many"

It's almost like the indentation in Python. It makes the (multiline) expression more uniform and makes reordering and adding/removing clauses more easy.

It's also more similar to (my) programming in whiteboard where the blocks are marked with a big line at the left margin.

(Also, I've heard some complains about Erlang, because in some cases you have to use : or ; or . or , and when you reorder the expressions you have to change the symbol. I never used it to be sure that how painful is it and if the complain is about a real problem.)

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

@mflatt I don't find it beautiful, but I think it works. I don't mind precedence in general (my C-exprs have them) but it can be confusing to have too many. Different things and I worry as well that if we pick 7 things, then in X times we'll wish we picked 9 or 6. It will really come down to how we tend to write the new forms. I think we'd want ; included on the right and . included at the left.

In your examples, I don't understand if you can tell the difference between different parenthesizations. For example f x , y is (, (f x) y) and f (x, y) is (f (, x y)) and (f (x, y)) is ((f (, x y))? The only one that seems weird is the last one, but I don't know why it isn't that.

You're also not using [] and {}. I assume that we'll support those and they will be different than ()? (and not just with an easy to ignore paren-shape property.)

@gus-massa I agree with you that the extra | is nice. I get hurt by that in Datalog and Haskell and Coq all the time. However, I don't think that is a syntax notation question, but a question about how we should use this notation to write the cond macro. We could have the same concern about the ELisp cond (no parens around question-answer pairs and the tail is the else) versus the Racket cond (brackets around the pairs and the else still gets a question.) --- @mflatt IMHO, the question of what to use : for is like this.

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024

Part of this experiment was imagining a distinction between "statement" grouping an "expression" grouping. I had in mind leaving . as an expression-level operator like + and * and &&. Maybe that's equivalent to saying that all expression operators have higher precedence, but part of what makes the notation work for me is that f(x, y) is parsed as an expression, so I'm not sure it's just an extension of precedence. No opinion on ;.

I had in mind that f(x, y) is a function-call form, and f (x, y) is two separate forms. In other words, whitespace would matter for parsing a function-call form. f x , y is two forms, f and x, y, with no function-call forms in it. Along similar lines, I had in mind parsing [] with no whitespace before as indexing. I had no ideas beyond that for [] and {}.

I went back and forth on whether : or | made more sense for cond. I decided to try the convention of using : to separate a syntax form's name from its parts, but I agree that | looks better for cond and for match after the first expression. Although | looks better for define with multiple function cases, it looked strange to me in define with one case.

from rhombus-prototype.

gus-massa avatar gus-massa commented on May 25, 2024

I like to imagine how the final language would look like. A sane syntax is important but a nice snippet too.

Is it possible to make the first | optional and make the reader magically fix it? For code in a single line the first | is not nice. (And I think it's better not to look at the \n to decide how to put the parenthesis.)

cond : x => 1 | else => 2
cond : | x => 1 | else => 2      // :(
cond : 
  x  => 1      // :(
| else => 2
cond : 
| x  => 1
| else => 2

How do I nest a cond and a match?

cond : x => match : v : "y" => 1 | "z" => 2 | else => 3    // ???
cond : x => (match : v : "y" => 1 | "z" => 2) | else => 3

What about the => numerical operator? (I guess it will not be a problem, because it is already "overloaded" and it is not a problem.)

printf(x => y)

from rhombus-prototype.

gus-massa avatar gus-massa commented on May 25, 2024

What about

define : c(x y inc?) =
  cond : 
  | inc? => x => y
  | else => x <= y

and

define : dec?(x y) =
  cond : 
  | x => y => #t 
  | else => #f 

Is it possible to use ==> instead of => to avoid confusion?

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024

@gus-massa What operation did you have in mind for => on numbers? (I assume not greater-or-equal, normally written >=.)

from rhombus-prototype.

gus-massa avatar gus-massa commented on May 25, 2024

Never mind, I always confuse >= and =>.

from rhombus-prototype.

rocketnia avatar rocketnia commented on May 25, 2024

Still wrestling with my own opinion on grouping-by-reader versus grouping-by-binding. I've claimed that grouping-by-binding may need more tool support, but the more I think about it, I'm not sure what that entails.

Maybe the problem is that I don't currently rely enough on tools. I need parenthesis matching and I use operations that traverse S-expressions and transpose them, but that's about it.

I don't use structure-aware s-expression editing myself, but since a lot of the tools for it are useful across multiple s-expression languages, I'd guess that they just assume they know what things like ( and ) mean, even if the user is using reader macros that mess with those meanings.

On the one hand that's not very hygienic. Some more careful tools might actually pay attention to the readtable entries for things like (, and as a result of their effort, they would have more information with which to provide more relevant assistance. For instance, a tool that did this could look up a user-defined reader macro and discover it had its own user-defined structural editing features -- maybe something #lang 2d would have made use of. (I don't suppose I'm saying anything new, since many #langs themselves are associated with user-defined DrRacket extensions.)

On the other hand, if the file is in an erroneous state, it's probably nice for the structural editing features to keep working.

I think both binding-aware and binding-unaware tools are valuable to explore, and both could even exist for the same language. (The binding-aware tools may even have binding-unaware fallback behaviors.) Nevertheless, I'd bet erroneous files would be common enough, and user-defined tool extensions would be rare enough, that binding-unaware tooling would be more immediately helpful. This suggests "grouping-by-reader" is more immediately helpful than "grouping-by-binding." (Edit: I just want to reiterate my lack of experience with these tools. Are erroneous files really as common as I suspect?)

If "grouping-by-reader" is the primary direction of tool development, then the tools would be most effective if grouping-by-binding were minimized in the language design. Just as with reader macros, it doesn't have to be eliminated entirely, just not used so pervasively and in such an unruly variety of ways that it regularly interferes with the tools.

from rhombus-prototype.

tgbugs avatar tgbugs commented on May 25, 2024

I routinely use a limited subset of paredit functionality and find even the few commands that I do use incredibly empowering. I frequently make use of the functionality in python as well, except that there are an extremely limited number of situations where the basic paredit logic can be applied successfully in python. Paredit is relative straightforward to implement and is robust to 'incorrect structure. The fact that the surface syntax is extremely regular/homogeneous is what makes this possible. I don't see any straight forward way to implement a 'reader only' kind of structured editing when moving away from s-expressions. On possible way out would be a 'reader+' level that can quickly get to a homogeneous representation (p4p seems like the closest to this?).

If every single new language written on top of Racket2 requires the author to implement structured editing support then I would simply give up any hope of being able to use structured editing because it is one more barrier that must be overcome. I think this is the case that needs to be avoided at all cost.

Currently Racket's surface syntax provides the homogeneous structure on top of which structured editing can be implemented universally. If Racket2 moves away from having a homogeneous surface syntax, then being able to get to a homogeneous level in only a few steps would seem to be critical. Another important factor would be the ability for the transformation from surface syntax to homogeneous representation to be able to succeed even when 'incorrect'. Having only skimmed the Honu paper I have no idea whether its approach makes it possible to reach such a homogeneous representation when the surface syntax is incorrect. In addition it would be quite useful if the reader+ translation rule could be applied locally (i.e. without having to inspect and process existing macros etc. the binding-aware case mentioned above I think), having to maintain a separate homogeneous AST to enable structured editing is possible but seems undesirable, especially given the requirement that it be possible to still generate the AST even when the structure is wrong.

from rhombus-prototype.

gus-massa avatar gus-massa commented on May 25, 2024

DrRacket has the default+4 rules for indentation (and 4 rules for square bracket), so in spite all s-expressions are created equal, it is necessary to have a minimal understanding of them to indent the code correctly. Is it possible to remove the necessity to add these ad hoc rules?

from rhombus-prototype.

spdegabrielle avatar spdegabrielle commented on May 25, 2024

@gus-massa you don’t need to understand the indentation rules - just use the DrRacket re-indent function(the standard), or a compliant editor.

I’m hoping we can eventually move to projectional editing with vim-like traversal and code manipulation. I should put that in #96 #96


https://docs.racket-lang.org/style/Textual_Matters.html


6.2 Indentation

DrRacket indents code and it is the only tool that everyone in PLT agrees on. So use DrRacket’s indentation style. Here is what this means.

For every file in the repository, DrRacket’s "indent all" functions leaves the file alone.

If you prefer to use some other editor (emacs, vi/m, etc), program it so that it follows DrRacket’s indentation style

from rhombus-prototype.

gus-massa avatar gus-massa commented on May 25, 2024

I tried to say that in spite parenthesis are enough to delimit the blocks in s-expressions, so the editor ca skip/delete/whatever a block, the parenthesis are not enough to give enough information to the editor to get the correct indentation, so DrRacket has many special rules to have the correct indentation automatically.

from rhombus-prototype.

bzyeung avatar bzyeung commented on May 25, 2024

I agree that we will need more clearly defined goals at some point, but as I understand it, even the goals are still under discussion (and open to community input). I'm working on a document to help with that, in a similar vein to the list of syntax considerations, but from a broader perspective.

I think it would be worthwhile to put together a proposal much like the C-expression proposal for S-expression syntax. It might help better define objectives, wants, and tradeoffs in general.

from rhombus-prototype.

jackfirth avatar jackfirth commented on May 25, 2024

@Nyanraltotlapun See the syntax-considerations.md file. Additionally, see these two paragraphs from the Racket2 possibilities post by Matthew Flatt:

Partly, I believe that a syntax with infix operators, fewer
parentheses, etc., would be better. That's a matter of opinion, but at
least it's an opinion backed by enough experience with parentheses
that it can't be written off as uninformed. More significantly,
parentheses are certainly an obstacle for some potential users of
Racket. Given the fact of that obstacle, it's my opinion that we
should try to remove or reduce the obstacle.

Syntax is not just an obstacle for potential users of Racket as a
programming language, but also for potential users of Racket as a
programming-language programming language. The idea of
language-oriented programming (LOP) doesn't apply only to languages
with parentheses, and we need to demonstrate that. Although we already
have tools for parsing non-parenthesis syntax into parentheses and
letting macros take over from there, that's not the kind of smooth
path from simple extension to full language building that we have in
the S-expression world.

Pull requests to expand upon the syntax-considerations.md file are greatly welcomed.

from rhombus-prototype.

sorawee avatar sorawee commented on May 25, 2024

I agree that }; pattern is ugly. I think that's what Matthew and I meant by "noisy". I think it is actually there because the C-style is not normally functional so it looks odd to have so much nesting. It's definitely a trade-off to get the tree-shape. I think there are a lot of little things that could be done taking a cue from some of the S-expression alternate notations.

For example, we could use a symbol like MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET as a "giant right parenthesis" that closes everything that is open. If that's too absurdly Unicode, then taking a cue from you, a . as the last non-comment character on a line could mean the same thing. I think it would be useful inside of functions, but not really inside of "classes" because you may have more functions afterwards. So the "Rust" example would look like:

This . looks "undelimited" to me. Here's a delimited version:

At the most basic level, we can use end like Pyret.

class big:
  fun foo(x):
    lam(y):
      for map(z from y):
        (z + x)
      end
    end
  end
  fun bar(x):
    lam(y):
      (x + y)
    end
  end
end

Too many ends? Make it possible to annotate what you want to end.

class big:
  fun foo(x):
    lam(y): for map(z from y): (z + x)
  end fun
  fun bar(x):
    lam(y): (x + y)
end class

There could probably be a syntax begin <x>: .... end <x> whose sole purpose is for code organization.

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

The named end is a cute idea. I don't understand how that works with parsing though.

BTW, here are the actual last two lines from a JavaScript program I wrote:

                        loop(res); }); }); }); }); }); }); } };
        loop(1); }); }); }); }

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

@rocketnia Your comments are smart and on point. FWIW, I never actually look at any parens/brackets/etc to determine "large"/control structure. I always rely on indentation. I think of whatever delimiters I put in as something like a tax that I pay to the parser. I really hate the standard JS/C style where {}s are on their own lines. It feels like such a big waste of vertical space and inhibits reading the code. I find that when I write C code, I rarely get very nested though... probably because there are no anonymous functions. Here's the "worst" ("best"?) line from a C program I was working on last night:

        return prev + 1; } } } }

The }s, in order, are an if, an else, a while, then the function.

from rhombus-prototype.

rocketnia avatar rocketnia commented on May 25, 2024

@jeapostrophe

Yeah, I guess I have a related experience. When I switched to JS after using Arc, there were a lot of little things I felt like I just had to put up with because of the language design. One of those was the question of how to indent deeply nested function calls foo(x, bar(y), z), where I contextually switched between a few different indentation styles and kept throwing myself off. If I had to be more meticulous about matching parens as a result of this, I just plugged along anyway, and I blamed the non-s-expression syntax for the bad experience.

You're talking closing parens being practically a non-entity that you only put in to satisfy the parser. Does this mean a whitespace-sensitive syntax would be closer to your ideal choice?

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

@rocketnia Ya, I think indentation-as-syntax has a place in the language. I'm working on another proposal that captures some of my other ideas.

from rhombus-prototype.

s-zeng avatar s-zeng commented on May 25, 2024

I'm really not a fan at all of switching away from s-expressions. A lot of this just seems like change for the sake of change

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

I put an experimental parser in #109 to explore how it feels

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

Alright, I've opened #114 which is the culmination of my ideas so far and incorporates a lot of the things in this thread. I hope you'll like it.

from rhombus-prototype.

mflatt avatar mflatt commented on May 25, 2024

An here's another take: #117 (direct link: https://github.com/mflatt/racket2-rfcs/blob/sapling/text/0005-sapling.md). It was inspired by @jeapostrophe's Lexprs, but different enough that he may disown the connection. :)

from rhombus-prototype.

brandonbloom avatar brandonbloom commented on May 25, 2024

Stumbled across Gel by Falcon and Cook, which seems like another point in the design space worth looking at.

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

Thanks @brandonbloom , I added it

from rhombus-prototype.

sorawee avatar sorawee commented on May 25, 2024

Here's another example of annoying thing about the Lisp style in non S-expression syntax.

Lately I have been writing macros that don't group forms but instead try to flatten them out. E.g.,

(cond*
  <test-1> => <expr-1>
  <test-2> => <expr-2>)

instead of

(cond*
  [<test-1> <expr-1>]
  [<test-2> <expr-2>])

Or:

(tests
  <actual-1> => <expected-1>
  <actual-2> => <expected-2>)

instead of

(tests
  [<actual-1> <expected-1>]
  [<actual-2> <expected-2>])

What I immediately notice is that editing the last statement feels really difficult, and unfortunately, the last statement is the one we usually edit the most. On the other hand, it doesn't feel difficult to edit:

tests {
  <actual-1> is <expected-1>
  <actual-2> is <expected-2>
}

Or:

tests:
  <actual-1> is <expected-1>
  <actual-2> is <expected-2>

The reason is that with parentheses, it is very easy to navigate through code by finding the matching parens. Without parentheses, the only tools you have left is moving by line, word, or character. And the Lisp style (ending delimiter in the same line as the last statement) will get in your way. For instance, you can't just delete the entire line because it will delete the delimiter as well.

So I really do think that there's an inherent conflict between the Lisp style and non S-expression syntax that @jeapostrophe is proposing.

from rhombus-prototype.

rocketnia avatar rocketnia commented on May 25, 2024

@jeapostrophe

Thanks @brandonbloom , I added it

I ran across Gel again today and tried to figure out if it was listed in the repo, but the only mention I found was in these comments. Did some edits not get merged in?

from rhombus-prototype.

jeapostrophe avatar jeapostrophe commented on May 25, 2024

I don't recall. I don't have any unpushed changes on my machine 🤦

from rhombus-prototype.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.