【问题标题】:Lazy evaluation of expression in ElixirElixir 中表达式的惰性求值
【发布时间】:2015-02-24 20:18:35
【问题描述】:

我试图弄清楚 clojure 中是否有类似于 delay 的宏来获取可以稍后评估的惰性表达式/变量。

用例是Map.get/3 的默认值,因为默认值来自数据库调用,我希望仅在需要时调用它。

【问题讨论】:

  • 查看 Clojure 中“延迟”的来源(此处:github.com/clojure/clojure/blob/…)看起来延迟更接近于协议而不是宏。如果我是你,我会调查 Elixir 中的协议并以这种方式进行。
  • 在提问之前我检查了delay 的源代码,有趣的是delay 是用java 实现的,而不是clojure——它允许对表达式进行不同的构造和操作。

标签: clojure elixir lazy-evaluation


【解决方案1】:

Elixir 的宏可用于编写用于条件评估的简单包装函数。我在下面列出了一个要点,尽管它可能是更好/更智能的方式。

https://gist.github.com/parroty/98a68f2e8a735434bd60

【讨论】:

  • 这也是我最终得到的解决方案,尽管我的代码使用的是 if else,而且看起来不太好。似乎没有更好的方法,除非我们确实获得了编译器支持 :-) 与 clojure 中的 delay 相比,一个缺点是我们必须为我们想要使用的每个函数编写一个宏,而不是使用一个通用运算符。
【解决方案2】:

“通用”懒惰有点难以破解,因为这是一个相当广泛的问题。流允许可枚举的惰性,但我不确定表达式的惰性是什么意思。例如,x = 1 + 2 的惰性形式是什么?什么时候评估?

对于惰性形式的表达式,想到的是过程表达式:

def x, do: 1 + 2

因为在实际调用表达式之前不会计算 x 的值(据我所知)。如果我在这一点上错了,我相信其他人会纠正我。但我认为这不是你想要的。

也许您想重新表述您的问题 - 忽略流和对枚举值的惰性求值。

【讨论】:

    【解决方案3】:

    一种方法是使用进程。例如,地图可以包装在像 GenServer 或 Agent 这样的进程中,其中默认值将被评估为惰性。

    【讨论】:

    • 我不太明白你如何使用惰性评估的过程,你介意举一些代码作为例子吗?
    【解决方案4】:

    默认值可以是进行昂贵调用的函数。如果Map.get/3 未被用于返回函数,您可以检查该值是否为函数,如果返回则调用它。像这样:

    def default_value()
      expensive_db_call()
    end
    
    def get_something(dict, key) do
      case Map.get(dict, key, default_value) do
        value when is_fun(value) ->
          value.() # invoke the default function and return the result of the call
        value ->
          value # key must have existed, return value
      end
    end
    

    当然,如果地图包含函数,这种类型的解决方案可能不起作用。

    同时检查 Elixir 的 Stream 模块。虽然我不知道它是否有助于解决您的特定问题,但它确实允许惰性评估。来自文档:

    流是可组合的、惰性的枚举。在枚举过程中一个一个生成项目的任何可枚举都称为流。例如,Elixir 的 Range 是一个流:

    更多信息请访问the Stream documentation

    【讨论】:

    • Stream 不是我想要的,我正在寻找一种方法来延迟任何任意表达式。您使用case 的解决方案非常接近实际解决方案,但该解决方案需要使用宏来处理任意表达式。
    【解决方案5】:

    Map.get_lazyKeyword.get_lazy 推迟生成默认值,直到需要,链接下面的文档

    https://hexdocs.pm/elixir/Map.html#get_lazy/3

    https://hexdocs.pm/elixir/Keyword.html#get_lazy/3

    【讨论】:

      【解决方案6】:

      你可以将它包装在一个匿名函数中,然后在调用该函数时对其进行评估:

      iex()> lazy = fn -> :os.list_env_vars() end
      #Function<45.79398840/0 in :erl_eval.expr/5>
      iex()> lazy.()
      

      【讨论】: