Skip to content

Instantly share code, notes, and snippets.

@amtal
Created September 25, 2011 00:37
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amtal/1240052 to your computer and use it in GitHub Desktop.
Save amtal/1240052 to your computer and use it in GitHub Desktop.
Monad example in Erlang.
-module(fileop).
-export([write_file/3]).
-compile({parse_transform,do}).
%% Uses an error monad to neatly compose a bunch of failing functions.
%%
%% Everything being composed returns ok|{ok,Result}|{error,Reason}. At
%% the first error, the reason term is returned. The monad factors out
%% the behaviour of piping all possible errors to the output (via a
%% try-throw or case tree) if they occur.
-type error_m(Result,Reason) :: ok | {ok,Result} | {error,Reason}.
%%
%% Here we know the underlying representation of the monad. We don't
%% necessarily need to! A "run" function can be added that turns the
%% opaque monad into a familiar type.
%%
%% -opaque error_m(Result,Reason).
%% -spec run_error_m(error_m(Result,Reason)) ->
%% ok | {ok,Result} | {error,Reason}.
%%
%%
%% Some command line examples:
%%
%% 1> fileop:write_file(".","foo",[]).
%% {error,eisdir}
%% 2> fileop:write_file("foo.txt",[foo],[]).
%% {error,{not_iolist,[foo]}}
%% 3> fileop:write_file("foo.txt","foo",[lock]).
%% {error,badarg}
%% 4> fileop:write_file("foo.txt","foo",[]).
%% ok
%%
%% Note the syntax being reminiscent of list comprehensions. LCs are
%% using the List monad.
-spec write_file(term(), term(), [atom()]) -> error_m(ok, term()).
write_file(Path, Data, Modes) ->
Modes1 = [binary, write | (Modes -- [binary, write])],
do([error_m ||
Bin <- make_binary(Data),
Hdl <- file:open(Path, Modes1),
Result <- do([error_m ||
ok <- file:write(Hdl, Bin),
file:sync(Hdl)]),
file:close(Hdl),
Result]).
%% Example of returning error monad values.
%%
%% Successes are done with return/1 and failures with fail/1. They take a value
%% and wrap it in the monad. (The fail call is unpopular in Haskell due to having
%% an unsafe crash-everything implementation in many monads. This isn't an issue
%% for us now.)
%%
%% Using return/1 and fail/1 is opaque. We can also use the underlying
%% implementation, which we happen to know.
-spec make_binary(atom()|list()|binary()) -> error_m(binary(), term()).
make_binary(Atom) when is_atom(Atom) ->
error_m:return(atom_to_binary(Atom,latin1));
make_binary(Bin) when is_binary(Bin) ->
do([error_m || return(Bin)]); % do-syntax adds monad module to return call
make_binary(N) when is_number(N) ->
error_m:fail({bad_binary,N});
make_binary(List) when is_list(List) ->
try {ok,iolist_to_binary(List)}
catch error:_ -> {error, {not_iolist,List}}
end.
@aaronlelevier
Copy link

Was trying to figure out what do is and parse_transform. I found the original code here:

https://github.com/rabbitmq/erlando#the-inevitable-monad-tutorial

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment