I just wanted to say "Yay!" to Araq and the other major contributors to this project.
I have recently spent some time replacing Lua with Nimrod as the scripting language for my experimental game framework. I find the language beautiful and powerful.
My favourite feature of Nimrod is its natural, consistent syntax and the way powerful generics and overloading provide a means for me to hide the complexities of an implementation from the library consumer without sacrificing efficiency.
I have been keeping an eye on Rust and have been using Go every opportunity I get, but I have never been able to fully commit to any of them. Now my hope is that Nimrod will at the very least be the blueprint for the next, great systems language.
Nimrod is still developing, and there are of course issues. Mainly I am concerned with some oddities of the syntax (from the top of my mind: publishing symbols using asterisks, dereferencing refs using brackets, a bit excessive reliance on pragmas) but I trust these things will get worked out on the road to 1.0. There is a very beautiful, consistent language at its core with powerful abstractions that strike a fine balance between the heart-warming frugality of C and the expressive madness of C++.
Thank you and best wishes for the future!
Thank you, these kind words actually help to keep the motivation up! :-)
The publishing by using asterisks keeps coming up and yet I challenge you to come up with a better syntax. Hint: These are not better, in my opinion:
export proc foo(a, b: int) # too verbose, not enough emphasis on 'foo'
proc Foo(a, b: int) # uppercase as in Golang requires a *rename* just to export a symbol
proc foo(a, b: int) export # weird, why not in the pragma section then?
proc foo+(a, b: int) # how is + better than *?
export # Haskell styled 'export' gets tedious, have to jump to the export list to add 'foo'
bar, baz, foo
Good! When I get into it I'll try to help a bit with the code too. I don't know much about compiler design, but maybe I can do some good in the library-department.
I humbly accept your export-syntax-challenge:
I agree with your points. I can never quite decide whether the capitalization as publication syntax of Go is brilliantly simple or just plain annoying, but it was definitely a major paper-cut getting into the language.
There are two problems with the export-asterisk, the way I see it:
This is inexact matter and you may of course disagree with any or all of the above, and there is little I can do about that.
If you prefer a typographic solution for its compactness, I would have preferred a different character. Preferably one with less C baggage. Consider the exclamation mark, then. It's the C not-operator all right, but it is never used in conjunction with declarations. It has a history with ruby where it is allowed as part of a symbol name and conventionally used to mark self mutating methods. Using it as an export qualifier makes more sense than either of those, in my cocky opinion. We could "shout" the symbols into the public.
proc foo!(a, b : int)
This addresses my first concern, but obviously not my second.
Modules might get quite complex. For the implementer/reader of the actual module code it makes sense to group definitions together in some logical manner probably grouped by the concepts or classes that each group of declarations define/implements. But when I am in the api-consumer state of mind I need a map of the public aspects of the module. I think an export-section is worth the hassle, especially with Nimrod-style declaration blocks which would allow comments interspersed with my symbol lists:
# A comment describing the purpose of the module
export
# A comment outlining the major types of this module
type TNode, PNode, TSprite, PSprite
# A comment motivating a section of the api
proc foo(a : PNode, b : int)
proc bar(a : PNode, c : int)
Specifying the parameters of the procs, templates and methods might get tedious, so maybe the asterisk could be reclaimed as a wildcard meaning "export all symbols of this name regardless of signature":
export
proc foo(*)
Or perhaps better yet, just omit the qualifier and signature to export all matching symbols:
export
foo
For simple modules with a natural "public" section of symbols, you could just combine the definition/implementation and the export like this:
export
type
TNode = object of TObject
id : int
proc foo(a : PNode, b : int) =
... code ...
Additionally "export" could be allowed as a prefix to allow inline exports (i.e. "export proc" and "export type").
An export-section would also allow a semantic feature that I wish for: In Node.js it is common to split a module implementation over several files and then present the consumer interface through an "index" module. That means there are several public symbols in the respective submodules, but you get to help point the library consumer towards the public symbols that are relevant from her perspective. I would love to be able to re-export symbols selectively from my sub-modules in such an exports section:
import window, widget, submodule3
export
window.createWindow, widget.createWidget
...
Okay, this is my 7am Nimrod-novice stab at your challenge. Thank you for your attention.
maybe put the '*' next to the proc?
compare:
proc* foo(x: int, y: string)
to
proc foo*(x: int, y: string)
The interesting part of this line is the foo(...). Taking the '*' elsewhere removes the emphasis from it.
Consider the exclamation mark, then. It's the C not-operator all right, but it is never used in conjunction with declarations. It has a history with ruby where it is allowed as part of a symbol name and conventionally used to mark self mutating methods.
IMHO Ruby's history with ! is enough to disqualify the ! at the end, especially since it is a useful convention which could be interesting to have also in Nimrod..
proc Foo(a, b: int) # uppercase as in Golang requires a rename just to export a symbol
If you don't have tools to do renaming in your code, you're doing it wrong(!), so it isn't such a bad idea..
If you don't have tools to do renaming in your code, you're doing it wrong(!), so it isn't such a bad idea..
I don't think most editors currently support foolproof renaming in nimrod code. Besides, how can the tool know whether or not it should replace inside strings and comments? Renaming is not likely to ever be a completely-automatic procedure.
renoX: > could be interesting to have also in Nimrod..
simenss:
The user of the module needs to see signatures, and rarely needs to see implementations. On the contrary, signatures may be optional in the implementation (only if there's no overloadings).
And then I think more handy should be to split modules into two parts (interface, implementation) by keywords without indentation, with only declarations allowed in interface section; this is used in some modules of the compiler now, with #implementation comment instead of keyword, e.g. compiler/astalgo.nim; they are very easy to read.
LeuGim:
Then the same should relate to other constructs: template, type, var, ..., not only proc.
Yes. Not sure how that might work with macros that produce new declarations - one would want to support exported and non-exported decls with the same macro.
import window, widget, submodule3
export
window.createWindow, widget.createWidget
...
"In fact the pragma syntax can be changed to the simpler {} almost everywhere but when I try this syntax in practice, I find it uglier than {. .}. We could easily support both. If enough people request this feature, it will be added. "
+1 yes please!
Ultimately I much prefer my original (hypothetical future) suggestion because it avoids having to type those hard-to-reach keys so often, it folds well, is consistent without reused symbols, and isn't cryptic.
I'm sorry but your proposal is the ugliest proposal that I've seen since quite a while ;-) :
Some things in Nimrod are verbose (discard, result, object of T, etc) while others are somewhat cryptic (a, str & str, ui +% ui, etc). This isn't really a problem, but I would always cast a vote in favor of consistency and verbose-over-cryptic.*
I find the underlying implicit assumption that only math is allowed to have short cute abstract symbols for everything disgusting. It's high time common operations in programming get their own deserved shortcuts. IMO that is really part of the whole DSL philosophy, the vocabulary should be optimized for the domain.
You confuse has type with is type (colon vs. equals).
In that case procs currently don't make sense. proc foo: int = bar --> foo has type int and is type bar? I remember talking a little on the IRC about this, and bringing up the same points. At some point a rigid definition for colon and equals ops need to be bent slightly. Currently proc/type's use of them are very inconsistent.
You don't make the variable's type public, but the variable itself, yet your syntax suggests otherwise. For these reasons it's much more inconsistent and cryptic than the current syntax...
Fair point. But that's easily fixed. Just move the pragma on the other side of the colon, eg:
var foo pub: int = 10
proc bar pub: int = foo
type baz pub: bax =
a pub, b pack: int
# or more complex example..
proc foo(a, b:int)
package
header("...")
importc("...")
: int =
# body..
I find the underlying implicit assumption that only math is allowed to have short cute abstract symbols for everything disgusting. It's high time common operations in programming get their own deserved shortcuts. IMO that is really part of the whole DSL philosophy, the vocabulary should be optimized for the domain.
I actually agree to some extent. For instance strings and lists should make use of '+' for concatenation, IMO. But, as I always bring up, math is taught to us from a very early age while other programming idioms are not (and they differ from language to language). Furthermore, we have the ability to alias things as we see fit, but usually disagree on exactly how those things should be aliased. So to me it makes sense to use one clear name in the standard and let people create/share their own convenience operator alias libs.
In that case procs currently don't make sense
Fair point. But it's hard to come up with a better syntax, IMO. The alternative syntaxes all look worse to me.
Hmm.. here's an idea for consistency. Replace ':' with 'of' and '=' with ':'. eg:
type Foo: ...
proc bar: ...
type Baz of Foo: ...
proc bax of Baz: ...
proc bar*(a, b:int) of int: a + b
# ---
type Vec{.pure.} of tuple[x, y: float] # identical AST as..
type Vec{.pure.} of tuple: x, y: float # ..this
# so objects could look like..
type Foo: ... # non-inheritable ?
type Foo of ref: ... # still non-inheritable ?
type Foo of object[...] # inheritable, and same as
type Foo of object: ... # .. this
# ---
type Foo:
a: int
proc bar of Foo: ... # same as..
proc bar of object[a:int]: ... # .. this ?
# ---
# 'of' instead of '->'
sort(cities) do (x,y: string) of int:
cmp(x.len, y.len)
That makes thing more consistent for type, proc, if/else/etc, (as well as user-defined macros which consume a stmt) but not var/let/const statements. So you could either leave those alone and still use ':' (since they're somewhat fundamentally different than defining type/proc it probably wouldn't feel out of place). Or you could just us 'of' for vars as well, eg:
let foo of float = 10
var bar of cstring = "..."
# which actually looks a bit like these..
for i in 0..10: ...
if n.kind in { ... }: ...
..which, if you think about it kinda looks like for i in 0..10: code blocks, so it probably wouldn't feel out of place.
Also, this probably wouldn't break the AST (much), mostly parser changes, but would still be a huge amount of work for sure (and break everyone's code, including the compilers). So yeah.. speaking of bikeshedding :P Still, it's food for thought.