
Spry: a programming language inspired by Smalltalk/Rebol and written in Nim - vmorgulis
http://sprylang.org/
======
gokr
(author of Spry) Just a few notes:

I like the cleanliness of Lisp, but I have always found it hard to read.
That's one reason for Spry to have a bit more syntactic elements than Lisp.

Spry supports both prefix and keyword style function calls, although as a
Smalltalker by heart I favour keyword style.

Finally, Spry is still an evolving experiment. Next up is the OO model which I
hope will be interesting.

~~~
Chyzwar
1 to: 5 do: [echo :x] in smalltalk translate to: 1 to: 5 do: [:x | Transcript
show: x ]

1(smallInt object) receive message 'to' with parameter 5 (smallInt object)
return numeric range, then you send 'do; message with parameter being a block
that evaluate for each element in range.

Smalltalk is about objects and message passing. Spry only use Smalltalk
syntax.

~~~
gokr
Well, Spry is much more Smalltalk than "only use Smalltalk syntax". But I have
an entire article being written on that subject :)

And no, your description of how it works in Smalltalk is wrong - #to:do: is
implemented in the receiver (1).

In Spry we are calling an infix function named to:do: and the only real
difference to Smalltalk in this case is the fact that the dispatch of to:do:
is not polymorphic. But as I described in other comments I am working on
polymorphic functions as a way to reach similar abstraction levels.

~~~
Chyzwar
The thing is that Smalltalk do not have language constructs like loops or
conditionals ifs/switch. Smalltalk also do not have functions only methods.
There are only objects and messages.

Your infix functions would need to live somewhere probably in global scope
with makes then no different than language constructs.

I would love to see new Smalltalk implementation that is not a Walled Garden.
Making a Smalltalk impure is not a way foward imo. You work is interesting but
what we need is Smalltalk VM on modern back-end like LLVM and better
concurrency.

~~~
gokr
Spry does not have these builtin either, they are all functions. In fact, even
assignment is, which makes Spry even more minimal then Smalltalk in this
sense.

I know Smalltalk VERY well, and Spry is trying to experiment. I think
polymorphic functions can be just as powerful as a class based model, even
more so, and still feel similar. But i need to write examples to show it.

------
dockimbel
Goran, congrats for this release and the nice website! It is interesting to
see where mixing ideas from Red/Rebol and Smalltalk can bring us to. What are
the main pros/cons of using Nim as the underlying language?

~~~
gokr
Hey! Thanks. Not me who put it here on HN though :)

Regarding Nim - IMHO the main big pro is the fact that I can piggy back on the
GC, use several of Nim's features (dynamic dispatch using methods for
example), target all three of C/C++/js. And finally, I can use the very good
C/C++ wrapping capabilities of Nim. As you may know wrapping C++ is ... very
hard, but if you let Nim compile via C++ it works great.

I also find Nim very, very nice to work in.

And yeah, I hope Spry will show some interesting results of mixing Rebol/Red
ideas with Smalltalk. And I do need to study Rebol/Red more. ;)

~~~
gokr
Also, Nim has very promising support for threads, threadpools, async stuff etc
etc. And when I rewrite the Spry interpreter to be stackless I hope to be able
to use a lot of those things too.

------
apricot13
For a moment I thought dreamweaver was making a comeback!
[https://en.wikipedia.org/wiki/Spry_framework](https://en.wikipedia.org/wiki/Spry_framework)

------
desireco42
This looks super interesting to me :) something I really thought we do need. I
will be looking into how Spry develops in the future.

------
xaduha
I think I'll stick with Red.

~~~
adamnemecek
What are you using it for if I may ask? And how are you finding it?

~~~
xaduha
It's not in a workhorse languages category yet, but in a hobby languages,
alongside Perl 6. I'm interested in Parse dialect, seems very powerful. Wasn't
a user of Rebol previously, but I like that Red mostly reimplements it,
without straying to the sides.

~~~
gokr
And yeah, Parse is very interesting. I would like to do something similar in
Spry but perhaps borrowing more from PetitParser (a very nice parser
combinator library in Smalltalk from Lukas Renggli)

------
nikolay
"funci", really?

~~~
gokr
funci for infix. func for prefix. May end up changing that.

~~~
nikolay
It's a terrible choice!

~~~
gokr
But hardly important :) I may actually end up not needing the distinction, but
if so, then I think a good convention on documenting funcs is needed. Well,
needed anyway. :)

~~~
nikolay
I want my programing language to be as close to natural English as possible.
Just voicing an opinon. There's a niche market for as cryptic languages as
possible, too!

~~~
sebastianconcpt
Just curious, are you working in one? link?

------
chdjdjdnc
I really, really like the concept, but I strongly dislike the syntax.
Excessive punctuation is the enemy of code readability.

Someone need to make lisp without the parentheses, where scoping can also be
managed by indentation like in python. Throw in some strong imperative
programming so that it's a lisp masquerading as C (because purism sucks), and
you'd have one very powerful language

~~~
riffraff
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?

~~~
pmontra
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?

~~~
junke

       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?

~~~
pmontra
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.

~~~
gokr
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.

~~~
junke
I tried to find an example of the macro-like approach of Spry and couldn't.
How is it expressed?

~~~
gokr
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.

~~~
junke
In case you want to work on that, be sure to define how quote and eval work
with respect to the lexical environment.

