Hacker News new | ask | show | jobs
by riffraff 3648 days ago
I think the problem is more the example than the syntax, it basically has keyword arguments (foo: bar), square brackets for lambda ([echo :x]) and :foo for block arguments.

I.e. this

    1 to: 5 do: [echo :x]
is pseudopython's

    loop(from=1, to=5, do=lambda x: echo x)
    loop(1, 5, lambda x: echo x)

which have comparable punctuation, mostly trading "," for ":" .

Being smalltalkish, you also get extra characters when a block is used for control structures, but that saves on the need to have two different syntaxes for single and multi-line/expression lambdas or N syntaxes for blocks and control structures.

How do you see that handled in your ideal language?

2 comments

Ruby, another language in the Smalltalk lineage, did without the [ ] at the cost of the "end" keyword to terminate blocks, but there is also the equivalent {} notation, recommended for one liners.

A Python indented Ruby would probably able to do without [ ] but I should think about whether there are some unresolvable ambiguities with that approach. I won't recommend that personally because syntactical spaces introduce bugs. I run into one a few days ago when I moved code around a file and forgot to fix the indentation of a few lines. Luckily this one cost me only five minutes. Many people like Python though.

Overall I like the terseness of

    1 to: 5 do: [echo :x]
instead of the more verbose

    loop(1, 5, lambda x: echo x)  # Python
    (1..5).each do { |x| echo x } # Ruby
    (1..5).each do |x|            # Ruby again
      echo x
    end
But all those : are annoying: they increase the noise/signal ratio. I'd even do without the | | in the multi line Ruby version. I wonder if the compiler could add the : automatically by matching what it sees with the signatures of the defined function.

    1 to 5 do [echo :x]
Then, why you need :x and couldn't use x without the : ? Furthermore, how do I know how the name of the block argument if I didn't write the function. Isn't that an unnecessary coupling between the name chosen by the developer of the library and my code? I probably didn't understand everything is going on here. Edit: it has been explained in another comment in the parent's thread.

And there are so many commonly used languages that use { } to define code blocks and [ ] for arrays. I'd stick with the majority for an easier onboarding of developers.

The possibility of defining pseudo keywords thanks to the infix/suffix arguments is great. That's probably anathema in the Python world (only one way of doing things) and also in Go's. It should be acceptable in Ruby's.

I'd prefer something like this

   func (n)to(m)do(blk) {
     x = n
     ...
     do blk x
     ...
   }
The order of arguments and function names is clear because you read them from left to right without going through two different lists.

Is anybody using Spry for real world programs?

   func (n)to(m)do(blk) {
     x = n
     ...
     do blk x
     ...
   }
This looks ambiguous. Your function does not have a name, how do you do if another library wants to do something different with the same keywords? And how do you know when arguments should be evaluated? In your case, "blk" seems to be a set of statements to be executed when "do" is applied, is it right? shall we scan the body to detect if we use "do" on our arguments or does "do" in the signature have a special meaning?
I was describing a syntax I'd like starting from the idea that the compiler should bend to developers and not viceversa.

Let's see if we can make it work.

func (n)to(m)do(blk) could be syntactic sugar for to:do: funci (n, m, blk). I saw funci in the examples, I didn't investigate if there are other type of function definitions. However, if the definition starts with (arg) it should be easy to map it into a funci. This either solves the problem of name clashes with other libraries, or the problem is unsolved right now.

The block passing is more serious. Maybe we could mark blk in such a way we know it's a block. Ruby uses & as in

    func (n)to(m)do(&blk)
Maybe Spry uses & in an incompatible way and I don't like it much anyway. It's developers bending to the compiler, but let's be realistic: we don't want slow compilers.

What Ruby also does is having a yield keyword that calls an anonymous block passed at the invocation point of the function (a method in Ruby's world). The block is not declared as argument of the function/method. Maybe:

    func (n)to(m)do() { # an empty arg is a block
     x = n
     ...
     do x
     ...
    }

    1 to 5 do { echo x }
But I think this is getting far away from the way of Spry.
I am sympathetic to the idea that compilers should bend for developers and that's partly why I love Lisp. Your new syntax has to be learnt by the compiler and that does not scale necessarily well. The regularity of Lisp's syntax is what helps writing code that writes code. Messing up with the readtable, you could obtain something like this:

    {1 to 5 (echo x)}
... which would read into:

    (loop for x from 1 upto 5 do (echo x))
Note that binding `x` implicitly is bad style, as well as defining terse syntax for every construct.
I should mention that Spry uses all of () []{}. The () are reified as Paren as in Rebol which makes it useful in templating etc. {} are used to create Maps, like {x = 1} creates a Map with one kv pair.
I tried to find an example of the macro-like approach of Spry and couldn't. How is it expressed?
It's not as evolved yet - but funcs pull in arguments via :x and can instead use :$x (I used ^ earlier, but switched to $) which will pull in that AST node (argument) without evaluating it first at the call site.

So it's a kind of quoting. I haven't pushed these things further yet, and frankly I am not that heavily into macros unless they are really needed. But obviously AST manipulation is easy in Spry.

You could also use mixfix notation for this:

    _to_do_
      n to m do f =
        x := n
        ...
No, Spry is not at that point yet. Or at least I sure hope noone is! :)

What you describe (interesting syntax) is a way to declare argument names and Spry doesn't declare them. That's a discussion in itself of course, but right now it doesn't.

Yes, correct. It's worth noting that echo is a prefix function so Spry supports both infix (first argument is on the left) and prefix functions, and if there is a series of keywords the parser will rewrite the keyword call to a prefix/infix call like this:

array at: 1 put: 2 ==> array at:put: 1 2

Thus a keyword call is purely syntactic sugar.

And also, the block [echo :x] doesn't use declarations of parameters - instead the ":x" is an operation that pulls in the next argument from the call site and stores it into the local binding x in the closure. Almost Forth-ish, but there is no stack.