Nim Apocrypha, Vol. I
21–30 of 59 posts
Re: Nim Apocrypha, Vol. I
#22Earlier quoted context omitted.
https://dlang.org/spec/statement.html#WithStatement D also has withs. Definitely in the "not necessary but nice to have" category of features, particularly when implementing state machines (i.e. statment soup)
State machines can be metaprogrammed: https://github.com/mratsim/Synthesis You describe the events, transitions and even interrupts and this compiles to optimized goto statements with no allocation, no indirect function calls, no switch statement. There are compile-time checks to make sure you don't have state with no transition from and it also produce a visual graph of your state and transitions. It's been tuned to…
wrt to the SMT integration, it's something that I would very much like to try in D, but it's quite difficult to work out exactly where to start (and means bolting a fairly big dependency on the compiler). There also seems to be a big gap in the literature between "practical" verification done "in" the widget being verified (e.g. SystemVerilog code) and more academic work where, effectively, a parallel structure is built which represents the semantics of the widget.
Re: Nim Apocrypha, Vol. I
#23Re: Nim Apocrypha, Vol. I
#24> Unless if you’re one of those NodeJS wielding kids who thinks Electron is a good idea and the web is the ultimate application development platform… Ignorance is bliss! MIC DROP!! Wonderful article. Nim is a great programming language. I am using nim and c these days as my primary languages.
(Saying this as a fan of Nim and lean desktop apps in general, though also someone who really doesn't get web technologies.)
Re: Nim Apocrypha, Vol. I
#25Earlier quoted context omitted.
Citation required. How can Wirth languages (e.g. Pascal, Modula) have "the same capabilities for low level programming", but without the traps, of C derived languages (e.g. C, C++)? The idea suggests itself that you can have one or the other, but not both. Personally from trying it out, I like the speed resulting from the stricter module system. And there might be a little additional protection against bad builds res…
Has answered multiple times, including to your previous comments. - bounds checking enabled by default for arrays and strings - real enumerations that aren't implicit converted into numeric types (fixed in C++11, if one bothers to use enum classes) - enumerations can be used as indexes (C++ can work around this with enum classes and some boilerplate templates) - most data conversions must be done explicitly - ability…
You can make your own explicit bounds checks, or you can use valgrind (which, just like any type system, can understand only simple situations). You can use C++ which actually does allow you to do bounds checking by default, if you're into such things.
> - real enumerations that aren't implicit converted into numeric types (fixed in C++11, if one bothers to use enum classes)
> - enumerations can be used as indexes (C++ can work around this with enum classes and some boilerplate templates)
> - ability to actually define subtypes that are enforced by the type system (C++ offers this, provided one does the required boilerplate class/template code)
These are total non-issues in practice, since treating them as integers is usually what you want (you almost always want to support some light arithmetic on them, you want to store sentinel values sometimes...). Furthermore (as you say) C++ allows you to treat them as separate types.
> most data conversions must be done explicitly
This is the same with C, except for integral types where though you need to turn on warnings to avoid implicit downcasts of integral types with some (most?) compilers. For upcasts, implicit is what you want.
> memory allocation by default doesn't require doing math with data sizes (C++ as well, yet there is too much malloc()/free() pollution in C++ code bases)
That's FUD, if you do memory allocation - of course you need to compute the number of bytes you need.
If you do "new Foo[count]" you're likely doing it wrong anyway, just like if you're calling malloc directly. To equate memory allocation with malloc (or syntax sugar in fancy object languages) is WRONG. malloc() is a stupid default allocator, and you don't know what allocator you're getting if you don't provide your own. Good system performance often requires writing (simple) custom allocators instead of using malloc which has to deal with random allocation sizes, allocation patterns, and multiple threads.
Whether using your own allocator or not, it shouldn't be too much to expect you to write a single line like:
#define ALLOCATE(type, count) ((type *) do_alloc(sizeof (type), (count)))
Or even #define ALLOCATE(ptr, count) (ptr = do_alloc(sizeof *(ptr), (count))
Now tell me, why exactly is "new Foo[count]" safer than "ALLOCATE(Foo, count)"? Or just do this minimal boilerplate by hand, this is not a practical issue unless your
code is very badly factored (good code requires only few allocations).> no need to deal with pointers for out parameters, thanks reference parameters, which even in C++ there isn't any guarantee they aren't null, even though that would actually trigger UB)
This is not a problem at all in practice, and anyway, if you need a lot of that you're doing something wrong. I very much like that C keeps it simple and you always see what happens. This is unlike Pascal and its Objects extensions, where it's impossible to see which data you're actually mutating because there are like 10 different "adressing modes". For example, dynamic arrays in Pascal can be assigned to other variables (or passed through a function parameter), and they will share memory, but only until one calls SetLength() on one "reference" at which point a Copy-on-Write is happening and they split lifes. This is not only not useful but extremely confusing. And the whole object extensions to Pascal (I can speak for Delphi) are built in that way - trying to not let the user see the complexity of what they're doing - which works until it doesn't, which is when you're dealing with extremely difficult to debug problems. Frankly Delphi ecosystem is a mess because it's a set of layers where each time they tried to be clever and add another philosophy-driven set of classes with implicit behaviours on top that was supposed to fix the previous layer's mistakes.
> - if one wants to do crazy C like code, all the necessary gear is available, pragmas to turn off bounds checking, pointer arithmetic, unchecked casts, unions, mapping variables to explicit memory addresses, pointers to callbacks, it is everything there in the package. The original bare bones ISO Pascal wasn't no longer relevant in the mid-80's, unless the teacher didn't knew any better.
This is why I took issue - you can't have both low-level bit twidding and "memory safe" programming. At most you can have a language that lets you choose between one or the other in a rather integrated system, but then you're still dealing with this abstraction gap, and having to traverse it constantly is not pleasant.
Re: Nim Apocrypha, Vol. I
#26Earlier quoted context omitted.
Has answered multiple times, including to your previous comments. - bounds checking enabled by default for arrays and strings - real enumerations that aren't implicit converted into numeric types (fixed in C++11, if one bothers to use enum classes) - enumerations can be used as indexes (C++ can work around this with enum classes and some boilerplate templates) - most data conversions must be done explicitly - ability…
> bounds checking enabled by default for arrays and strings You can make your own explicit bounds checks, or you can use valgrind (which, just like any type system, can understand only simple situations). You can use C++ which actually does allow you to do bounds checking by default, if you're into such things. > - real enumerations that aren't implicit converted into numeric types (fixed in C++11, if one bothers to…
Yeah, apparently Google, Apple, Microsoft think otherwise.
> Now tell me, why exactly is "new Foo[count]" safer than "ALLOCATE(Foo, count)"? Or just do this minimal boilerplate by hand, this is not a practical issue unless your code is very badly factored (good code requires only few allocations).
1 - Everyone needs to create their ALLOCATE macro, and not everyone does it right
2 - Actually it fails with certain kinds of type given as parameter
> I very much like that C keeps it simple and you always see what happens.
That is what people that never read ISO C and the myriad of UB cases think they do.
That is why I get such a kick of C Pub Quizzes, everyone is so full of themselves in regards to their C knowledge.
> This is why I took issue - you can't have both low-level bit twidding and "memory safe" programming. At most you can have a language that lets you choose between one or the other in a rather integrated system, but then you're still dealing with this abstraction gap, and having to traverse it constantly is not pleasant.
That is the whole point, dangerous code that can trigger memory corruption code should only be written when there is no way around to do it in a safer way.
And most of the time, it is an optimiser bug that is even the case to start with, unless one is writing drivers or kernel code.
Re: Nim Apocrypha, Vol. I
#27> Unless if you’re one of those NodeJS wielding kids who thinks Electron is a good idea and the web is the ultimate application development platform… Ignorance is bliss! MIC DROP!! Wonderful article. Nim is a great programming language. I am using nim and c these days as my primary languages.
Re: Nim Apocrypha, Vol. I
#28Earlier quoted context omitted.
I seem to be in the minority but I really dislike this type of sugar. I find that it always makes code harder to read because you can't disambiguate what the code is doing without some heavy context, and changes to the class can profoundly change the way the code is behaving without triggering as much as a warning. For this same reason I really dislike that in C++ you can just implicitly drop the "this->" to target c…
Use syntax highlighting that differentiates between members and non members, problem solved.
Re: Nim Apocrypha, Vol. I
#29Earlier quoted context omitted.
Citation required. How can Wirth languages (e.g. Pascal, Modula) have "the same capabilities for low level programming", but without the traps, of C derived languages (e.g. C, C++)? The idea suggests itself that you can have one or the other, but not both. Personally from trying it out, I like the speed resulting from the stricter module system. And there might be a little additional protection against bad builds res…
Undefined behavior and null terminated strings with the attendant buffer overflows are not fundamental constants of low level programming; they’re implementation details of C. Rusty (and Nim, Modula, Pascal, Ada, etc.) show that it’s perfectly possible to write low-level code with zero-cost abstractions without “unsafe by default” programming languages like C.
UB in C has some unfortunate historic problems, and is _probably_ way too complicated. But UB per se is needed for a number of really important optimizations - for example, for array indexing using 32-bit integers you need to be able to assume that index-scaling operations (implemented as multiplications/shifts) don't overflow.
> null terminated strings
null terminated strings are like 10-50% space savers compared to string + length tuples for realistic strings (which are small). They're also easy to pass around and require no bikeshedding what's the best string type.
You can easily make your own type if you want (struct String { const char *buffer; int length; }) or you can just use C++ and one of the more terrible full featured string types with all sorts of badly matching built-in memory management and what not.
(By the way, the issue with SetLength() that I described in my other comment applies to Pascal Strings, too)
(Oh, and Pascal/Delphi also wants you to choose between PChar, String, AnsiString, WideString and what not, and for every little thing you're doing you need to locate and use the proper API for whatever string type you've chosen. Or you need to do conversions before the calls, with all the necessary implicit memory allocations. Enjoy!)
(Oh, this is basically how I achieved a 100x-1000x speedup in a module of a business application in a short Delphi stint. I converted the string data to chunk-allocated bytes with manually computed indexing - instead of allocating thousands of little strings and throwing them back and forth. That reduced the total application startup time (including a lot of code that I never touched) from minutes (in some cases) to something bearable (1-5 secs).
> with the attendant buffer overflows are not fundamental constants of low level programming
Of course they are. If you're not controlling bytes directly, and deciding about data layout and tradeoffs, I wouldn't call that low-level programming. YMMV. But - while my code is never fuzzed - it's been years since I've been bitten by a string bug. The importance of this stuff is way overblown, and knowledge and experience how to program with simple data structures is only found in more obscure corners of the internet. Also, very little in systems programming is about strings.
Re: Nim Apocrypha, Vol. I
#30Earlier quoted context omitted.
Undefined behavior and null terminated strings with the attendant buffer overflows are not fundamental constants of low level programming; they’re implementation details of C. Rusty (and Nim, Modula, Pascal, Ada, etc.) show that it’s perfectly possible to write low-level code with zero-cost abstractions without “unsafe by default” programming languages like C.
> Undefined behavior UB in C has some unfortunate historic problems, and is _probably_ way too complicated. But UB per se is needed for a number of really important optimizations - for example, for array indexing using 32-bit integers you need to be able to assume that index-scaling operations (implemented as multiplications/shifts) don't overflow. > null terminated strings null terminated strings are like 10-50% spa…