6 ms·
Nimony (Nim 3.0) Design Principles
- deleted 1y ago[deleted]
- mwkaufma 1y agoBig "college freshman" energy in this take: I personally prefer to make the error state part of the objects: Streams can be in an error state, floats can be NaN and integers should be low(int) if they are invalid (low(int) is a pointless value anyway as it has no positive equivalent). It's fine to pick sentinel values for errors in context, but describing 0x80000000 as "pointless" in general with such a weak justification doesn't inspire confidence.
- ratmice 11mo agoWithout the low int the even/odd theorem falls apart for wrap around I've definitely seen algorithms that rely upon that. I would agree, whether error values are in or out of band is pretty context dependent such as whether you answered a homework question wrong, or your dog ate it. One is not a condition that can be graded.
- School-Cotton 11mo agoWhat is the "even/odd theorem" ?
- ratmice 11mo agothat all integers are either even or odd, and that for an even integer that integer + 1 and - 1 are odd and vice versa for odd numbers. That the negative numbers have an additional digit from the positive numbers ensures that low(integer) and high(integer) have different parity. So when you wrap around with overflow or underflow you continue to transition from an even to odd, or odd to even.
- xigoi 11mo agoIf you need wraparound, you should not use signed integers anyway, as that leads to undefined behavior.
- ratmice 11mo agoPresumably since this language isn't C they can define it however they want to, for instance in rust std::i32::MIN.wrapping_sub(1) is a perfectly valid number.
- School-Cotton 11mo agoSigned overflow being UB (while unsigned is defined to wrap) is a quirk of C and C++ specifically, not some fundamental property of computing.
- xigoi 11mo agoNim (the original one, not Nimony) compiles to C, so making basic types work differently from C would involve major performance costs.
- School-Cotton 11mo ago> making basic types work differently from C would involve major performance costs. Not if you compile with optimizations on. This C code: int wrapping_add_ints(int x, int y) { return (int)((unsigned)x + (unsigned)y); } Compiles to this x86-64 assembly (with clang -O2): wrapping_add_ints: lea eax, [rdi + rsi] ret Which, for those who aren't familiar with x86 assembly, is just the normal instruction for adding two numbers with wrapping semantics.
- Mond_ 11mo agoMeh, you also see algorithms that have subtle bugs because the author assumed that for every integer x, -x has the same absolute value and opposite sign. I view both of these as not great. If you strictly want to rely on wraparound behavior, ideally you specify exactly how you're planning to wrap around in the code.
- sevensor 11mo agoI have been burned by sentinel values every time. Give me sum types instead. And while I’m piling on, this example makes no sense to me: proc fib[T: Fibable](a: T): T = if a <= 2: result = 1 else: result = fib(a-1) + fib(a-2) Integer is the only possible type for T in this implementation, so what was the point of defining Fibable?
- Hendrikto 11mo agoI agree about sentinel values. Just return an error value. I think the fib example is actually cool though. Integers are not the only possible domain. Everything that supports <=, +, and - is. Could be int, float, a vector/matrix, or even some weird custom type (providing that Nim has operator overloading, which it seems to). May not make much sense to use anything other than int in this case, but it is just a toy example. I like the idea in general.
- sevensor 11mo agoWell, I agree about Fibable, it’s fine. It’s the actual fib function that doesn’t work for me. T can only be integer, because the base case returns 1 and the function returns T. Therefore it doesn’t work for all Fibables, just for integers.
- cb321 11mo agoIn this case, it compiles & runs fine with floats (if you just delete the type constraint "Fibable") because the string "1" can be implicitly converted into float(1) { or 1.0 or 1f64 or float64(1) or 1'f64 or ..? }. You can think of the "1" and "2" as having an implicit "T(1)", "T(2)" -- which would also resolve your "doesn't work for me" if you prefer the explicitness. You don't have to trust me, either. You can try it with `echo fib(7.0)`. Nim is Choice in many dimensions that other PLang's are insistently monosyllabic/stylistic about - gc or not or what kind, many kinds of spelling, new operator vs. overloaded old one, etc., etc., etc. Some people actually dislike choice because it allows others to choose differently and the ensuing entropy creates cognitive dissonance. Code formatters are maybe a good example of this? They may not phrase opposition as being "against choice" as explicitly as I am framing it, but I think the "My choices only, please!" sentiment is in there if they are self-aware.
- k__ 11mo agoI had the impression, the creator of Nim isn't very fond of academic( solution)s.
- kbd 11mo agoThe biggest thing I still don’t like about Nim is its imports: import std/errorcodes proc p(x: int) {.raises.} = if x < 0: raise ErrorCode.RangeError use x I can’t stand that there’s no direct connection between the thing you import and the names that wind up in your namespace.
- xigoi 11mo agoIt needs to be this way so that UFCS works properly. Imagine if instead of "a,b".split(','), you had to write "a,b".(strutils.split)(',').
- polotics 11mo agook I do not understand. What is preventing this import std/errorcodes from allowing me to use: raise errorcodes.RangeError instead of what Nim has? or even why not even "import std/ErrorCodes" and having the plural in ErrorCodes.RangeError I wouldn't mind
- PMunch 11mo agoNothing, and it fact this works. To move to an example which actually compiles: import math echo fcNormal echo FloatClass.fcNormal echo math.fcNormal echo math.FloatClass.fcNormal All of these ways of identifying the `fcNormal` enum value works, with varying levels of specificity. If instead you do `from math import nil` only the latter two work.
- PMunch 11mo agoThere is a direct connection, you just don't have to bother with typing it. Same as type inference, the types are still there, you just don't have to specify them. If you have a collision in name and declaration then the compiler requires you to specify which version you wanted. And with language inspection tools (like LSP or other editor integration) you can easily figure out where something comes from if you need to. Most of the time though I find it fairly obvious when programming in Nim where something comes from, in your example it's trivial to see that the error code comes from the errorcodes module. Oh, and as someone else pointed out you can also just `from std/errorcodes import nil` and then you _have_ to specify where things come from.
- andyferris 11mo ago> floats can be NaN and integers should be low(int) if they are invalid (low(int) is a pointless value anyway as it has no positive equivalent). I have long thought that we need a NaI (not an integer) value for our signed ints. Ideally, the CPU would have overflow-aware instructions similar to floats that return this value on overflow and cost the same as wrapping addition/multiplication/etc.
- mikepurvis 11mo agoFrom an implementation point of view, it would be similar to NaN; a designated sentinel value that all the arithmetic operations are made aware of and have special rules around producing and consuming.
- fithisux 11mo agoR has it.
- esafak 11mo ago> "Modern" languages try to avoid exceptions by using sum types and pattern matching plus lots of sugar to make this bearable. I personally dislike both exceptions and its emulation via sum types. ... I personally prefer to make the error state part of the objects: Streams can be in an error state, floats can be NaN and integers should be low(int) if they are invalid. Special values like NaN are half-assed sum types. The latter give you compiler guarantees.
- torben-friis 11mo agoI’d like to see their argument for it. I see no help in pushing NaN as a number through a code path corrupting all operations it is part of, and the same is true for the others.
- cb321 11mo agoThere is no direct argument/guidence that I saw for "when to use them", but masked arrays { https://numpy.org/doc/stable/reference/maskedarray.html https://numpy.org/doc/stable/reference/maskedarray.html } (an alternative to sentinels in array processing sub-languages) have been in NumPy (following its antecedents) from its start. I'm guessing you could do a code-search for its imports and find arguments pro & con in various places surrounding that. From memory, I have heard "infecting all downstream" as both "a feature" and "a problem". Experience with numpy programs did lead to sentinels in the https://github.com/c-blake/nio https://github.com/c-blake/nio Nim package, though. Another way to try to investigate popularity here is to see how much code uses signaling NaN vs. quiet NaN and/or arguments pro/con those things / floating point exceptions in general. I imagine all of it comes down to questions of how locally can/should code be forced to confront problems, much like arguments about try/except/catch kinds of exception handling systems vs. other alternatives. In the age of SIMD there can be performance angles to these questions and essentially "batching factors" for error handling that relate to all the other batching factors going on. Today's version of this wiki page also includes a discussion of Integer Nan: https://en.wikipedia.org/wiki/NaN https://en.wikipedia.org/wiki/NaN . It notes that the R language uses the minimal signed value (i.e. 0x80000000) of integers for NA. There is also the whole database NULL question: https://en.wikipedia.org/wiki/Null_(SQL) https://en.wikipedia.org/wiki/Null_(SQL) To be clear, I am not taking some specific position, but I think all these topics inform answers to your question. I think it's something with trade-offs that people have a tendency to over-simplify based on a limited view.
- esafak 11mo agoFrom my interaction with the Nim community, I came to the conclusion that nim could be more popular if its founder devolved decision making to scale up the community. I think he likes it the way it is; small, but his. He is Torvaldsesque in his social interactions.
- xigoi 11mo agoPlease no. Design by committee would lead to another C++.
- almostgotcaught 11mo agoThe second or third most popular language of all time? God forbid lol
- xigoi 11mo agoPopular does not mean good. Tobacco smoking is also popular.
- almostgotcaught 11mo agoDo you think this is clever? For a metaphor to be relevant to a discussion it has to be fitting, not just a dunk.
- xigoi 11mo agoIt’s not a metaphor. I was giving a counterexample to your implied claim that popularity is an indicator of quality.
- kanaffa12345 11mo agoThat wasn't an implied claim because we're not discussing metrics for judging quality.
- 11mo ago
- jcmfernandes 11mo ago>WCET ("worst case execution time") is an important consideration: Operations should take a fixed amount of time and the produced machine code should be predictable. Good luck. Give the avionics guys a call if you solve this at the language level.
- hota_mazi 11mo ago> It is not possible to say which exceptions are possible So repeating the same mistake that Spring made by using runtime exceptions everywhere. Now you can never know how exactly a function can fail, which means you are flying completely blind.
- ninjaquiv 11mo agoDoes Nimony/Nim 3.0 have pattern matching, or any plans for it?
- SJMG 11mo agoYes Design: https://github.com/nim-lang/RFCs/issues/559 https://github.com/nim-lang/RFCs/issues/559 Plan: https://forum.nim-lang.org/t/13357#81170 https://forum.nim-lang.org/t/13357#81170
- matchee 11mo agoInteresting. I wonder how Nim 3/Nimony handles or will handle bindings in patterns regarding copy, move or reference. Rust can change it per binding, and Ada's experimental pattern matching might have some plans or properties regarding that.[1] > By default, identifier patterns bind a variable to a copy of or move from the matched value depending on whether the matched value implements Copy. > This can be changed to bind to a reference by using the ref keyword, or to a mutable reference using ref mut. For example: match a { None => (), Some(value) => (), } match a { None => (), Some(ref value) => (), } . The Github issue had a strange discussion. I really disliked goteguru's equals-sign-based syntax, though I had difficulty judging the main design syntax. I wonder what Araq thinks of Scala's Expression AST type. Tree, TermTree, and all the subtype case classes [2]. Tree has fields. Though I am not certain how the common variables are initialized. [1] https://doc.rust-lang.org/reference/patterns.html#r-patterns.ident.move https://doc.rust-lang.org/reference/patterns.html#r-patterns... [2] https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/ast/Trees.scala https://github.com/scala/scala3/blob/main/compiler/src/dotty...
- kapija 11mo agogreat, now they just need to fix the whitespaces and people will start using it
- xigoi 11mo agoBy modularizing the compiler, it will presumably be easier to create syntactic skins for Nim. I’m planning to make an S-expression syntax if nobody else does.