Created
September 25, 2011 00:37
-
-
Save amtal/1240052 to your computer and use it in GitHub Desktop.
Monad example in Erlang.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-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. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Was trying to figure out what
do
is andparse_transform
. I found the original code here:https://github.com/rabbitmq/erlando#the-inevitable-monad-tutorial