Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I read the Nim manual a while ago (http://nim-lang.org/manual.html), back when it was Nimrod.

As a Python user, I loved it. Every single problem I had with Python, Nim seemed to have solved elegantly. Performance, distribution, typing... Everything looked perfect, and none of the Python expressiveness seemed to have been sacrificed.

But it was transpiled to C, and the abstraction was leaky. Every once in a while the manual would mention that certain structures were not supported, and it was clear C was the culprit. I think the most glaring example were nested functions, or something similar.

I thought to myself "this will bite me in the ass sooner or later" and gave up. Maybe it's time to try again. If they plugged the abstraction holes, this will be a killer language, with applications everywhere.



Nim isn't compiled to C in the same way that, say, Coffeescript is compiled to Javascript.

Nim's compiler converts the AST into an intermediate representation that can be compiled to several backend. The primary backend is C source code, but it also supports outputting Javascript (experimentally) or interpreting the intermediate representation ala Python.

The difficulty of developing the IR -> C transformation certainly influences the features of the language, and certain features, like tail calls, can't be implemented because Nim expresses functions as C functions for the benefit of foreign code. I wouldn't say it's a leaky abstraction though, not any more than C is a leaky abstraction over machine code.


Your example isn't exactly true. Clang and GCC both do tail call optimizations. Compiling language functions to C functions doesn't 100% preclude you from TCO. http://david.wragg.org/blog/2014/02/c-tail-calls-1.html


See also: http://www.pipeline.com/~hbaker1/CheneyMTA.html

It looks like work/research in this area has been going on for at least ~20 years.


Indeed it has. For a few more examples, see also http://www.ustream.tv/recorded/43777177, and http://www.ccs.neu.edu/racket/pubs/stackhack4.html. Pyret (http://pyret.org) uses similar stack techniques to these to simulate an arbitrarily deep stack while compiling to JS.


It will, otherwise semantics will depend on which C compiler is available and ANSI C doesn't require TCO.


Tail calls are pretty easy to implement in a language that compiles to C, even if the C compiler doesn't cooperate. This can be done without bothering the foreign code. For instance:

int f(int a, int b){return f(b,a);}

compiles into

int f(int a, int b){start: int t=a; a=b; b=tmp; goto start;}


It doesn't make sense that a language that compiles to C can't support nested functions, just because C doesn't.

All the transpiler has to do is invent a globally unique name for each function, for instance. Of course they might not have implemented it yet, but there shouldn't be any firm reason why it can't be done.


> All the transpiler has to do is invent a globally unique name for each function, for instance.

I would expect nested functions to close over their context. Still implementable in C of course (or ghc -fvia-c wouldn't work), but not as trivially.


Procedures can appear at the top level in a module as well as inside other scopes, in which case they are called nested procs. A nested proc can access local variables from its enclosing scope and if it does so it becomes a closure.

From the manual: http://nim-lang.org/manual.html#closures


Nested functions like this?:

  proc a =
    proc b =
      proc c =
        proc d = echo "Hello World"
        d()
      c()
    b()
  a()
Works fine.


If (and I haven't been interested enough in Nim to investigate) it is indeed compiled to C and then compiled down to platform specific code, unless they are implementing functions differently, nested functions are not a standard part of C; they are a compiler extension (which GCC supports).

Not necessarily a problem, but unless implemented as something other than nested functions in C, there may be some portability issues. Worth investigating.


As unwind suggested, they just get transformed down to global functions with unique names. Here's the actual C code generated: https://gist.github.com/def-/0fe87bf1d35102c62d3b#file-nest-...


Nice. Not the most compact code I've seen, but it makes sense considering it's the output of a compiler, and can thus take advantage of optimizations from clang/gcc/what_have_you.

EDIT: Actually this raises the question of how it handles closures, since the generated code does not seem to provide for preserving the surrounding environment data. Though I may just need to dive in now that it's catching my interest.



What about variables nested within these functions then? Is scope enforced? i.e. you can reach back to variable aa in function a() from nested d() but can't reach variable dd in d() from a()?


I have no idea how it actually works, but it would be easy enough to implement by having nested functions take a parameter that's a pointer to a structure containing the variables that are in scope in the outer function.

In your example, it could be something like this:

    struct variables_in_a_that_are_visible_in_d {
        int aa;
    };
    
    void d_nested_in_a(struct variables_in_a_that_are_visible_in_d *ascope) {
        // Variable dd, not accessible from a
        int dd = 42;  
    
        // Increment a's aa
        ascope->aa += 1;
    }
    
    void a() {
        int aa = 0;
    
        // Pack up variables for d()
        struct variables_in_a_that_are_visible_in_d ford;
        ford.aa = aa;
    
        // actually call d)
        d_nested_in_a(&ford);
    
        // Unpack after calling d()
        aa = ford.aa;
    
        // Continue with a()
        // ...
    }
    
I'm a little surprised people are so hung up on this. They don't call C "portable assembly language" for nothing. If it can be done compiling to native code or some virtual machine assembly language, it can be done compiling to C.


If you know the stack layout, you don't need to do the pack/unpack. You can define the struct to match the stack layout and pass a pointer into the stack instead.

The struct may have holes that contain variables that aren't used by the nested function, but that doesn't matter. Also, you would have to make sure to flush values from registers onto the stack, and, depending on the ABI, would have to explicitly push arguments passed in registers onto the stack (that would have the struct contain a return address, but that's not a problem, either), if you need them to pass arguments to the nested function.


  proc a =
    var x = 10
    proc b = echo x
    b()
  a()

Comes pretty close, the struct is further up: https://gist.github.com/def-/c496cd42774617fd0271#file-nestc...


Right, you could do that, it was more a question about how the closures work.


I assume so? That would be a static check of the nim code.


Dang, sorry. I skimmed the manual again, but couldn't find the exact feature(s) that triggered my conclusion. Maybe they have been plugged already.

Anyway, I think I'm overdue on reevaluating the language. I can't wait to replace Python/Go/C/Java with this.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: