Skip to content

raises tracking should behave like nosideeffect tracking: effects through params should be allowed #403

@timotheecour

Description

@timotheecour

here's my thought regarding effectsDelayed (nim-lang/Nim#18610): IMO what we should do instead is same as what we do for {.nosideeffect.}, where a func is allowed to have side effects so long they're only through parameters:

nosideeffect: effects are allowed through params:

when true:
  proc fn1 = echo "ok1" # sideeffect
  proc maybeCall(a: proc()) = a() # maybeCall can be assumed to call a
  func bar1(a: proc()) = a() # direct call => no func violation because all sideeffects happen through params
  func bar2(a: proc()) = maybeCall(a) # potentially indirect call vs direct call doesn't matter: same analysis as bar1
  bar1(fn1) # ok
  bar2(fn1) # ok

  when false:
    # this correctly gives: Error: 'bar3' can have side effects
    # the side-effect is not through a param => error
    func bar3() = maybeCall(fn1)

raises: effects should also be allowed through params:

when true:
  proc fn1 =
    if false: raise newException(ValueError, "")
  type A = proc() {.raises: [ValueError].}
  proc maybeCall(a: proc()) = a()
  proc bar1(a: proc()) {.raises: [].} = a()
  proc bar2(a: proc()) {.raises: [].} = maybeCall(a)
  bar1(fn1)
  bar2(fn1)

  var f: A = fn1 # ok

  when false:
    # this correctly gives: Error: fn1() can raise an unlisted exception: ref ValueError
    # the raises is not through a param => error
    proc bar3() {.raises: [].} =
      fn1()

so far so good, it behaves the same as nosideeffect.

The problem is the following; which is what this RFC is about:

  when false: # BUG!
    # this gives: Error: a() can raise an unlisted exception: ValueError
    # BUG: this should work, because the raise only happens through params
    # it doesn't make sense that we'd disallow this but now `bar1` which is more general
    proc bar4(a: A) {.raises: [].} = a()
    # proc bar5(a: A) {.raises: [].} = maybeCall(a) # same as bar4, the distinction indirect vs direct should be irrelevant

proposal

  • don't introduce effectsDelayed , it's not needed
  • make effect tracking work uniformly and in same way as it already works for nodesideeffect, such that effects are allowed through params
  • in the manual https://nim-lang.org/docs/manual.html#effect-system-exception-tracking, remove distinction between direct and indirect call, it's not what's relevant; what's relevant is whether the effect happens through a param (allowed) or not (tracked and disallowed if mismatch)

note

this is analog to how purity works in D, eg: https://forum.dlang.org/thread/[email protected]

D's working definition of a pure function is "effect only depends on parameters". Mutating parameters does fit the definition.
This is less tight than other languages' definition, but we believe it's a sweet spot between reaping the modularity benefits of purity, and benefiting of the advantages of mutation.

and related articles, eg: https://klickverbot.at/blog/2012/05/purity-in-d/

›Weak‹ Purity Allows for Stronger Guarantees

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions