-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
prp: refactor #47
base: dev
Are you sure you want to change the base?
prp: refactor #47
Conversation
Replace the `xor_in_place` C function with a pure Nim implementation, This avoids use of unsafe language features and improves clarity. In particular, the proc signature proc runHmacPrf(ctx: PrpCtx, key: string) = was previously not self documenting: it mutated the (non-ref) object `PrpCtx` via `addr`. And make some other changes for readability: - Prefer `let` over `var`. - Prefer to initialize explicitly. It is expected that this will eventually be enforced in a future version of Nim [1]. - Prefer the object construction syntax. - Prefer the least powerful construct: proc, rather than template. We can rely on the compiler to inline when appropriate. - Remove a redundant `$` operation. [1] https://github.com/nim-lang/Nim/blob/v2.0.0/doc/manual_experimental.md#strict-definitions-and-out-parameters
Some of the items you bring up here as changes you'd make are things I definitely agreed with at the time. For instance, I never care about performance until it's a proven problem, so I don't mind the code going to Nim... it was in C because I tried multiple different Nim implementations and had either problems getting it to compile at all, or bizarre memory problems, that were fixed when I wrote it in C. I can talk about why some of the things here are the way they are (e.g., I remember fighting with nim on some of the things I'd consider basic in this file, and I remember a few of the things that seemed like they should have worked, before I got exasperated and dropped to C). But I don't think that's important (I'm 100% fine w/ it being slower and in Nim), I think the broader style conversation is, and thanks for bringing it up. From a style perspective, I don't care as much about 'idiomatic Nim' as I do trying to make things as easy to follow as possible for someone coming to Nim from outside. There aren't many Nim programmers anyway, so if our code is something people are looking at, it should be as clear as possible. And I also think some of the things that are 'idiomatic nim' obfuscate things for the programmer, because they are too intertwined with Nim's implementation details. Particularly, I think that the Most people's initial understanding of
For instance, of these two patterns:
And give up the
The former is much more clear. In the context of just a single function, the With cross-function calls, I think there's more of a case for it being valuable to the programmer to ensure immutability. However, that's where Nim's semantics get muddled, and prefer the implementation to the programmer. First, even if I declare something Things get muddled when we move to Most languages don't have the But Nim having modifiers like
Which would be fine, except, very few new people coming in would ever have a good intuition about when fields of something passed are going to be mutable. What happens when the object is Even though I am reasonably aware of how Nim's implementation works, I still often have to test to understand what is going to happen. The fact that immutability and reference passing are intertwined in that notion of I have had more than a few memory errors that clearly are due to interactions between Even though you're asked to use I'm sure the problems mainly stem from when Nim chooses to copy, and when Nim chooses to send a reference, and I understand that's arcane, and why it's arcane. But that implementation detail should NEVER need to be visible to the programmer in my view. I'm sure it's not intentionally that way, but I don't think it's acceptable to end up putting the mental burden on developers. To be clear, I was initially reasonably happy w/ the idea of 'ref object' vs 'object', because as a C programmer I definitely like the flexibility of whether to embed a reference or whether to inline the data structure. I didn't previously love the approach of "you always get a pointer". But in Nim it is not worth the tradeoff of unclear semantics for me. All of this to say, I'm not actually a fan of many of Nim's idiomatic bits:
The last one is a Nim-ism picked up from the functional world that values compactness over clarity (I think it dates all the way back to the first version of ML, if not further), that I have absolutely never been on board with. In your code, I can admit there are cases where it's clear enough, and helps declutter, especially when calling Otherwise, I don't mind if other people want to use constructs that are idiomatic nim. But I also feel no particular love for what's idiomatic, and have no desire to enforce idioms I don't feel are going to bring a lot of value. If it's causing people difficulty in driving overall Besides, I'm trying to get out of the critical path on anything code related here, so you'll have wide latitude on style. I'd ask two things though:
|
BTW, after 30 years of not having any problem w/ Python's white space approach, and not really understanding why it's so polarizing (despite having written tons of C and Python), I have come to be on It's not really Nim's fault, but the comment above illustrates part of the problem... when I typed the I also have had lots of issues where moving code around is more difficult w/o the brace delimiters, and leaves bugs in code past the compile. The braces help address that. Nothing to action, just an observation :) |
A small refactor, in case we didn't need the C implementation here. But this is mainly to begin to discuss some Nim patterns for long-term code readability/maintainability in general.
If we really care about performance here, I admit that this PR is likely a performance regression. I believe the C implementation is likely to be lowered to SIMD instructions, but this Nim one isn't because the codegen for strings cannot be optimized as easily. See https://nim.godbolt.org/z/G8Ys4vPce (or here with
--opt:size
), and note that the Nim implementation withbyte
is similarly optimized.I believe it'd be a little more idiomatic to prefer
array[n, byte]
oropenArray[byte]
oropenArray[char]
rather than usingstring
. And the compiler can optimize that better. ButopenArray
is admittedly awkward sometimes, especially given that view types are still marked as experimental.Replace the
xor_in_place
C function with a pure Nim implementation, This avoids use of unsafe language features and improves clarity. In particular, a reader should not generally expect that calling a proc with a signature likemutates the non-ref object
bar
viaaddr
. That's what happened previously with:Of course, the reader can get it from the context, but it's still less clear.
At the time of writing, we only use this module's public procedures (
prp
andbrb
) in chalk, and only inattestation.nim
on:Also, make some other changes for readability:
let
overvar
.$
operation.template
only when necessary, never use theinline
pragma, and enable link-time optimization in release builds. And from the manual:In general, unnecessary template usage has the potential to:
--opt:size
).