Wed Sep 22, 2021 8:22 am
Login Register Lost Password? Contact Us


Conditional execution

Comments and questions related to the Enterprise Control Language

Wed Oct 29, 2014 3:55 pm Change Time Zone

Hi all,

Imagine that depending on a condition I want to get a my result value from a certain function or another one. When I specify this using an IF statement, I can see that both are actually executed, although then I am given the right value.

This can be good when the calculation is simple enough, but it can be the case when one of the functions will perform complex calculations ("boil the ocean") and the other may call an external service we have to pay for. We may not want to call both every time, as it would be bad in terms of performance and we will be wasting money.

So in the following code :

Code: Select all
EXPORT rec := RECORD
  STRING1 number;
END;

EXPORT doFunction1 := FUNCTION
  OUTPUT('Boil the ocean');
  RETURN '1';
END;

EXPORT doFunction2 := FUNCTION
  OUTPUT('Pay for a service');
  RETURN '2';
END;

EXPORT ds := DATASET([{'1'},{'2'}],rec);

result := IF(ds[1].number='1', doFunction1, doFunction2);

OUTPUT(result);


I can see we are getting the right value ('1') but both outputs are displayed ('Boil the ocean' and 'Pay for a service'), so I can tell that both functions were executed.

Thanks again for your time.
Ignacio
 
Posts: 10
Joined: Sat Feb 01, 2014 9:04 pm

Thu Oct 30, 2014 3:01 pm Change Time Zone

Hi Ignacio,

I don't see how your code example will even compile. Rule #1 in ECL is that you cannot mix EXPORTed definitions with actions.

That said, removing the FUNCTIONs and replacing with SEQUENTIAL actions handles the condition well:

Code: Select all
rec := RECORD
        STRING1 number;
      END;

    doFunction1 := SEQUENTIAL(OUTPUT('Boil the Ocean'),OUTPUT('1'));
    doFunction2 := SEQUENTIAL(OUTPUT('Pay for a service'),OUTPUT('2'));
    ds := DATASET([{'1'},{'2'}],rec);

    result := IF(ds[1].number='1', doFunction1, doFunction2);
    result;


Here's another alternative:

Code: Select all
ds := DATASET([{'1'},{'2'}],{STRING1 choice});

MyChoice(STRING1 whatfunction) := FUNCTION
FirstResult  := SEQUENTIAL(OUTPUT('Boil the ocean'),OUTPUT('1'));
SecondResult := SEQUENTIAL(OUTPUT('Pay for a service'),OUTPUT('2'));
ChoiceOut := IF (whatfunction = '1',      
                  FirstResult,
             SecondResult);
RETURN ChoiceOut;                        
END;


MyChoice(ds[1].choice);
MyChoice(ds[2].choice);


Regards,

Bob
bforeman
Community Advisory Board Member
Community Advisory Board Member
 
Posts: 1005
Joined: Wed Jun 29, 2011 7:13 pm

Mon Nov 03, 2014 11:10 am Change Time Zone

Bob: You want to avoid using SEQUENTIAL unless you really need to use it.

It has significant effects - including stopping code being shared between branches of the sequential.


Ignacio:

The short answer to your question is that IF() only tries to ensure that only the correct branch of the IF() is executed if the type of the results is an action or a dataset. For scalars (and rows) both branches may be executed.

To ensure that the other branch isn't executed you can use IFF(). Changing to IFF() prevents any work involved in calculating RETURN '1' or RETURN '2'. (If they were complex expressions) - which is likely to be the issue in your real life example.

[Somewhat strangely in this case that causes the entire expression to be constant folded - I'm not 100% sure why it isn't with the IF() - but it means we need to add a NOFOLD into the example code to prevent that happening.]


However... in this case that isn't the whole story. Your "work" is being done in your two outputs. These are associated with the next expression, but only loosely. In this example they are spotted as being invariant, and moved so they are executed globally.

You should be able to use WHEN to directly tie the outputs to the expressions:

Code: Select all
EXPORT rec := RECORD
  STRING1 number;
END;

EXPORT doFunction1 := FUNCTION
  o1 := OUTPUT('Boil the ocean');
  RETURN WHEN('1', o1, SUCCESS);
END;

EXPORT doFunction2 := FUNCTION
  o1 := OUTPUT('Pay for a service');
  RETURN WHEN('2', o1, SUCCESS);
END;

EXPORT ds := DATASET([{'1'},{'2'}],rec);

