https://github.com/nim-lang/Nim/pull/21066 changed the definition (and implementation) of what a "strict function" is once again. The definition was changed to "stores to locations that involve a ptr/ref indirection are forbidden". As you can see in this pull request this makes the language less expressive and requires more .cast(noSideEffect) overrides but in practice the number of required changes was quite low.
The advantage is that rule is much easier to teach and reason about. Reporting an error only has a single line information. Complexities like "X is the statement that potentially connects Y to a graph G that is connected to an immutable parameter P and Y is mutated here" are gone.
Another benefit is this new language rule does not depend on alias analysis so that generic code is not affected. No more "my generic func works with int but not with ref T".
It's now so simple that I think we can enable strict funcs in upcoming Nim versions and eventually reap the benefits for Nim's optimizer. But as usual, the optimizer can also compute the same property on its own and not bother Nim programmers.
Does that mean, you no longer have to make ref parameters var for funcs to compile?
That was never the goal of strict functions so I'm not sure what to say.
But it's about every ref/ptr location now, including locals?
Yeah, but it doesn't matter much because you can adapt the coding style quite easily, see the PR.
stores to locations that involve a ptr/ref indirection are forbidden
Does this mean that stores through a var parameter are allowed? If so that’s a bit unintuitive to me since it allows side effects, and the hallmark of a “pure” function in the math/FP sense is that it has no side effects, which makes its behavior a lot easier to reason about.
On the other hand, Nim is obviously not trying to become a FP language :) and maybe there are benefits to the optimizer or the programmer in allowing a strict function to mutate via a pointer expressed as a var parameter?
Should this be considered a bug?
{.experimental: "strictFuncs".}
func f(p:proc() {.noSideEffect.}) =
p()
f(proc() {.noSideEffect.} = discard newSeq[int](10))
If so that’s a bit unintuitive to me since it allows side effects, and the hallmark of a “pure” function in the math/FP sense is that it has no side effects, which makes its behavior a lot easier to reason about.
Efficiency in FP languages is not that easy to reason about. Nor is emulating mutable state by dummy function accumulator parameters. The FP advocates are as bad as the OO advocates.
Why would there be a var parameter if not for the mutability aspect.
Another example, I can just move heap allocation to a template and it seems to bypass any checks:
{.experimental: "strictFuncs".}
template t() =
result = newSeq[int](10)
func f():seq[int] =
t()
for i in 0..9:
result[i] = i
echo f()
Should this be considered a bug?
No. An allocation is not an effect. Never has been, never will be.
You can think of
func f(x: var X): Y
var x: X
let y = f(x)
as sugar for
func f(x: X): tuple[X, Y]
var x: X
let (newx, y) = f(x)
x = newx
So effectively this change will cause the following changes to the experimental strict funcs:
func a(x: var int) =
discard
func b() =
var x = 1
var p = addr x
a(x) # OK
a(p[]) # FAIL, was OK before