[Effects] (Solved) How to debug the case when an effect is not handled

effects

#1

I’m writing my first program with effects about memoization. The idea is that a recursive function sends and effect asking about all arguments that has been passed to it in the current call stack and gets this list as effect result. Than it does some useful work wrapped into try block which handles the same effect and returns the previous list extended with current call arguments.

And it seems that second raising of the effect is not handled. I want to know how to understand why this is happening in general case. Below is the part of relevant code and a link to the full repo. It should be compilable but I hope that it s not needed.

output:

➜  ocanren-eff git:(eff) ✗ ./test666.native 
Entering appendo '[1; 2; 3]' and '[4; 5]' and '_.10'
handlig askAppendo regression/test666.ml 58
Size of cache is 1
Entering appendo '[2; 3]' and '[4; 5]' and '_.13'
Fatal error: exception Unhandled

A piece of relevant code:

17  effect AskAppendoCache : (Obj.t * Obj.t * Obj.t) -> Cache3.t

20  let rec appendo a b ab = fun st ->
21    let _ = ignore @@ project3 ~msg:"Entering appendo" a b ab st in
22    let cache = perform (AskAppendoCache Obj.(repr a, repr b, repr ab)) in
23    let () = printf "Size of cache is %d\n%!" (Cache3.size cache) in
24
25    let inner st0 = conde
26      [ ((a === nil ()) &&& (b === ab))
27      ; Fresh.three @@ fun h t ab' ->
28          ?& [
29            (a === h%t);
30            (h%ab' === ab);
31            (appendo t b ab');
32          ]
33      ] st0
34    in
35    match inner st with
36    | x -> x
37    | effect (AskAppendoCache new_arg) k -> 
38            continue k Cache3.(extend new_arg cache)

52  let () = 
53    runL (-1)  q  qh ("", (fun q   -> fun st ->
54           let rel st = appendo (ilist [1;2;3]) (ilist [4;5]) q st in
56           match rel st with
57           | effect (AskAppendoCache arg) k -> 
58                  printf "handlig askAppendo %s %d\n" __FILE__ __LINE__;
59                  continue k Cache3.(extend arg empty)
60           | effect (AskReversoCache arg) k -> 
61                  printf "handlig askReverso\n";
62                  continue k Cache3.(extend arg empty)
63           | stream -> stream
64                 ));
65    ()
66

@kayceesrk, please, rescue me :slight_smile:


#2

Fresh.three seems to take a higher-order function as an argument. Is it possible that this function is evaluated in a context where there are no enclosing handlers? If you are using the latest multicore compiler, then you can use gdb to debug the program. Easiest would be to set a break point on the appendo function and get a backtrace. If you have handlers in the context, then they show up in the backtrace.


#3

@kayceesrk, It seems that I have a generator function and it’s evaluation will be postponed for the feature many times. And it seems that context is not grabbed with the thunk, so effect is not handled. Is there any example wit similar issues?


#4

What you can do in the specific example above is rather than wrapping inner st with the handler, you could wrap appendo t b ab' with the handler. In that case, the handler will be captured as a part of the higher order function. Would that work?


#5

Thanks, KC, It seems that I finally resolved it as I want to, your advice was very helpful.

It seems that I don’t need a root handler at all. The original purpose of this handler was to get the empty result for the cases where effect handler is not yet created (first run of the function). Finally, I have realized that it is not a problem: we can detect that there is no handler for our effect by catching the exception.

The code I currently have is like that.

    16      effect AskAppendoCache : (Obj.t * Obj.t * Obj.t) -> Cache3.t
    17      effect AskReversoCache : (Obj.t * Obj.t * Obj.t) -> Cache3.t
    18
    19      let rec appendo a b ab = fun st ->
    20        let _ = ignore @@ project3 ~msg:"Entering appendo" a b ab st in
    21        let cache =
    22          let arg = Obj.(repr a, repr b, repr ab) in
    23          try perform (AskAppendoCache arg)
    24          with Unhandled -> Cache3.(extend arg empty)
    25        in
    26
    27        let appendo a b ab = fun st ->
    28          match appendo a b ab st with
    29          | ss -> ss
    30          | effect (AskAppendoCache new_arg) k ->
    31              continue k Cache3.(extend new_arg cache)
    32        in
    33        conde
    34          [ ((a === nil ()) &&& (b === ab))
    35          ; Fresh.three @@ fun h t ab' ->
    36              ?& [
    37                (a === h%t);
    38                (h%ab' === ab);
    39                (appendo t b ab');
    40              ]
    41          ]
    42          st
    43
    44      let rec reverso a b = fun st ->
    45        let _ = ignore @@ project2 ~msg:"Entering reverso" a b st in
    46        let cache =
    47          let arg = Obj.(repr a, repr b, repr 1) in
    48          try perform (AskReversoCache arg)
    49          with Unhandled -> Cache3.(extend arg empty)
    50        in
    51        let reverso a b = fun st ->
    52          match reverso a b st with
    53          | ss -> ss
    54          | effect (AskReversoCache new_arg) k ->
    55              continue k Cache3.(extend new_arg cache)
    56        in
    57        conde
    58          [ ((a === nil ()) &&& (b === nil ()))
    59          ; Fresh.three @@ fun h t a' ->
    60              (a === h%t) &&&
    61              (reverso t a') &&&
    62              (appendo a' !<h b)
    63          ]
    64          st
    65
    66
    67      let () =
    68        runL (-1)  q  qh ("", (fun q   -> appendo (ilist [1;2;3]) (ilist [4;5]) q       ));
    69        runL (-1)  q  qh ("", (fun q   -> reverso (ilist [1;2;3]) q                     ));
    70      ()