高阶Erlang:超大只的问答房间
使用gen_server实现的更为复杂的。。。山寨kahoot!!!!!!
然鹅这次我们换名字了,现在它叫阿童木TA(robota)!
-module(robota).
-behaviour(gen_server).
%% Test helper function
-export([get_module_grader/1, get_concurrent_grader/1, valid/3,
get_mgrader/1, run_all_module_grader/1, troels_eval/2, check/2, eval/3]).
%% API
-export([get_the_show_started/0, new/2,
add_part/3, del_part/2, available/1, unavailable/1, status/1,
grade/4]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-define(SERVER, ?MODULE).
%%%===================================================================
%%% API
%%%===================================================================
get_the_show_started() ->
try
gen_server:start_link({local, ?SERVER}, ?MODULE, [], [])
catch
_ : _ -> {error,"Failed to crate a RoboTA!/n"}
end.
new(RoboTA, Name) ->
gen_server:call(RoboTA, {new, Name}).
add_part(AssHandler, Label, Grader) ->
gen_server:call(?MODULE, {add_part, AssHandler, Label, Grader}).
del_part(AssHandler, Label) ->
gen_server:cast(?MODULE, {del_part, AssHandler, Label}).
available(AssHandler) ->
gen_server:call(?MODULE, {available, AssHandler}).
unavailable(AssHandler) ->
gen_server:call(?MODULE, {unavailable, AssHandler}).
status(AssHandler) ->
gen_server:call(?MODULE, {status, AssHandler}).
grade(RoboTA, Name, Submission, Pid) ->
gen_server:call(RoboTA, {grade, Name, Submission, Pid}).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) -> {ok, #{}}.
handle_call({new, Name}, _From, State) ->
case maps:find(Name, State) of
{ok, _} -> {reply, {error,the_name_has_been_token}, State};
_ -> Ref = make_ref(),
AssHandler = {Name, Ref},
NewState = maps:put(Name, {[], false, []}, State),
{reply, {ok, AssHandler}, NewState}
end;
handle_call({add_part, AssHandler, Label, Grader}, _From, State) ->
{Name, _Ref} = AssHandler,
case maps:find(Name, State) of
% check assignment not active
{ok, {AssignmentList,false, GraderList}} ->
% check label unique
case lists:keymember(Label, 1, AssignmentList) of
true -> {reply, {error, {Label, not_unique}}, State};
false ->
NewAssignmentList = lists:append(AssignmentList,[{Label,Grader}]),
NewState = maps:put(Name, {NewAssignmentList, false, GraderList}, State),
{reply, ok, NewState}
end;
{ok, {_AssignmentList,true, _}} ->
{reply, {error, {Name, is_available}}, State};
_ -> {reply, {error, fake_asshandler}, State}
end;
handle_call({available, AssHandler}, _From, State) ->
{Name, _Ref} = AssHandler,
case maps:find(Name, State) of
{ok, {AssignmentList,false, GraderList}} ->
ModuleGrader = get_mgrader(AssignmentList),
case ModuleGrader of
[] -> NewState = maps:put(Name, {AssignmentList, true}, State),
{reply, ok, NewState};
_ ->
try
NewAssignmentList = run_all_module_grader(AssignmentList),
NewState = maps:put(Name, {NewAssignmentList, true, GraderList}, State),
{reply, ok, NewState}
catch
Error -> Error
end
end;
{ok, {_AssignmentList,true, _}} ->
{reply, {error, already_available}, State};
_ -> {reply, {error, fake_asshandler}, State}
end;
handle_call({unavailable, AssHandler}, _From, State) ->
{Name, _Ref} = AssHandler,
case maps:find(Name, State) of
{ok, {AssignmentList, true, GraderList}} ->
% check no submission under grading and all graders have been upload
ModuleGrader = get_mgrader(AssignmentList),
try
unload_all(ModuleGrader),
check_no_grading(GraderList),
NewState = maps:put(Name, {AssignmentList, false, []}, State),
{reply, ok, NewState}
catch
Reason -> {reply, {error, Reason}, State}
end;
{ok, {_AssignmentList,false, _}} ->
{reply, {error, already_unavailable}, State};
_ -> {reply, {error, fake_asshandler}, State}
end;
handle_call({status, AssHandler}, _From, State) ->
{Name, _Ref} = AssHandler,
case maps:find(Name, State) of
{ok, {AssignmentList, false, _}} ->
{reply, {unavailable, AssignmentList}, State};
{ok, {AssignmentList, true, _}} ->
{reply, {available, AssignmentList}, State}
end;
handle_call({grade, Name, Submission, Pid}, _From, State) ->
case maps:find(Name, State) of
{ok, {_AssignmentList, false, _}} ->
{reply, {error, {Name, is_unavailable}}, State};
{ok, {AssignmentList, true, GraderList}} ->
GraderProcess = spawn(fun() -> graderloop([]) end),
NewState = maps:put(Name, {AssignmentList, true, [GraderProcess | GraderList]}, State),
case valid(Submission, [], AssignmentList) of
false -> Pid ! {error, invalid_submission};
ValidSub ->
try
Feedback = eval(ValidSub, AssignmentList, GraderProcess),
Pid ! {ok, Feedback}
catch
_:_ -> totally_grader_failed
end
end,
{reply, finished, NewState}
end.
handle_cast({del_part, AssHandler, Label}, State) ->
{Name, _Ref} = AssHandler,
case maps:find(Name, State) of
{ok, {AssignmentList, Active, GraderList}} ->
case lists:keyfind(Label, 1, AssignmentList) of
false -> {noreply, State};
Tuple -> NewAssignmentList = lists:delete(Tuple, AssignmentList),
NewState = maps:put(Name, {NewAssignmentList, Active, GraderList}, State),
{noreply, NewState}
end;
_ -> {noreply, State}
end.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
% helper functions
get_concurrent_grader([]) -> [];
get_concurrent_grader([X|Xs]) ->
case X of
{_Label, {troels, Gs}} ->
lists:append(Gs, get_concurrent_grader(Xs));
_ -> get_concurrent_grader(Xs)
end.
get_module_grader([]) -> [];
get_module_grader([X|Xs]) ->
case X of
{_Label, {andrzej, Callback, Init}} ->
lists:append([{andrzej, Callback, Init}], get_module_grader(Xs));
{andrzej, Callback, Init} ->
lists:append([{andrzej, Callback, Init}], get_module_grader(Xs));
_ -> get_module_grader(Xs)
end.
% combine get_concurrent_grader and get_module_grader
get_mgrader(AL) ->
CL = get_concurrent_grader(AL),
AAL = lists:append(CL,AL),
get_module_grader(AAL).
% run all module grader
run_all_module_grader([]) -> [];
run_all_module_grader([X|Xs]) ->
case X of
{Label, {andrzej, Callback, Init}} ->
case Callback:setup(Init) of
{ok, Salt} -> lists:append([{Label, {andrzej, Callback, Salt}}], run_all_module_grader(Xs));
% throw?
Error -> Error
end;
{Label, {troels, Gs}} ->
try
NGs = run_list_module_grader(Gs),
lists:append([{Label, {troels, NGs}}], run_all_module_grader(Xs))
catch
_ -> grader_failed
end;
Other -> lists:append([Other],run_all_module_grader(Xs))
end.
run_list_module_grader([]) -> [];
run_list_module_grader([X|Xs]) ->
case X of
{andrzej, Callback, Init} ->
case Callback:setup(Init) of
{ok, Salt} -> lists:append([{andrzej, Callback, Salt}], run_list_module_grader(Xs));
% throw?
Error -> Error
end;
Other -> lists:append([Other],run_list_module_grader(Xs))
end.
valid([],_,_) -> [];
valid([{Label, Answer} | Xs], Cont, AssignmentList) ->
case lists:keymember(Label, 1, AssignmentList) of
true ->
case lists:member(Label, Cont) of
true -> valid(Xs, Cont, AssignmentList);
false -> lists:append([{Label, Answer}], valid(Xs, [Label|Cont], AssignmentList))
end;
false -> false
end.
% check submissions with label and Answers with label
eval(_ValidSub,[],_) -> [];
eval(ValidSub,[{Label,Grader} | AL],GraderProcess) ->
case lists:keyfind(Label, 1, ValidSub) of
false -> lists:append([{Label, missing}], eval(ValidSub, AL, GraderProcess));
{Label,Answer} ->
try
Me = self(),
GraderProcess ! {Me,grading_start},
spawn(fun() ->
try
Result = check(Answer, Grader),
Me ! Result
catch
_:_ -> grader_failed
end
end),
receive
Result ->
GraderProcess ! {Me, grading_finish},
lists:append([{Label,Result}], eval(ValidSub,AL, GraderProcess))
after
3000 ->
GraderProcess ! {Me, grading_finish},
lists:append([{Label,grader_failed}], eval(ValidSub,AL, GraderProcess))
end
catch
_:_ -> lists:append([{Label,grader_failed}], eval(ValidSub,AL, GraderProcess))
end
end.
% check Gs result or total grader fail
troels_eval([],[]) -> [];
troels_eval([A|As],[G|Gs]) ->
Me = self(),
try
spawn(fun() ->
try
Res = check(A,G),
Me ! Res
catch
_:_ -> grader_failed
end
end),
Rs = troels_eval(As,Gs),
receive
Result -> lists:append(Rs, [Result])
end
catch
_:_ -> throw(grader_failed)
end;
troels_eval(_,_) -> throw(grader_failed).
% check single Answer in Gs is correct or not
check(A,G) ->
case G of
abraham -> looks_good;
niels -> failed;
{mikkel, Expect} ->
case (A =:= Expect) of
true -> looks_good;
_ -> failed
end;
{simon, Arg, Expect} ->
case (A(Arg) =:= Expect) of
true -> looks_good;
false -> failed
end;
{andrzej, Callback, Salt} ->
{ok, Result} = Callback:grade(Salt, {fakelabel, A}),
Result;
{troels, Gs} ->
try
troels_eval(A, Gs)
catch
_:_ -> grader_failed
end;
_ -> throw(grader_failed)
end.
unload_all([]) -> ok;
unload_all([{andrzej, Callback, Salt}|Xs]) ->
try
Callback:unload(Salt),
unload_all(Xs)
catch
Error -> Error
end.
graderloop(GraderList) ->
receive
{Pid, grading_start} ->
NewGraderList = [Pid | GraderList],
graderloop(NewGraderList);
{Pid, grading_finish} ->
graderloop(lists:delete(Pid, GraderList));
{From, check_finished} ->
case GraderList of
[] -> From ! all_finished;
_ -> graderloop(GraderList)
end
end.
check_no_grading([]) -> ok;
check_no_grading([G|Gs]) ->
From = self(),
G ! {From, check_finished},
receive
all_finished -> check_no_grading(Gs)
end.

浙公网安备 33010602011771号