Hacker News new | ask | show | jobs
by arc776 1605 days ago
> So why can't Nim infer from `let b: uint = a`

It "can", but it's a design decision not to by default because mixing `uint` and `int` is usually a bad idea.

This is telling the compiler you want to add an `int` that represents (say) 63 bits of data with a +/- sign bit to a `uint` that doesn't have a sign bit. If `a = -1` then `b = uint(a)` leaves `b == 18446744073709551615`. Is that expected? Is it a bad idea? Yes. So, the explicit casting is "getting in your way" deliberately so you don't make these mistakes. If `a` is a `uint`, it can't be set to `-1`, and adding them is freely allowed.

Incidentally `uint` shouldn't be used for other reasons too, for instance unsigned integers wrap around on overflow, whereas integers raise overflow errors. The freedom of mixing types like this are why languages like C have so many footguns.

In short, explicit is better than implicit when data semantics are different. When the semantics are the same, like with two `int` values, there's no need to do this extra step.

You could create a converter to automatically convert between these types, but you should know what you're doing; the compiler is trying to save you from surprises. For `int`/`float`, there is the lenientops module: https://nim-lang.org/docs/lenientops.html. This has to be deliberately imported so you're making a conscious choice to allow mixing these types.

> don't you get tired of typing (and reading) `uint` twice in the latter setting?

Well, no because I wouldn't be writing this code. This example is purely to show how the typing system lets you write pythonesque code with inferred typing for sensible things, and ensures you're explicit for less sensible things.

For just `int`, there's no need to coerce types:

    var
      a = 1
      b = a + 2
      intro = "My name is "
      name = "Foo"
      greeting = ""

    b *= 10

    # Error: type mismatch: can't concatenate a string with the `b` int.
    # greeting = intro & name & " and I am " & b & " years old"

    # The `$` operator converts the `b` int to a string.
    greeting = intro & name & " and I am " & $b & " years old"

    # If we wanted, we could allow this with a proc:
    proc `&`(s: string, b: int): string = s & $b

    # Now this works.
    greeting = intro & name & " and I am " & b & " years old"

    echo greeting # "My name is Foo and I am 30 years old"

    # Normally, however, we'd probably be using the built in strformat.
    # Incidentally, this is similar to the printf macro mentioned in the article.

    import strformat
    echo &"My name is {name} and I am {b} years old"
1 comments

Okay, int/uint was a bad example; but what about

  let a: int = 1
  let b: float = a
Why wouldn't we want our dream language to infer a coercion here?

That said, Python's behavior (though correct to spec) is arguably worse:

   a: int = 1
   b: float = a 
   print(b, type(b))
   >>> 1 <class 'int'>
With no complaints from mypy.
We don't want to automatically convert between `int` and `float` because there's a loss of information, since floats aren't able to represent integers precisely.

However, we don't need to specify types until the point of conversion:

    let a = 1
    let b = a.float
> Python's behavior (though correct to spec) is arguably worse

Yeah that is not ideal. Looking at the code it seems logical at first glance to expect that `b` would be a `float`. In this case, the type hints are deceptive. Still, it's not as bad as JavaScript which doesn't even have an integer type! Just in case you haven't seen this classic: https://www.destroyallsoftware.com/talks/wat

Another gotcha I hit in Python is the scoping of for loops, e.g.,https://stackoverflow.com/questions/3611760/scoping-in-pytho...

Python takes a very non-obvious position on this from my perspective.

Ultimately, all these things are about the balance of correctness versus productivity.

I don't want to be writing types everywhere when it's "obvious" to me what's going on, yet I want my idea of obvious confirmed by the language. At the other end of the scale I don't want to have to annotate the lifetime of every bit of memory to formally prove some single use script. The vast majority of the time a GC is fine, but there are times I want to manually manage things without it being a huge burden.

Python makes a few choices that seem to be good for productivity but end up making things more complicated as projects grow. For me, being able to redefine variables in the same scope is an example of ease of use at the cost of clarity. Another is having to be careful of not only what you import, but the order you import, as rather than raise an ambiguity error the language just silently overwrites function definitions.

Having said that, as you mention, good development practices defend against these issues. It's not a bad language. Personally, after many years of experience with Nim I can't really think of any technical reason to use Python when I get the same immediate productivity combined with a static type checking and the same performance as Rust and C++ (also no GIL). Plus the language can output to C, C++, ObjC and JavaScript so not only can I use libraries in those languages directly, and use the same language for frontend and backend, but (excluding JS) I get small, self contained executables that are easily distributable - another unfortunate pain point with Python.

For everything else, I can directly use Python from Nim and visa versa with Nimpy: https://github.com/yglukhov/nimpy. This is particularly useful if you have some slow Python code bottlenecking production, since the similar syntax makes it relatively straightforward to port over and use the resultant compiled executable within the larger Python code base.

Perhaps ironically, as it stands the most compelling reason not use Nim isn't technical: it's that it's not a well known language yet so it can be a hard sell to employers who want a) to hire developers with experience from a large pool, and b) want to know that a language is well supported and tested. Luckily, it's fairly quick to onboard people thanks to the familiar syntax, and the multiple compile targets make it able to utilise the C/C++/Python ecosystems natively. Arguably the smaller community means companies can have more influence and steer language development. Still this is, in my experience, a not insignificant issue, at least for the time being.