Hey, I've recently been exploring the effect system as a way to stop people 
from calling coroutine-only functions outside of a coroutine. This _seems_ like 
a prime use-case for effects.
    
    
    # coro.nim
    
    type YieldEffect = ref object of RootEffect
    
    proc jield*() {.tags: [YieldEffect].} =
      discard  # todo: implement me
    
    
    Run

In theory, I could annotate my application's `main()` proc with `{.tags: [].}` 
which would guarantee `jield()` is never called on the main code path. This 
unfortunately falls apart because _callbacks_ and _forward-declared procs_ are 
assumed to have any and all effects, unless their effects are explicitly 
annotated.
    
    
    # foo.nim
    
    var callback: proc()
    
    proc setCallback*(f: proc()) {.effectsOf: [].} =
      callback = f
    
    proc doCallback*() =
      callback()
    
    
    Run
    
    
    # main.nim
    
    import foo
    
    proc main() {.tags: [].} =
      setCallback(proc () =
        echo "hello world!"
      )
      doCallback()   # Error: doCallback() can have an unlisted effect: 
RootEffect
    
    main()
    
    
    Run

Library authors aren't gonna put `{.tags: [].}` on their callbacks (nor should 
they, as they can't assume that all effects are bad), therefore most libraries 
will accidentally bestow RootEffect on you at some point.

I can get closer to a workable solution by using a non-inherited type. This way 
I can permit RootEffect and anything that inherits from it, but anything else 
is still an error.
    
    
    # coro.nim
    
    type YieldEffect = object
    # ...
    
    
    Run
    
    
    # main.nim
    
    import foo
    
    proc main() {.tags: [RootEffect].} =
      setCallback(proc () =
        echo "hello world!"
      )
      doCallback()  # ok!
      # jield()     # correctly prevented :)
    
    main()
    
    
    Run

But now the opposite problem occurs, it's easy for the effect to be "lost", for 
example by assigning to a proc variable, or calling a foward-declared proc, or 
using `effectsOf` from Nim 1.6:
    
    
    # main.nim
    
    import foo
    
    proc bar()
    
    proc main() {.tags: [RootEffect].} =
      
      setCallback(proc () =
        echo "hello world!"
        jield()
      )
      var f: proc()
      f = proc() =
        echo "hello world!"
        jield()
      
      doCallback()     # allowed :(
      f()           # allowed :(
      bar()         # allowed :(
    
    main()
    
    proc bar() =
      jield()
    
    
    Run

I believe what I'm really looking for is a "forbidden" or "must be explicit" 
effect which is _always_ an error when assigned-to, passed-to or called-within 
any proc whose effects are left unspecified. The effect would only be allowed 
if it appears in the tag list.

The onus is then on the propagators of the effect to always list it, rather 
than every possible reciever of the effect to reject it.

Maybe it would look something like this?
    
    
    type
      YieldEffect = ref object of ExplicitEffect
      YieldEffect {.forbidden.} = object    # or like this...
      YieldEffect {.explicit.} = object     # or like this...
      YieldEffect = distinct object         # or like this?
    
    
    Run

Does this sound like a good idea or am I overlooking something?

Reply via email to