如何在Erlang中使用闭包?

ljsrvy3e  于 2022-12-08  发布在  Erlang
关注(0)|答案(3)|浏览(200)

我有两个清单:L和E。我试着写一个函数,它返回另一个列表,其中包含E中元素从L开始出现的次数。

-module(mymodule).
-export([count/2]).
-export([numberOfOccurences/2]).

count([Head|Tail], Counter) ->
  fun(Element) -> if
    [Head|Tail] == [] -> Counter;
    Element == Head -> count(Tail, Counter + 1);
    Element /= Head -> count(Tail, Counter)
  end
end.

numberOfOccurences(L, E) -> 
    lists:map(count(L, 0), E).

mymodule:numberOfOccurences[1,2,"abc",2,1,"abc",4,1,1], [1,2,3,"abc"])应该返回[4,2,0,2]。但它返回了一个包含4个函数的列表。我做错了什么?

jexiocij

jexiocij1#

What's happening here, is, if we unroll this map, count(L, 0) is being called first, then that resultant fun is being passed to lists:map . When that resultant fun is being mapped with each member of E , and being passed to the anonymous function, the return value for most of the elements is the result of calling count(Tail,Counter) , which returns a function.
Here's a rewritten version of your functions that works. The big thing is

  1. I fixed the base case, otherwise, you'll likely run into a match error when the empty-set is passed to count() , and more importantly,
  2. In your closure, in order to ensure the proper recursion, you need to call the return value of count() , so I'm storing the result of count() calls into F , then calling that function with the passed element.
    So here is the updated module:
-module(mymodule).
-export([count/2]).
-export([numberOfOccurences/2]).

count([],Counter) ->
    fun(_) -> Counter end;
count([Head|Tail], Counter) ->
    fun(Element) ->
        if
            Element == Head ->
                F = count(Tail, Counter + 1),
                F(Element);
            Element /= Head ->
                F = count(Tail, Counter),
                F(Element)
        end
    end.

numberOfOccurences(L, E) ->
        lists:map(count(L, 0), E).

Results:

> mymodule:numberOfOccurences([1,2,"abc",2,1,"abc",4,1,1], [1,2,3,"abc"]).
[4,2,0,2]
brvekthn

brvekthn2#

Let us look at your function count/2.

count([Head|Tail], Counter) ->
  fun(Element) -> if
    [Head|Tail] == [] -> Counter;
    Element == Head -> count(Tail, Counter + 1);
    Element /= Head -> count(Tail, Counter)
  end
end.

This function contains statement that is the definition of a function. This being the last statement it becomes the return value. So the call:

lists:map(count(L, 0), E).

indeed returns a list of functions. Looking at the definition of the count function, it indeed makes recursive calls to count and might actually work if it was ever called, which it isn't.
We could add one statement to the end of your program to call all the elements of the by changing the call in this way:

numberOfOccurences(L, E) ->
        [F(E) || F <- lists:map(count(L, 0), E)].

Or alternatively, if you have a preference for the map function:

numberOfOccurences(L, E) ->
    lists:map(fun(F) -> F(E) end, lists:map(count(L, 0), E)).

However these do not run.
mymodule:numberOfOccurences([1,2,"abc",2,1,"abc",4,1,1], [1,2,3,"abc"]). ** exception error: bad function 4 in function mymodule:'-numberOfOccurences/2-lc$^0/1-0-'/2 (/home/tony/Projects/temp/src/mymodule.erl, line 20)
3>
As a matter of style the code would be easier to reason about if count parameters were passed rather than using closures in this way. Sometimes closures are essential such as when using spawn/1, but this is not one of those cases.
Analysing the problem I agree the first stage is a map, however I suggest counting matching elements is best achieved with a fold. However I will generally substitute a list comprehension for a map call. I just think it looks tidier.
So here is my solution:

-module occurances.
-export [count/2].

count(Data,KeyList) ->
    [ lists:foldl(
       fun(X,Count) when X =:= Key -> Count+1;
          (_,Count) -> Count
       end,
    0,Data) 
     || Key <- KeyList].

Note pattern matching with the value Key inside the foldl function will result in a shadow variable warning. X =:= Key is used instead.
occurances:count([1,2,"abc",2,1,"abc",4,1,1], [1,2,3,"abc"]). [4,2,0,2]
So having made the code actually work how about putting into a closure so that spawn/1 could be called for example. Let us edit our working code module to make such a closure, and let the closure write the result to standard io so we can see the result.

make_closure(Data,KeyList) ->
fun() ->
    io:format("~p~n",[count(Data,KeyList)])
end.

8> F=occurances:make_closure([1,2,"abc",2,1,"abc",4,1,1], [1,2,3,"abc"]).
F=occurances:make_closure([1,2,"abc",2,1,"abc",4,1,1], [1,2,3,"abc"]).
#Fun<occurances.0.132489632>
9> spawn(F).
spawn(F).
[4,2,0,2]
<0.107.0>
10>
fjaof16o

fjaof16o3#

For the record, you don't need a closure to define count .
In my book, it's much clearer to have an explicit count function, and plug it appropriately.

Edit: I've put list parameter second, to match lists module API.

count(X, [],    Acc) -> Acc;
count(X, [X|T], Acc) -> count(T, X, Acc+1);
count(X, [_|T], Acc) -> count(T, X, Acc).

numberOfOccurences(L, E) -> 
    [count(X, L, 0) || X <- E].

相关问题