【问题标题】:A syntax for custom lazy-evaluation/short-circuiting of function parameters用于函数参数的自定义惰性求值/短路的语法
【发布时间】:2018-09-30 14:01:03
【问题描述】:

Oracle 定义了几个结构,这些结构利用了看似惰性求值但实际上是短路的结构。

例如:

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

永远不会调用函数 count_all(...)。

不过,我更感兴趣的是看起来像常规函数调用的语法:

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

Hundreth_digit_of_pi() 不会被调用,因为 coalesce 不是一个常规函数,而是一种语法糖,看起来像一个 - 对于常规的,参数会在函数被调用时进行评估。

问题是: 是否可以在 plsql 中定义一个行为相同的自定义过程/函数?

如果你不相信我会举一个有用的例子:

我们使用“框架”进行日志记录。 要跟踪你称之为过程的东西:

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

Trace_something 是执行以下步骤的“pragma 自主事务”过程: 1.查看是否启用了任何跟踪方法(例如文件/数据库表) 2. 对于每个启用的方法,使用该方法将参数保存在某处。 3. 如果保存到DB,提交。

在构建要跟踪的实际字符串时可能会出现问题,如果一开始甚至没有启用跟踪,我们就不想花费大量时间。

目标是,用伪代码:

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));

可以在plsql中完成吗?

【问题讨论】:

  • 我不这么认为。
  • This question 关于功能参数可能与您所要求的类似。
  • @Glenn 感谢您的评论,不幸的是它并没有为我剪掉它(在我的具体情况下,'first_50_paragraphs' 将行类型作为参数并且不是全局可见的,我已经编辑相应的问题)。我也尽可能避免立即执行。

标签: oracle plsql lazy-evaluation short-circuiting


【解决方案1】:

可以(部分)使用引用游标、条件编译或立即执行来实现惰性求值。 ANYDATA 类型可用于传递通用数据。

参考光标

引用游标可以使用静态 SQL 语句打开,作为参数传递,并且在需要之前不会执行。

虽然这确实回答了您关于惰性评估的问题,但我不确定它是否真的实用。这不是引用游标的预期用途。并且必须将 SQL 添加到所有内容中可能并不方便。

首先,为了证明慢函数正在运行,创建一个简单的休眠几秒钟的函数:

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 or replace function do_i_have_to_trace return boolean is
begin
    return true;
end;
/

这个函数可以通过执行 SQL 语句来完成工作。 SQL 语句必须返回某些内容,即使您可能不想要返回值。

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;
/

现在创建将始终调用跟踪但不必花时间评估参数的过程。

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;
/

默认情况下它正在工作并且速度很慢:

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

但是,当您将 DO_I_HAVE_TO_TRACE 更改为返回 false 时,该过程很快,即使它传递了一个缓慢的参数。

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;
/

其他选项

条件编译更传统地用于启用或禁用检测。例如:

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;
/

您可能还想重新考虑动态 SQL。编程风格和一些语法糖在这里可以产生很大的不同。简而言之,替代的引号语法和简单的模板可以使动态 SQL 更具可读性。更多详情见我的帖子here

传递通用数据

ANY 类型可用于存储和传递任何可以想象的数据类型。不幸的是,每种行类型都没有本机数据类型。您需要为每个表创建一个 TYPE。这些自定义类型非常简单,因此可以在必要时自动执行步骤。

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;
/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-30
    • 2011-02-06
    • 2019-12-09
    • 2021-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-24
    相关资源
    最近更新 更多