[mercury-users] yield and iterators [was: First solution in csharp without try/catch]

Jeff Thompson jeff at thefirst.org
Thu Jan 12 17:21:44 AEDT 2012


On 1/8/2012 8:58 PM, Mark Brown wrote:
>> Is there another way (where the implementation doesn't use try/catch) to
>> make a semidet predicate which succeeds on the first solution of some code
>> block?
> In general you need a way to wind back the stack by an arbitrary number
> of frames for a commit, and in C# throwing an exception may well be the
> quickest way to achieve that.  In principle the compilation model could
> return a flag for each nondet call, but that would need to be tested after
> every call even if not actually required.

A way in C# is to use the "yield" and iterator features built into the 
language where the compiler adds this unwinding support.  Here is my 
example again where test succeeds if the condition succeeds once:

:- pred test is semidet.
test :- if multiPred(10, X), X > 15 then true else fail.

:- pred multiPred(int::in, int::out) is multi.
multiPred(M, X) :- X = 1 * M.
multiPred(M, X) :- X = 2 * M.

Here is the Mercury compiler C# output (simplified) for test which 
catches the runtime.Commit() exception thrown by test_0_p_0_3:

         private class Test_0_p_0_env_0
         {
             public bool succeeded;
             public int X_1;
         }
         public static bool test_0_p_0()
         {
             var env_ptr = new testYield.Test_0_p_0_env_0();
             try
             {
                 multiPred_2_p_0(10, testYield.test_0_p_0_3, env_ptr);
                 env_ptr.succeeded = false;
             }
             catch (runtime.Commit commit_variable)
             {
                 env_ptr.succeeded = true;
             }
             return env_ptr.succeeded;
         }
         public static void multiPred_2_p_0(int M_1, 
runtime.MethodPtr2_r0<int, object> cont, object cont_env_ptr)
         {
             cont(1 * M_1, cont_env_ptr);
             cont(2 * M_1, cont_env_ptr);
         }
         private static void test_0_p_0_3(int arg1, object env_ptr_arg)
         {
             var env_ptr = (testYield.Test_0_p_0_env_0)env_ptr_arg;

             env_ptr.X_1 = arg1;
             env_ptr.succeeded = (env_ptr.X_1 > 15);
             if (env_ptr.succeeded)
                 throw new runtime.Commit();
         }

Here is the code using "yield".  See embedded comments.

         public static bool test_0_p_0_yield()
         {
             var X_1 = new int[1];
             foreach (var l1 in multiPred_2_p_0(10, X_1))
             {
                 foreach (var l2 in test_0_p_0_3(X_1[0]))
                     return true;   // Commit. The compiler handles all 
the stack unwinding at this point.
             }
             return false;
         }
         public static IEnumerable<bool> multiPred_2_p_0(int M_1, int[] X_1)
         {
             X_1[0] = 1 * M_1;
             yield return false;
             X_1[0] = 2 * M_1;
             yield return false;   // Multiple yields for "multi" 
predicates.
         }
         private static IEnumerable<bool> test_0_p_0_3(int arg1)
         {
             if (arg1 > 15)
                 yield return true;      // This nested call doesn't 
need to know at this point that the outer loop will do a commit.
             // else if no yield, then this predicate "fails".
         }

Note also that with this approach it is not necessary for a predicate to 
create an env_ptr or to pass a continuation, since upon "yield" the 
control returns to the predicate.

Thanks,
- Jeff
--------------------------------------------------------------------------
mercury-users mailing list
Post messages to:       mercury-users at csse.unimelb.edu.au
Administrative Queries: owner-mercury-users at csse.unimelb.edu.au
Subscriptions:          mercury-users-request at csse.unimelb.edu.au
--------------------------------------------------------------------------



More information about the users mailing list