A syntax for custom lazy-evaluation/short-circuiting of function parameters

北慕城南 提交于 2019-12-22 10:44:58

问题


Oracle defines several structures that make use of what looks like lazy evaluation but what's actually short-circuiting.

For example:

x := case when 1 = 2 then count_all_prime_numbers_below(100000000)
          else 2*2
     end;

The function count_all(...) will never be called.

However, what I'm more interested in is the syntax that looks like regular function call:

x := coalesce(null, 42, hundreth_digit_of_pi());

Hundreth_digit_of_pi() will not be called since coalesce is not a regular function, but a syntax sugar that looks like one - for regular ones parameters get evaluated when the function is called.

The question is: is it possible do define in plsql a custom procedure/function that would behave in the same way?

If you're not convinced I'll give an example when that could be useful:

We use ''framework'' for logging. To trace something you call a procedure:

trace_something('A text to be saved somewhere');

Trace_something is 'pragma autonomous transaction' procedure that does the following steps: 1. See, if any tracing methods (for example file / db table) are enabled 2. For every enabled method use that method to save parameter somewhere. 3. If it was saved to DB, commit.

A problem occurs when building the actual string to be traced might take noticable amount of time, and we wouldn't want to have to spend it, if tracing isn't even enabled in the first place.

The objective would be, in pseudocode:

procedure lazily_trace_something(some_text lazily_eval_type) {
    if do_i_have_to_trace() = TRUE then
        trace_something(evaluate(some_text));
    else
        NULL; -- in which case, some_text doesn't get evaluated     
    end if;
}


/*

*/

lazily_trace_something(first_50_paragraphs_of_lorem_ipsum(a_rowtype_variable));

Is it possible to be done in plsql?


回答1:


Lazy evaluation can be (partially) implemented using ref cursors, conditional compilation, or execute immediate. The ANYDATA type can be used to pass generic data.

Ref Cursor

Ref cursors can be opened with a static SQL statement, passed as arguments, and will not execute until needed.

While this literally answers your question about lazy evaluation I'm not sure if it's truly practical. This isn't the intended use of ref cursors. And it may not be convenient to have to add SQL to everything.

First, to prove that the slow function is running, create a function that simply sleeps for a few seconds:

grant execute on sys.dbms_lock to <your_user>;

create or replace function sleep(seconds number) return number is
begin
    dbms_lock.sleep(seconds);
    return 1;
end;
/

Create a function to determine whether evaltuation is necessary:

create or replace function do_i_have_to_trace return boolean is
begin
    return true;
end;
/

This function may perform the work by executing the SQL statement. The SQL statement must return something, even though you may not want a return value.

create or replace procedure trace_something(p_cursor sys_refcursor) is
    v_dummy varchar2(1);
begin
    if do_i_have_to_trace then
        fetch p_cursor into v_dummy;
    end if;
end;
/

Now create the procedure that will always call trace but will not necessarily spend time evaluating the arguments.

create or replace procedure lazily_trace_something(some_number in number) is
    v_cursor sys_refcursor;
begin
    open v_cursor for select sleep(some_number) from dual;
    trace_something(v_cursor);
end;
/

By default it's doing the work and is slow:

--Takes 2 seconds to run:
begin
    lazily_trace_something(2);
end;
/

But when you change DO_I_HAVE_TO_TRACE to return false the procedure is fast, even though it's passing a slow argument.

create or replace function do_i_have_to_trace return boolean is
begin
    return false;
end;
/

--Runs in 0 seconds.
begin
    lazily_trace_something(2);
end;
/

Other Options

Conditional compilation is more traditionally used to enable or disable instrumentation. For example:

create or replace package constants is
    c_is_trace_enabled constant boolean := false;
end;
/

declare
    v_dummy number;
begin
    $if constants.c_is_trace_enabled $then
        v_dummy := sleep(1);
        This line of code does not even need to be valid!
        (Until you change the constant anyway)
    $else
        null;
    $end
end;
/

You may also want to re-consider dynamic SQL. Programming style and some syntactic sugar can make a big difference here. In short, the alternative quote syntax and simple templates can make dynamic SQL much more readable. For more details see my post here.

Passing Generic Data

The ANY types can be use to store and pass any imaginable data type. Unfortunately there's no native data type for each row type. You'll need to create a TYPE for each table. Those custom types are very simple so that step can be automated if necessary.

create table some_table(a number, b number);
create or replace type some_table_type is object(a number, b number);

declare
    a_rowtype_variable some_table_type;
    v_anydata anydata;
    v_cursor sys_refcursor;
begin
    a_rowtype_variable := some_table_type(1,2);
    v_anydata := anydata.ConvertObject(a_rowtype_variable);
    open v_cursor for select v_anydata from dual;
    trace_something(v_cursor);
end;
/


来源:https://stackoverflow.com/questions/49931697/a-syntax-for-custom-lazy-evaluation-short-circuiting-of-function-parameters

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!