列表:在Erlang中带有副作用的地图

时间:2018-11-04 17:35:55

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

我有一个ID批次(子列表)的列表,我想遍历此列表并为ID批次中的每个ID生成一个工作进程。这些工作者中的每一个都将查询某些服务,获取结果并将其发送回呼叫者。简单来说,我想将Integer.MAX_VALUE的列表映射到借助这些System.out.println(Integer.MAX_VALUE + 1); // -2147483648 System.out.println(Integer.MAX_VALUE + 1 == Integer.MIN_VALUE); // true 获得的数据列表。我设法实现了这一目标,但我相信这是一种独特的方式:

id

在这种情况下,如您所见,我误用了id函数,因为它的设计无副作用。但我看不到其他选择。有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,而在我的情况下,我也想保留列表的形状。在Haskell中,有一个方便的带有foreach()函数的类型类ok,它可以做到这一点:运行Traversable并且同时允许您对每个项目执行操作(效果)。在Erlang中有类似的东西吗? (例如traverse?)。

2 个答案:

答案 0 :(得分:3)

与Haskell不同,Erlang不是 pure 功能编程语言。因此,它不会对功能是否产生副作用施加任何限制。在Haskell中,即使I / O子系统也无法打破其纯度,这就是为什么在TraversableFunctortraversefmap)的类型级别上存在区别的原因前者可以对容器的每个元素产生影响,而后者则不能。在Erlang中,没有明显的区别,因此,您可能拥有一个函数execute(Container) ->,并且仅通过凝视其签名就不知道它是否会产生效果。这就是为什么在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的理解。

答案 1 :(得分:1)

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

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

因此,这就是我不使用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.

请务必注意,您在此处未处理任何错误,这一点也很重要。由于您没有捕获出口,因此在生成的过程中出现错误只会杀死主要出口。