I have a general fp/dynamic language background and have been playing with nim for a few weeks. I am lured mainly by its performance, so far it's quite fine in this aspect. I tried to build some restful API with httpbeast and got a really good benchmark result, fasthttp level good. I'd say that's very impressive.
However, from a design perspective, nim is full of problems, maybe not golang level bad, but still not pleasant. After some trial and failure, I decided to write this post. Apologize in advance if I have or will offend any nim developer. And don't get me wrong, nim do have some cool feature like UFC and hygienic macro, but they're mixed with many bad design.
First, some major problems:
proc foo(t: Table[int, int]) = echo 123
foo(initTable())
nor does this:
proc bar(t: proc(i: int): int) = echo t(2)
t(proc(i) = i * 2))
2nd example can be fixed with sugar module but that's still quite limiting. I can't even write an async arrow function.
4. Have a subrange type that is broken, but no literal type. So following compiles
type iX = range[1..5]
let a: iX = -1
while this doesn't
let a = case 1
of 1: 2
and some minor problems:
Have a subrange type that is broken, but no literal type. So following compiles
That literally doesn't compile. https://play.nim-lang.org/#ix=2d9m
choose defer over try with resource/with.
defer is documented as sugar for wrapping everything in a try/finally, meaning you can easily do this with a template.
template with(f, body) =
try:
body
finally:
close(f)
let file = open("file.txt")
with file:
echo file.readAll
No pattern matching. Also this 'object variant' is quite noisy compared to ML way of tagged union.
https://github.com/nim-lang/Nim/wiki/Common-Criticisms#sum-types-are-weird
There is also a macro library for this called patty.
I personally don't use the functional aspect of Nim that much so I can't say anything about those. As far as I know Nim is meant to have a small core language with macros and templates to solve most problems.
I mean this does compile, seems like I'm a bit too emotional last morning
type iX = range[1..5]
var a: iX = 3
let b = 10
a = b
others are minor points which I don't think as nearly as as bad, they just seems weird. I know patty and it's still under construction and lack fundamental feature like nested pattern matching.
Choose defer even as syntax sugar is still bad taste, and I believe use macro to fix everything is an awful idea. It slows compile down, it's hard for editor to correctly support, and what's most important, core language should provide a mature solution or you will get a few flavors of solution in community and they often happen to be mutually exclusive. I can't believe I need to argue against this in year 2020.
Maybe with all that knowledge you can help improve things, send PR to whatever you think you can improve!.
:D
I also use https://github.com/alehander92/gara from time to time. Object variants take awhile to get used to but it's far from bad.
I think type inference is fine in Nim. It might not be something like in F#/OCaml but I find that even in those languages people use type inference sparingly. You also say: and I believe use macro to fix everything is an awful idea. Then Nim's view of doing this and yours just don't align, I think that's OK. OCaml might be a better fit in the things you're looking for(FP first/ great performance).
just a genuine question
Not null safe by default
How compiler know whether the data is nil or not if the data for example taken from IO? Or is it adding the additional if thedata == nil in the code? But the checking still done in run-time right?
Or is it just limited to data/object construction?
The compiler enforces code paths to not have nil``s (ie. forcing ``ref to be initialized). In the case where you use an FFI API that might return nil, you have to add the checks in yourself, and the compiler will do analysis to determine if it's enough.
Currently Nim has not nil annotations but it doesn't infer the programmer's nil checks well, which makes it rather useless. alehander92 has worked on improving this: https://github.com/nim-lang/Nim/pull/12401, but as of now it hasn't progressed further.
Yeah, I wrote quite some OCaml and want to find something new, seems like nim just isn't for me.
Also OCaml is slow and multicore support is still under construction.
@Somerandomguy
I mean this does compile, seems like I'm a bit too emotional last morning
type iX = range[1..5]
var a: iX = 3
let b = 10 # will give RT error
# const b = 4 # will work
# const b = 10 # will give CT error
a = b
not-a-bug. it will correctly throw a Error: unhandled exception: value out of range: 10 [RangeError] at runtime. b is know at RT, not CT. Nim doesn't try to convert non-const vars into const-vars.
The compiler could potentially support some kind of analysis do give CT error in this case, but it's not very useful since you can just do const b = ... instead of let b = ..., at least in this case.
Choose defer even as syntax sugar is still bad taste
IMO defer is as simple as it gets but what would be your ideal syntax (ported to nim, even if not valid nim)? (honest question)
I believe use macro to fix everything is an awful idea.
not "everything" but certainly many things; that's why we have {.magic.} when compiler support is needed, or vmops when CTFE is running into limitations. When you gain more experience with them and start realizing the range of applications is enables in pure user code without custom compiler support, you hopefully would gain more appreciation for them. To name just 3:
It slows compile down
you would need to backup this claim to be convincing. There are many things that could improve the (already good) compile times (eg incremental compilation) but I doubt this is one of them.
it's hard for editor to correctly support
not sure what you mean, they work fine in IDE's I use eg sublime text + others. For debugging what macros produce, treeRepr and repr are your friends (and even nim --expandMacro); I don't see how editors could help here given that what macros expand to depend on their inputs
It's known at compile time as int32
int, not int32
It's known at compile time as int32, not iX, while nim compiler totally ignores this and assume they are compatible. But they aren't. [...] the more sane option is not provide a subrange type in the first place.
again, not-a-bug. 2.int (and all the others) convert to range[1..5], and a range-check is performed at runtime. There are few implicit conversions in nim (unlike C, C++, D etc), but this one makes sense and is convenient, it avoids having a bunch of boilerplate conversions.
type iX = range[1..5]
var a: iX = 3
let b = 3
a = b
a = 4
a = 2.int
doAssert type(a) is iX
doAssert type(a) is int
doAssert int isnot iX
proc fun(a: iX) = discard
fun(3)
fun(int(3))
in any case, this is really a very minor issue; there are other fair criticisms one could make about nim but this ain't one of them. I know it's trivial, but it's still nice to have, and I'm against overuse marco.
Now support the else branch too.
In the ideal world,this should compile
var b = 10
var a: iX = 5
if b >= 1 and b<=5:
a = b
and if you remove that if it doesn't. Of course it is hard(it's called subtraction type or something)
Otherwise it is still implicit downcast and it's only "right" when you lower you standard to c level.
It is not a macro.
:)
type iX = range[1..5]
var a: iX = 3
var b = 10
a = b
When you compile this with -d:release -d:danger it doesn't throw any error. In the generated C file, type iX doesn't exist at all
$ cat example.nim
type iX = range[1..5]
var a: iX = 3
var b = 10
a = b
$ nim c -r -d:release -d:danger --rangeChecks:on example.nim
Hint: 8022 LOC; 0.029 sec; 11.656MiB peakmem; Dangerous Release build; proj: Hint: /home/juan/code/temp1/example [Exec]
fatal.nim(64) sysFatal
Error: unhandled exception: value out of range: 10 [RangeError]
Yap, compiling with -d:release only adds range checker helper function
static N_INLINE(NI, chckRange)(NI i, NI a, NI b)
This is a design decision, should the codegen generate range checker with -d:danger, not my call :P
Regarding with and else in Python:
$ cat with_else.py
with open("/etc/passwd") as fobj:
pass
else:
pass
$ python3.8 with_else.py
File "with_else.py", line 3
else:
^
SyntaxError: invalid syntax
At least in the newest stable version it doesn't work. Maybe you confused it with else with for and while?
Anyway, although I see the usefulness of else for loops, I'm not a fan of it. ;-)