7 ms·
Prologue: Full-Stack Web Framework Written in Nim
- hombre_fatal 6y agoI played with Nim this weekend. It's a shame that it did so many things right yet went with unqualified imports by default. After being spoiled by other languages, something even Node.js got very right, I just don't want to try to get work done anymore in a language where you go `import Lib` and then a bunch of unqualified identifiers like `foo()` are available in the code. I liked that Ruby feature as a beginner. But it didn't take long for me to realize that knowing where things come from is worth the 500ms one-time-cost you spend qualifying each import.
- oehtXRwMkIs 6y agoThat always bothered me too but never saw any good justification for that. I feel like it's just a straight up flaw in the language, unless someone can tell me why you would ever want unqualified imports.
- elcritch 6y agoIt hasn't bothered me with types and auto-complete combined with unified-call syntax (UCS) after a bit of use. Not sure if UCS and qualified imports would work so well. You can import specific names (similar to Python) like `import posix as ps`. Is that what you mean by qualified imports?
- Varriount 6y agoSee https://news.ycombinator.com/item?id=23452857 https://news.ycombinator.com/item?id=23452857 In short: unqualified imports don't play well with operator overloading, templates, or generics.
- curioussavage 6y agoNothing stopping you from using them in your own code. I prefer qualified too.
- sergiotapia 6y agoI code Nim for fun some weekends and agree that totally sucks, and what's worse it seems like it's idiomatic to write Nim that way. However, for us we can do: from "./crawlers/leetx.nim" import nil leetx.fetchLatest() That forces you to use the qualified version and doesn't contaminate your space.
- Varriount 6y agoNim supports both qualified and unqualified imports[0]. One uses `from module_name import nil`. Then every access to members of that module must be qualified. From my viewpoint (I'm sure there are others) I see two main reasons why unqualified imports are the default: operators and templates/generics. First, operators[1]. In Nim, one can implement an operator for any type, without extending that type, and without even implementing the operator in the same module as the type definition. If qualified imports were the default, then a + b would by default be module_name.`+`(a, b) (The backticks are used to force the plus sign to be recognized as an identifier) Yes, one could hypothetically perform an "unqualified import" for that specific symbol, but what if you have 5+ operators to import (like for a math library)? It would get tedious rather quickly. Now, templates. If you're not familiar with Nim, templates are similar to C templates, except that rather than a template performing textual substitution, it performs syntax tree substitution. As an example, this is how "less than" is implemented: template `>`*(x, y: untyped): untyped = ## "is greater" operator. This is the same as ``y < x``. y < x If qualified imports were the default, templates would be almost useless. Something like this: template toJsonString(value: untyped): untyped = $(toJson(value)) where `$` turns its argument into a string, and toJson turns its argument into a JSON node type, would fail because the template doesn't qualify any of those procedures. The same reasoning for templates also applies to generics (and might be a bit more familiar). A generic doesn't know where its input type comes from ahead of time, so it can't know how to qualify function calls. Python, C#, and other class based languages deal with these complications by using classes as a kind of "automatic unqualified import" signal. Though it doesn't have classes, Go uses a similar mechanism (explicit methods types that must be defined in the same package as the type implementation). Since Nim has neither method types nor classes, it just uses unqualified imports. I agree that unqualified imports are a pain for code navigation, but with the right tooling that navigation can be made easier (I know there's a Nim plugin for VSCode, and I believe there's ones for Vim and Emacs?) [0] https://nim-lang.org/docs/manual.html#modules-from-import-statement https://nim-lang.org/docs/manual.html#modules-from-import-st... [1] https://nim-lang.org/docs/manual.html#syntax https://nim-lang.org/docs/manual.html#syntax [2] https://nim-lang.org/docs/manual.html#templates https://nim-lang.org/docs/manual.html#templates
- hombre_fatal 6y ago
- larme 6y agothere's an article[0] addressing this topic though I'm not fully convinced [0] https://narimiran.github.io/2019/07/01/nim-import.html https://narimiran.github.io/2019/07/01/nim-import.html
- parhamn 6y agoIn short: - You can do python style by doing `from x import nil`, then all imports are package qualified. (Con: style enformcenet) - Ambiguous overrides are compiler errors in Nim (import has an 'except' keyword to omit some), so import overrides isn't as much of an issue. (Con: upstream package can break your compile by introducing the same function name) - Dynamic dispatch works across modules so its nice to just do sqrt() instead of math.sqrt() and complex.sqrt() (Con: this feels meh to begin with). Shrug.
- arc619 6y agoThe tl;dr is that Nim is statically typed so two functions that take different types are not ambiguous. If there is something that takes the same parameters, of the same type, in the same order it wont compile and you would use the module prefix to disambiguate. In over 5 years of coding in Nim this came up once when two libraries defined a `Point` type. They were subsequently given distinct names so i didnt even need it for long. Other options for ambiguous calls are renaming things during import with `import thing as othername` or using `type SdlPoint = sdl2.point` and so on, but i have never needed to do this. The main point is that its a compile time error to try to call an ambiguous thing. Btw mouse over in VSCode shows you where a symbol is defined too.
- planetis 6y agoExactly, this is when this problem pops up. Imo the best way to avoid this is to do: ``import sdl2 except Point`` and use the version of the procs that don't use Point, so you pass your own type as: ``render.drawPoints(p.x, p.y)``
- planetis 6y agoI've been programming exclusively in Nim for 4 years, never had a problem with it. In fact it's a lot easier not to think were to get each symbol both in terms of refactoring and mental overhead. Good editor support means, with a mouse over any symbol, you can see were it was declared. But I don't use nimsuggest, or nimlsp, I simply never have to think about it, in my own codebase. I can understand it can be helpful when working in big projects like the nim compiler itself but I'm talking about goto definition not "qualified" imports, those are useless...
- elcritch 6y agoNote that this won't currently run on embedded devices. The ARC GC release with Nim 1.2.0 could be really useful on embedded processors the Async story hasn't been ported and contains cycles which leak memory. It works great ... until you OutOfMemory and it resets. The Nim core team is working on it. :-) Still IMHO, Nim has a lot of potential on embedded devices. I've been using it to augment some C code I'm using on an ESP32 and it integrates pretty smoothly. It's enjoyable to write Nim code and I can do hacky C-like tricks with pointers if I need to which is great. Writing C isn't to bad until you want to just have some generics and containers like lists and hashes. C++ has them of course, but personally just to much headache for me.
- hliyan 6y agoOnly obliquely related: I was hoping Nim would be the kind of language that preferred libraries over frameworks. We have way too many frameworks and not enough libraries. A library comes with an API. A framework must arrive with its own architecture, a vocabulary to describe that architecture, tooling, a plugin ecosystem, configurations and a lot of best practices and recommendations. Are we paying too much for that initial, one-time effort of bootstrapping an app and stitching a bunch of libraries together into an app?
- sergiotapia 6y agoCute notion, terrible in practice. You end up in a MeteorJS situation where nobody knows what the hell to use, and new users? Good luck. You're toast. This killed MeteorJS. For Nim to succeed, it needs a killer framework like this. It needs to let developers build value for the companies they work for. I really hope Nim does grow tremendously, it's a great language.
- atemerev 6y agoWell, React was pretty successful... While being a library.
- matthiaswh 6y agoI don't know if this is a good example. While React tries to bill itself as a library, and there are arguments to be made for it being a library, there are equally compelling arguments as to why it is a framework. If anything, this example does more to prove the distinction is semantic and trivial than anything.
- jgwil2 6y agoThis is correct; React is a framework.
- sergiotapia 6y agoAnother cute notion but React is a framework in practice.
- frankpf 6y agoI've been writing an interpreter with Nim and overall the language is really good. One feature that I'm excited for is DrNim[1]. It's not distributed with the stable version just yet (you have to compile it yourself, but that's very straightforward), but it's supposed to bring refinement types to Nim. These allow you to write things like: proc safeDivide(a: int, b: int): float {.requires b > 0.} = a / b and DrNim will check at compile-time that all code paths lead to `b` being greater than 0, e.g.: var x = stdin.readLine.parseInt safeDivide(1, x) # DrNim will error at compile-time var y = stdin.readLine.parseInt if y > 20: # no error since DrNim (using Z3) can # prove that y is always greater than 0 here safeDivide(1, y) [1]: https://nim-lang.org/docs/drnim.html https://nim-lang.org/docs/drnim.html
- zie 6y agoThis looks amazing, off to read about it now! Thank you!!!
- 411111111111111 6y agoAt first it looks like a guard, but if it's a compile-time guard, it's even better then the original concept! Reminds me of static analysis tools like sonarqube, but even more integrated
- steve_adams_86 6y agoI didn't know that this is called a refinement type! I've been wanting it badly, both in Nim and in Rust. Now I have a way to search for what I'm looking for to learn more. Would it be possible to use this to ensure a string matches a certain pattern? For example, maybe I want the string to look like a phone number or zip code. I know I can use an abstraction to do this and have some level of safety, even without a statically typed language, but I love the idea of implementing that with primitives. As far as I know it isn't really a thing though. I'm assuming a refinement is only really a refinement of a type within the same system, and doesn't allow for special logic like pattern matching. That would be amazing though. I've spent so much of my career working with dynamic languages, it's hard to know exactly which tools are available to me with these kinds of type systems. I love it though. Learning to leverage types has been such a fun shift.
- zesily 6y agolooks good
- danpalmer 6y agoThis looks nice, but I'd question what about this is "full stack"? While full stack frameworks are unlikely to actually build at every layer of the stack, I'd expect them to be aware of and do something to address each layer. At a minimum I'd expect an ORM of some sort, something around validation of data from the client, bits for building APIs, potentially a structure for building frontend assets, tooling around deployments and maybe migrations. This isn't what a lot of developers want, but it is full-stack. Maybe more things should be happy _not_ being full-stack and owning the idea that they are good at one part of the stack.
- giancarlostoro 6y agoAs a full time Python developer (though it's not my only tool in the toolshet) I've been looking at Nim for a while. I think it looks healthy currently. I'm wanting to give it a shot, and projects like Prologue are what I'm sort of waiting on before I dive in there. There's a few things that concern me, like looking at the tutorial the echo statement is not a function, reminds me of Python before Python 3. Also the pascalCase function for readLine. I think I see why Python mainly uses single word functions for things, to avoid the debate about variable naming conventions as much as possible. Though now the community agrees on underscores mainly. Though this doesn't concern me as much so long as it remains consistent.
- matthiaswh 6y agoThe `echo` statement is indeed, a function. The tutorial is taking advantage of Nim's optional parenthesis when calling the function. echo "Can you hear me?" is the same as echo("Can you hear me?") Nim also has somewhat unique rules about casing and underscores for variables and names. `readLine` is the same as `read_line` is the same as `rEadLInE`. However, the first letter must be the same (there are reasons for this), so `Readline` is not the same as all of those. The idea is that you can choose your preference and not have to think about what anyone else chooses. If a library uses pascalCase and your prefer snake_case then you can use snake_case. This is a big point of contention for many people, but in practice, in my experience, it is never an issue.
- 384028345 6y agoecho IS a function. What might be confusing to you is that in Nim all three following variations are the same echo("foo") echo "foo" "foo".echo If you're curious about nim, I can highly recommend "Nim in Action".
- alberth 6y agoI’m surprised no one has mentioned Jester. It’s the web framework powering the official forum site for NIM. https://github.com/dom96/jester https://github.com/dom96/jester https://forum.nim-lang.org/ https://forum.nim-lang.org/