export_to (Was: Re: the OO metaphor)

Robert Virding <>
Fri Dec 1 14:20:58 CET 2000


Luke Gorrie <> writes:
>Sean Hinde <> writes:
>
>> These sort of things would become much cleaner if we had Richard O'Keefe's
>> idea of 'internal exports' (so for example the functions currently exported
>> for gen_server callbacks wouldn't become part of the API).
>
>Could someone explain to me how in practice you actually get in
>trouble from not having export_to?

Here is one paper where Richard explains export_to and other things.  It
is a TeX file, if anyone wants the DVI or PostScript files please tell
me.  I have also attached the example files he mentions.

Some of this stuff should definitely get into the system.

	Robert
-------------- next part --------------
%% Copyright (C) 1990, Ellemtel Telecommunications Systems Laboratories
%% File    : user.erl
%% Author  : Robert Virding
%% Purpose : Basic standard i/o server for user interface port.
%% Edited  : January 2000 by Richard A. O'Keefe
%% Reason  : To demonstrate all-functions-findable Erlang.

-module(user).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/5').

-export([
    start/0,
    start/1,
    start_out/0,
    server/1,
    server/2
]).
	 
-use(init, [				% Knows how to tell if we want a shell
    get_argument/1
]).
-use(shell, [				% Knows how to start a shell
    start/0
]).
-use(lists, [				% Knows how to test membership
    member/2				% should be a guard test now.
]).
-use(io_lib, [
    deep_char_list/1,
    format/2,
    write/1
]).

-import_static(io_msgs, [
    #done/2,
    #fwrite/2,
    #get_until/2,
    #io_request/3,
    #more/1,
    #put_chars/1,
    #requests/1
]).

-pid_name(user).

%%
%% The basic server and start-up.
%%

start(Closure) ->		% Closure() should return a PID, for a
    case Closure() of		% process that will act as the I/O port.
        Pid when pid(Pid) ->
	    pid_name_spawn(user, fun() ->
		process_flag(trap_exit, true),
		link(Pid),
		run(Pid)
	    end);
        Other -> {not_a_pid,Other}
    end.      

start() ->
    start_port([eof]).

start_out() ->			% Output-only version of start/0
    start_port([out]).

start_port(PortSettings) ->
    pid_name_spawn(user, fun() ->
	process_flag(trap_exit, true),
	run(open_port({fd,0,1}, PortSettings))
    end).

%   P is the Pid returned by Closure() in start(Closure) or
%   the Port opened by the other versions of start...
%   The split into run/1 and catch_loop/2 is so that a BEL
%   character can fire up a new server_loop/3, or not.

run(P) ->
    case init:get_argument(noshell) of
	{ok, [_|_]} -> 		% non-empty list => noshell
	    server_loop(P, -1, []);
	_ ->
	    group_leader(self(), self()),
	    catch_loop(P, shell:start())
    end.

catch_loop(Port, Shell) ->
    case catch server_loop(Port, 7, []) of
	new_shell ->
	    exit(Shell, kill),
	    catch_loop(Port, shell:start());
	{'EXIT',R} ->
	    exit(R)
    end.

throw_new_shell_if_abort_in_input(AbCh, _) when AbCh < 0 -> true;
throw_new_shell_if_abort_in_input(AbCh, Bytes) ->
    case lists:member(AbCh, Bytes) of
        true -> throw(new_shell);
	_    -> true
    end.

%   Port is the PID or Port we want to receive input from.
%   AbCh is the Abort Character (-1 if none).
%   Buf0 is the input characters currently buffered.

server_loop(Port, AbCh, Buf0) ->
    receive
	{Port,{data,Bytes}} ->
	    throw_new_shell_if_abort_in_inputs(AbCh, Bytes),
	    server_loop(Port, Buf0 ++ Bytes);
	#io_request(From,ReplyAs,Request) ->
	    Buf = io_request(Request, From, ReplyAs, Port, AbCh, Buf0),
	    server_loop(Port, AbCh, Buf);
	{Port,eof} ->
	    put(eof, true),
	    server_loop(Port, AbCh, Buf0);
	%% Ignore messages from port here.
	{'EXIT',Port,badsig} ->			%Ignore badsig errors
	    server_loop(Port, AbCh, Buf0);
	{'EXIT',Port,What} ->			%Port has exited
	    exit(What);
	_ ->					%Ignore other messages
	    server_loop(Port, AbCh, Buf0)
    end.

%% NewBuf = io_request(Request, FromPid, ReplyAs, Port, AbCh, OldBuf)

io_request(Req, From, ReplyAs, Port, AbCh, Buf0) ->
    case io_request(Req, Port, AbCh, Buf0) of
	{ok,Reply,Buf} ->
	    io_reply(From, ReplyAs, Reply),
	    Buf;
	{error,Reply,Buf} ->
	    io_reply(From, ReplyAs, Reply),
	    Buf;
	{exit,What} ->
	    send_port(Port, close),
	    exit(What)
    end.

put_chars(Chars, Port, Buf) ->
    case io_lib:deep_char_list(Chars) of	%Check deep list
	true ->
	    put_port(Chars, Port),
	    {ok,ok,Buf};
	false ->
	    {error,{error,put_chars},Buf}
    end.

/*  io_request(Request, Port, AbCh, Buf)
    returns one of
	{exit,What}		fatal error
	{error,Reply,Buf1}	non-fatal error, Buf1 is updated Buf
	{ok,Reply,Buf1}		success, Buf1 is updated Buf.
*/
io_request(#put_chars(Chars), Port, _, Buf) ->
    put_chars(Chars, Port, Buf);
io_request(#fwrite(Format,Args), Port, _, Buf) ->
    put_chars(catch io_lib:fwrite(Format, Args), Port, Buf);
io_request(#get_until(Prompt,Closure), Port, AbCh, Buf) ->
    case get(eof) of
	undefined ->
	    get_until(Prompt, Closure, Port, AbCh, Buf);
	true when Buf == [] ->
	    {ok, eof, Buf};
	_ ->
	    get_until(Prompt, Closure, Port, AbCh, Buf)
    end;
io_request(#requests(Requests), Port, AbCh, Buf) ->
    io_requests(Requests, {ok,ok,Buf}, Port, AbCh);
io_request(R, Port, Buf) ->			%Unknown request
    {ok,{error,{request,R}},Buf}.		%Ignore but give error (?)

%% Status = io_requests(RequestList, PrevStat, Port, AbCh)
%%  Process a list of output requests as long as the previous status is 'ok'.

io_requests([R|Rs], {ok,_,Buf}, Port, AbCh) ->
    io_requests(Rs, io_request(R, Port, AbCh, Buf), Port, AbCh);
io_requests([_|_], Error, _) ->
    Error;
io_requests([], Status, _) ->
    Status.

%% put_port(DeepList, Port)
%%  Take a deep list of characters, flatten and output them to the
%%  port.  But the code does NOT flatten the list!

put_port(List, Port) ->
    send_port(Port, {command, List}).

%% send_port(Port, Command)

send_port(Port, Command) ->
    Port ! {self(),Command}.

%% io_reply(From, ReplyAs, Reply)
%%  The function for sending i/o command acknowledgement.
%%  The ACK contains the return value.

io_reply(From, ReplyAs, Reply) ->
    From ! #io_reply(ReplyAs,Reply).



/*  get_until(Prompt, Closure, Port, AbCh, OldBuf)
    accepts characters from the input port as long as the applied function
    returns #more(_).  It does not block output until input has been received.
    Returns:
  	{ok,Result,NewBuf}
  	{error,Result,NewBuf}
  	{exit,Reason}
*/

get_until(Prompt, Closure, Port, AbCh, Buf) ->
    prompt(Port, Prompt),
    get_until1(Prompt, Closure, Port, AbCh, Buf).

get_until1(Prompt, Closure, Port, AbCh, []) ->
    receive
    %%  Input cases
	{Port,{data,Bytes}} ->
	    throw_new_shell_if_abort_in_input(AbCh, Bytes),
	    get_until2(catch Closure([], Bytes), Closure, Port, AbCh);
	{Port, eof} ->
	    put(eof, true),
	    {ok, eof, []};
    %%  Catch io_requests whoseRequest is one of the two output requests.
	#io_request(From,ReplyAs,R = #put_chars(_)) ->
	    get_until1_out(R, From, ReplyAs, Prompt, Closure, Port, AbCh);
	#io_request(Form,ReplyAs,R = #fwrite(_,_)) ->
	    get_until1_out(R, From, ReplyAs, Prompt, Closure, Port, AbCh);
    %%  Fatal errors.
	{'EXIT',From,What} when node(From) == node() ->
	    {exit,What}
    end;

get_until1(Prompt, Closure, Port, AbCh, Buf) ->
    get_until2(catch Closure([], Buf), Closure, Port, AbCh).

get_until1_out(Req, From, ReplyAs, Prompt, Closure, Port, AbCh) ->
    io_request(#put_chars("^R\n"), Port, AbCh, []),  %"Cancel" prompt
    io_request(Req, From, ReplyAs, Port, AbCh, []),  %No new buf here!
    get_until(Prompt, Closure, Port, AbCh, []).

get_until2(#more(Cont), Closure, Port, AbCh) ->
    case get(eof) of
	undefined ->
	    receive
		{Port,{data,Bytes}} ->
		    throw_new_shell_if_abort_in_input(AbCh, Bytes),
		    get_until2(catch Closure(Cont,Bytes), Closure, Port, AbCh);
		{Port, eof} ->
		    put(eof, true),
		    get_until2(catch Closure(Cont,eof), Closure, Port, AbCh);
		{'EXIT',From,What} when node(From) == node() ->
		    {exit,What}
	    end;
	_ ->
	    get_until2(catch Closure(Cont,eof), Closure, Port, AbCh)
    end;
get_until2(#done(Result,Buf), _, _, _) ->
    {ok,Result,Buf};
get_until2(_, _, _) ->
    {error,{error,get_until},[]}.

%% prompt(Port, Prompt)
%%  Print Prompt onto port Port, special case just atoms and print unquoted.

prompt(Port, Prompt) when atom(Prompt) ->
    put_port(atom_to_list(Prompt), Port);
prompt(Port, {format,Format,Args}) ->
    case catch io_lib:format(Format, Args) of
	{'EXIT',_} ->
	    put_port("???", Port);
	List ->
	    put_port(List, Port)
    end;
prompt(Port, Prompt) ->
    put_port(io_lib:write(Prompt), Port).

/*  I have eliminated put(noshell)/get(noshell).
    I'd like to eliminate the put/get on eof as well.
*/
-------------- next part --------------
%% Copyright (C) 1990, Ellemtel Telecommunications Systems Laboratories
%% File    : io.erl
%% Author  : Robert Virding
%% Purpose : Standard i/o server interface functions.
%% Revised : 2000.02.08

-module(io).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/2').

-export([put_chars/1,put_chars/2,nl/0,nl/1,
	 get_chars/2,get_chars/3,get_line/1,get_line/2]).
-export([write/1,write/2,read/1,read/2]).
-export([fwrite/1,fwrite/2,fwrite/3,fread/2,fread/3,
	 format/1,format/2,format/3]).
-export([scan_erl_exprs/1,scan_erl_exprs/2,scan_erl_exprs/3,
	 scan_erl_form/1,scan_erl_form/2,scan_erl_form/3,
	 parse_erl_exprs/1,parse_erl_exprs/2,parse_erl_exprs/3,
	 parse_erl_form/1,parse_erl_form/2,parse_erl_form/3]).
-export([request/1,request/2,requests/1,requests/2]).

-import_static(io_msgs, [
    #io_request/4,
    #io_reply/3,
    #put_chars/1,
    #fwrite/2,
    #get_until/2,
    #get_chars/2,
    #get_line/1,
    #fread/2,
    #format/2,
    #write/1,
    #requests/1
]).
-use(io_lib, [
    collect_chars/3,
    collect_line/2,
    fread/3
    nl/0
]).
-use(erl_parse, [
    parse_exprs/1,
    parse_form/1,
    parse_term/1
]).
-use(erl_scan, [
    tokens/3
]).

%%
%% User interface.
%%

%% Writing and reading characters.

put_chars(Chars) ->
    put_chars(default_output(), Chars).

put_chars(Io, Chars) ->
    request(Io, #put_chars(Chars)).

nl() ->
    nl(default_output()).

nl(Io) ->
    request(Io, #put_chars(io_lib:nl())).

get_chars(Prompt, N) ->
    get_chars(default_input(), Prompt, N).

get_chars(Io, Prompt, N) ->
    request(Io, #get_chars(Prompt,N)).

get_line(Prompt) ->
    get_line(default_input(), Prompt).

get_line(Io, Prompt) ->
    request(Io, #get_line(Prompt)).

%% Writing and reading Erlang terms.

write(Term) ->
    write(default_output(), Term).

write(Io, Term) ->
    request(Io, #write(Term)).

read(Prompt) ->
    read(default_input(), Prompt).

read(Io, Prompt) ->
    case scan_erl_exprs(Io, Prompt, 1) of
	{ok,Toks,EndLine} ->
	    erl_parse:parse_term(Toks);
	{error,E,EndLine} ->
	    {error,E};
	{eof,EndLine} ->
	    eof;
	Other ->
	    Other
    end.

%% Formatted writing and reading.

fwrite(Format) ->
    fwrite(Format, []).

fwrite(Format, Args) ->
    fwrite(default_output(), Format, Args).

fwrite(Io, Format, Args) ->
    request(Io, #fwrite(Format,Args)).

fread(Prompt, Format) ->
    fread(default_input(), Prompt, Format).

fread(Io, Prompt, Format) ->
    request(Io, #fread(Prompt,Format)).

format(Format) ->
    format(Format, []).

format(Format, Args) ->
    format(default_output(), Format, Args).

format(Io, Format, Args) ->
    request(Io, #fwrite(Format,Args)).

%% Scanning Erlang code.

scan_erl_exprs(Prompt) ->
    scan_erl_exprs(default_input(), Prompt, 1).

scan_erl_exprs(Io, Prompt) ->
    scan_erl_exprs(Io, Prompt, 1).

scan_erl_exprs(Io, Prompt, StartLine) ->
    request(Io, #get_until(Prompt, fun (B, C) ->
		erl_scan:tokens(B, C, StartLine) end)).


scan_erl_form(Prompt) ->
    scan_erl_form(default_input(), Prompt, 1).

scan_erl_form(Io, Prompt) ->
    scan_erl_form(Io, Prompt, 1).

scan_erl_form(Io, Prompt, Pos0) ->
    scan_erl_exprs(Io, Prompt, Pos0).

%% Parsing Erlang code.

parse_erl_exprs(Prompt) ->
    parse_erl_exprs(default_input(), Prompt, 1).

parse_erl_exprs(Io, Prompt) ->
    parse_erl_exprs(Io, Prompt, 1).

parse_erl_exprs(Io, Prompt, Pos0) ->
    case scan_erl_exprs(Io, Prompt, Pos0) of
	{ok,Toks,EndPos} ->
	    case erl_parse:parse_exprs(Toks) of
		{ok,Exprs} -> {ok,Exprs,EndPos};
		{error,E} -> {error,E,EndPos}
	    end;
	Other ->
	    Other
    end.

parse_erl_form(Prompt) ->
    parse_erl_form(default_input(), Prompt, 1).

parse_erl_form(Io, Prompt) ->
    parse_erl_form(Io, Prompt, 1).

parse_erl_form(Io, Prompt, Pos0) ->
    case scan_erl_exprs(Io, Prompt, Pos0) of
	{ok,Toks,EndPos} ->
	    case erl_parse:parse_form(Toks) of
		{ok,Exprs} -> {ok,Exprs,EndPos};
		{error,E} -> {error,E,EndPos}
	    end;
	Other ->
	    Other
    end.

%% Miscellaneous functions.

request(Request) ->
    request(default_output(), Request).

request(Io, Request) ->
    case io_request(Io, io_request(Request)) of
	{error, E} ->
	    {error, E};
	_ ->
	    wait_io_reply(Io)
    end.

requests(Requests) ->				%Requests as atomic action
    requests(default_output(), Requests).

requests(Io, Requests) ->			%Requests as atomic action
    case io_request(Io, #requests(io_requests(Requests))) of
	{error, E} ->
	    {error, E};
	_ ->
	    wait_io_reply(Io)
    end.


default_input() ->
    group_leader().

default_output() ->
    group_leader().

io_request(standard_io, Request) ->
    group_leader() ! #io_request(self(),standard_io,Request);
io_request(Io, Request) ->
    case catch Io ! #io_request(self(),Io,Request) of
	{'EXIT', R} -> {error, arguments};
	X -> X
    end.

wait_io_reply(From) ->
    wait_io_reply(From, status_p(From)).

wait_io_reply(From, undefined) ->
    {error, terminated};
wait_io_reply(From, _) ->
    receive
	#io_reply(From,Reply) ->
	    Reply;
	{'EXIT',From,Reason} ->			%In case we are trapping exits
	    {error,terminated}
    end.

status_p(P) when pid(P), node(P) == node()  ->
    process_info(P, status);
status_p(undefined) ->
    undefined;
status_p(standard_io) ->
    status_p(group_leader());
status_p(N) when atom(N) -> 
    status_p(whereis(N));
status_p(_) -> {status, remote}.

    
%% io_requests(Requests)
%%  Transform requests into correct i/o server messages. Only handle the
%%  one we KNOW must be changed, others, including incorrect ones, are
%%  passed straight through. Perform a flatten on the request list.
%%  NB:  it would be nice to list _all_ the valid requests here and reject
%%  any bad ones.

io_requests(Rs) ->
    io_requests(Rs, [], []).

io_requests(#requests(Rs), Cont, Tail) ->
    io_requests(Rs), Cont, Tail);
io_requests([R|Rs], Cont, Tail) ->
    [io_request(R)|io_requests(Rs, Cont, Tail)];
io_requests([], [Rs|Cont], Tail) ->
    io_requests(Rs, Cont, Tail);
io_requests([], [], Tail) ->
    [].

io_request(    #format(F,A))    -> #fwrite(F, A);
io_request(    #write(Term))    -> #fwrite('~w',[Term]);
io_request(    nl)              -> #put_chars(io_lib:nl());
io_request(    #get_chars(P,N)) -> #get_until(P, fun (B,C) ->
				   io_lib:collect_chars(B, C, N) end);
io_request(    #get_line(P))    -> #get_until(P, fun (B,C) ->
				   io_lib:collect_line(B, C) end);
io_request(    #fread(P,F))     -> #get_until(P, fun (B,C) ->
				   io_lib:fread(B, C, Format);
/*
io_request(R = #put_chars(_))   -> R;
io_request(R = #get_until(_,_)) -> R;
io_request(R = #fwrite(_,_))    -> R;
*/
io_request(R) -> R.		%  Pass junk straight through.

-------------- next part --------------
-module(io_msgs).
-author('').
-static.				% -import_static & -use_static allowed.

-export_to([io, user, file], [		% Private protocol
    #io_request/4,
    #io_reply/3
]).
-export([				% To I/O parser modules
    #done/2
    #more/1
]).
-export([				% Public and private
    #put_chars/1,
    #fwrite/2,
    #get_until/2,
    #requests/1
]).
-export([				% Public protocol
    #get_chars/2,
    #get_line/1,
    #fread/2,
    #format/2,
    #write/1
]).

/*  The I/O messages can be divided into three groups:

    1.  Requests that anyone can pass to the io module, but which are
        mapped to something else.  They are thus in the public protocol
        but not the private protocol.

    2.  Requests that the io module sends to the server processes, but
        which are not to be used by other code.  They are thus in the
        private protocol but not the public protocol.

    3.  Requests which are in both the public protocol and the private
        protocol.  It is particularly important for the io module to
        check these before sending them on, because most of the time they
        will be ok, but occasionally an error will erupt deep in a distant
        process if we don't watch out.

    One good reason for the public protocol to exist is so that
    a batch of requests can be done "atomically".  There is a problem
    with that, and io:requests/[1,2] are not described in the
    documentation for the io module.

    VERY IMPORTANT NOTE:
    None of these patterns should have 'EXIT' or anything that looks like
    a Port as their first element.  Server modules should take care to use
    PIDs for ports, rather than registered names (should they be available).
*/

/*  Group 3:  public and private  */

#put_chars(Chars) /* when deep_char_list(Chars) */ -> {put_chars,Chars}.

    /* The argument should be a deep char list.  When should this be checked?
       It would be nice to check in the io module so that the server process
       could assume it was valid, but there are other things that can be bad
       like format/argument mismatch and closure failures in get_until calls,
       so it is best to have a uniform convention that error checks are done
       in the receiver.  There are error reporting issues in the design that
       may need reconsideration.
    */

#get_until(Prompt,Closure) when closure(Closure) ->
    {get_until,Prompt,Closure}.
    /*  This is used to accept characters from an input port as long as
        the Closure wants more input.  The Closure is called as
	    Closure(Buf, Bytes)		Buf is current buffered input, and
					    Bytes the next chunk, or
	    Closure(Buf, eof)		Buf is current buffer, no more input!
	and should return either
	    {more,NewBuf}			continue, NewBuf combines Buf,Bytes
	    {done,Result,NewBuf}		stop, Result is desired result, and
					    NewBuf the characters left over.
	Note that Closure("", eof) will NOT be called; the result is always
	eof when that would have happened.

	The results of Closure are not sent as messages, nevertheless they
	are an important aspect of the protocol, so they are defined here.
    */

#more(Cont) /* when char_list(Cont) */ ->
    {more,Cont}.

#done(Result, NewBuf) /* when char_list(NewBuf) */ ->
    {done,Result,NewBuf}.


#fwrite(Format,Args) when list(Args) ->
    {fwrite,Format,Args}.
    /*  Format should be an atom or a string; it should be a well
        formed format and the list of Args should agree with it.
        As noted above, this is checked by the receiver, not the sender.
	The same message is used for calls to `format'.
    */

#requests(Requests) when list(Requests) ->
    {requests,Requests}.
    /* In the public protocol, Requests is a list of requests from
       the public protocol, including nested #requests(_).
       In the private protocol, Requests is a list of requests from
       the private protocol.  The io module translates from public
       requests to private ones before sending this message.
       All of the (translated) requests are done by a server before it
       returns to its main loop.  This WOULD constitute an atomic
       request were it not for the fact that user:get_until1/5 will let
       output requests overtake an input request that is waiting for
       more input.  The really nasty thing there is that it will only
       let _unrelated_ output requests overtake an input request.
    */

/*  Group 1:  public only  */

#get_chars(Prompt,N) when integer(N), N >= 0 ->    
    {get_chars,Prompt,N}.
    /* The result should be a list of up to N characters, with fewer than
       N only if end of file is reached.  Implemented using get_until.
    */

#get_line(Prompt) ->
    {get_line,Prompt}.
    /* The result should be a list of characters, comprising a complete
       line of input.  Implemented using get_until.
    */

#fread(Prompt,Format) ->
    {fread,Prompt,Format}.

#format(Format,Args) when list(Args) ->
    {format,Format,Args}.
    /*  Format should be an atom or a string; it should be a well
        formed format and the list of Args should agree with it.
	This is converted to #fwrite() before passing on to the receiver.
    */

#write(Term) ->
    {write,Term}.
    /*  This is converted to #fwrite('~w',[Term]).
    */

/*  Group 2:  private  */

#io_request(From, ReplyAs, Request) when pid(From) ->
    {io_request,From,ReplyAs,Request}.
    /* From identifies the process the reply should be sent to.
       ReplyAs is basically a tag; it is passed back unchanged in the reply.
       Request is the actual request.
    */

#io_reply(ReplyAs, Reply) ->
    {io_reply,ReplyAs,Reply}.
    /* This is the reply that is sent back to From.  The form of the Reply
       is described with each Request.
    */

-------------- next part --------------
A non-text attachment was scrubbed...
Name: FINDABLE.TEX
Type: application/x-tex
Size: 51753 bytes
Desc: findable.tex
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20001201/41ab6699/attachment.tex>
-------------- next part --------------
Robert Virding                          Tel: +46 (0)8 545 55 017
Alteon Web Systems                      Email: 
S:t Eriksgatan 44                       WWW: http://www.bluetail.com/~rv
SE-112 34 Stockholm, SWEDEN
"Folk säger att jag inte bryr mig om någonting, men det skiter jag i".


More information about the erlang-questions mailing list