【问题标题】:lists:map with side-effects in Erlang列表:在 Erlang 中具有副作用的映射
【发布时间】:2019-04-08 04:09:39
【问题描述】:

我有一个 id 的批次(子列表)列表,我想遍历这个列表并为这批 id 中的每个 id 生成一个工作进程。这些工作人员中的每一个都将查询一些服务,获取结果并将其发送回调用者。简单来说,我想将ids 的列表映射到我通过ids 获得的数据列表。我设法做到了这一点,但我相信这是一种单一的方式:

lists:map(fun(Ids) ->
Pids = [spawn_link(fun() ->
    Result = [...] % Here goes a side-effect operation (http request)
    Self ! {received_data, process(Result)}
end) || Id <- Ids],
[receive {received_data, Data} -> Data end || _Pid <- Pids],
end, JobChunks)))

在这种情况下,如您所见,我误用了 map 函数,因为它被设计为无副作用。但我没有看到其他选择。有foreach(),但它仅用于运行副作用并仅返回ok,而在我的情况下,我也想保留列表的形状。在 Haskell 中有一个方便的类型类 Traversabletraverse 函数,它正是这样做的:运行 fmap 并同时允许您对每个项目执行操作(效果)。 Erlang中有类似的东西吗? (也许像smap?)。

【问题讨论】:

    标签: functional-programming erlang erlang-otp map-function traversable


    【解决方案1】:

    Erlang 与 Haskell 不同,它不是一种函数式编程语言。作为推论,它没有对功能是否可以产生副作用进行限制。在 Haskell 中,即使是 I/O 子系统也无法破坏其纯度,这就是为什么在 TraversableFunctortraversefmap)之间存在类型级别的区别,前者可以在每个类型上运行效果容器的元素,而后者不能。在 Erlang 中没有如此明显的区别,因此,您可能有一个函数 execute(Container) -&gt;,而您只看它的签名就无法知道它是否会运行效果。这就是为什么在 Erlang 中使用 mapsmap(或 traverse,或其他任何名称)没有任何意义,也不会带来任何价值。但确实使用lists:map 进行此类操作会破坏map 的合同,这应该是一个纯函数。在这种情况下,我可能会建议您使用列表理解,在我看来是一种更惯用的方式:

    [begin
        Pids = [spawn_link(fun() ->
            % Side-effect operation which worker performs
        end) || Id <- Ids],
       [receive {received_data, Data} -> Data end || _Pid <- Pids]
    end || Ids <- JobChunks].
    

    再次以我自己的观点副作用是列表推导和lists:map() 之间的主要区别。当它们以上述方式使用时,我通常认为它们是 Haskell 的 monad 理解。

    【讨论】:

      【解决方案2】:

      我喜欢@Oleksandr 的回答,但在列表理解中使用 begin..end 块感觉有点。我会为此使用函数。

      同样重要的是要注意他的回答的第二部分并不能保证尊重原始列表的顺序(即它只会有相同的元素 # 但它们将根据它们到达的顺序进行排序)。这对您来说可能没问题,但如果您希望能够匹配输入(ID)和输出(结果),您必须使用选择性接收,如下所示。

      所以,这就是我在没有 OTP 的情况下实现它的方式(因为你也没有使用 OTP):

      your_function() ->
          [process_chunk(Ids) || Ids <- JobChunks].
      
      process_chunk(Ids) ->
          Pids = [spawn_side_effect_fun(Id) || Id <- Ids],
          [get_result_for(Pid) || _Pid <- Pids].
      
      spawn_side_effect_fun(Id) ->
          Self = self(),
          spawn_link(fun() ->
              Self ! {received_data, self(), your_side_effect_operation()}
          end).
      
      get_result_for(Pid) ->
          receive
              %% Here we're pattern-matching on Pid
              %% so that we get the result for this particular Pid
              %% therefore the order is preserved in the final list.
              {received_data, Pid, Data} -> Data
          end.
      

      同样重要的是要注意您在这里没有处理任何错误。由于您没有捕获出口,因此衍生进程中的错误只会杀死主要进程。

      【讨论】:

        猜你喜欢
        • 2016-10-01
        • 1970-01-01
        • 2017-08-14
        • 1970-01-01
        • 2019-03-16
        • 1970-01-01
        • 2013-11-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多