result := IFF(NOFOLD(ds[1].number)='1', doFunction1, doFunction2);
//(The NOFOLD prevents the test condition being optimized away).

OUTPUT(result);


but there is one problem - https://track.hpccsystems.com/browse/HPCC-10243 - the SUCCESS option hasn't been implement for scalars.

So... If you really want your example to work you need to return datasets from your functions:

Code: Select all
EXPORT rec := RECORD
  STRING1 number;
END;

EXPORT doFunction1 := FUNCTION
  o1 := OUTPUT('Boil the ocean');
  RETURN WHEN(DATASET(['1'], rec), o1, SUCCESS);
END;

EXPORT doFunction2 := FUNCTION
  o1 := OUTPUT('Pay for a service');
  RETURN WHEN(DATASET(['2'], rec), o1, SUCCESS);
END;

EXPORT ds := DATASET([{'1'},{'2'}],rec);

result := IF(NOFOLD(ds[1].number)='1', doFunction1, doFunction2);

OUTPUT(result[1].number);


I suspect in real life using IFF will fix your problem, but if your costs really are with associated actions then you'll need to adopt something similar to the code above.
ghalliday
Community Advisory Board Member
Community Advisory Board Member
 
Posts: 198
Joined: Wed May 18, 2011 9:48 am

Tue Nov 04, 2014 5:04 pm Change Time Zone

Thanks both, Bob and Gavin.

bforeman wrote:I don't see how your code example will even compile. Rule #1 in ECL is that you cannot mix EXPORTed definitions with actions.

Sorry for that bad practice.

ghalliday wrote:IF() is executed if the type of the results is an action or a dataset.

I was not aware of it, good to know.

ghalliday wrote:To ensure that the other branch isn't executed you can use IFF().

As you guessed, in my real life example the use of IFF() was fine to do the trick. No need for the other tweaks, although again, good learning.

Thanks all again.

Ignacio.
Ignacio
 
Posts: 10
Joined: Sat Feb 01, 2014 9:04 pm

Sat Nov 08, 2014 5:39 pm Change Time Zone

Hi all,

Imagine that depending on a condition I want to get a my result value from a certain function or another one. When I specify this using an IF statement, I can see that both are actually executed, although then I am given the right value. :D





_______________
www.solitairechamp.info
Last edited by nawaz on Mon Jan 04, 2016 11:14 am, edited 2 times in total.
nawaz
 
Posts: 1
Joined: Sat Nov 08, 2014 5:38 pm

Sat Nov 08, 2014 7:08 pm Change Time Zone

Yes that can be true if it is a scalar expression. If that's a problem use IFF instead.
Gavin
ghalliday
Community Advisory Board Member
Community Advisory Board Member
 
Posts: 198
Joined: Wed May 18, 2011 9:48 am

Wed Nov 04, 2015 11:55 am Change Time Zone

Hi all,

Thanks again for your previous comments. For some time that looked like working in our application, but although we are getting the right answer, when looking at the graph, I'm not sure that the inner behavior is what we want.

In Gavin's example, we are not getting that output to "Pay for a service" (which is correct) but still we are getting an empty tab, as if actually some code was executed in that branch of the IFF. When looking at the graph in Roxie, I can see as attached in the picture below.

We have several real life examples, some more complex than other. The simplest one is when depending on an input we want to output one value calculated from a function or from other.

Code: Select all
IFF(NOFOLD(pIn_ClientId)=ClientID1,
      getDataById1(pIn_ID),
IFF(NOFOLD(pIn_ClientId)=ClientID2,
      getDataById2(pIn_ID),
      DS_EMPTY_RESULT));

And although with your approach we fixed some issues that happened when using IF, we can still see in the graph that for some reason both branches are evaluated even though the return value is a dataset (as opposed to scalars as in the example).

Are we missing anything? How do you think this can be possible? Can you think of any other workaround?

Best regards.

Ignacio.
Attachments
iff1.jpg
(67.05 KiB) Downloaded 708 times
Ignacio
 
Posts: 10
Joined: Sat Feb 01, 2014 9:04 pm

Wed Nov 04, 2015 4:05 pm Change Time Zone

Since the expression is a scalar you need to use IFF()

result := IFF(ds[1].number='1', doFunction1, doFunction2);

I would also avoid relying on side-effects associated with expressions unless there is no other way of achieving the same thing.
ghalliday
Community Advisory Board Member
Community Advisory Board Member
 
Posts: 198
Joined: Wed May 18, 2011 9:48 am


Return to ECL

Who is online

Users browsing this forum: No registered users and 1 guest

cron