Package: algae
algaeAlgebraic Data Types for PythonThis repository contains an implementation from scratch of simple algebraic data types in Python.The following types are implemented and tested:EitherOptionTryThis library is inspired by Scala's implementation of these structures.Feel free to open an issue orsend me an emailin case you'd like to contribute or if you see something that can be improved.InstallationThis project is published onPyPiasalgaeso you can easily install it withpipas:pipinstallalgaeor withpoetryas:poetryaddalgaeSetupPoetryThis project usespoetryto manage its dependencies.
Please refer topoetry's official docfor more info.Usage ExamplesEitherEitherrepresents a value that can assume one of two types.Concrete instances are of typeLeftorRight.As an example, let's consider the case of making HTTP calls which might return a
status code representing an error as the url is user-defined.
If a call is successful, we want to return the JSON from the response, but if it's
not we'll map it to an internal error message.The examples use thisexample server.importrequestsfromalgae.eitherimportLeft,Right,EitherfromtypingimportDict,Anydefmap_response_to_msg(response:requests.models.Response):returnf"The{response.request.method}request to{response.url}couldn't be completed "\f"and returned a{response.status_code}status_code"defcall_and_check(url:str)->Either[str,Dict[Any,Any]]:response=requests.get(url)returnRight(response.json())ifresponse.okelseLeft(map_response_to_msg(response))Users of this method will then be able to further chain operations which can result in 2 different results easily,
keeping track of the error message identifying the step that returned something unexpected in the chain.base_url="https://jsonplaceholder.typicode.com"users_json=call_and_check(f"{base_url}/users")posts=users_json.flat_map(lambdajson:call_and_check(f"{base_url}/posts?userId={json[0]['id']}"))Lastly, we'll log the content of theEitherat the appropriate level in each case; the contained string in theLeftcase atwarn, or themsgfield of the JSON dictionary in theRightcase atinfo.fromloggingimportgetLoggerlogger=getLogger()posts.fold(logger.warning,lambdax:logger.info(x[0]["title"]))The above example enters theRightbranch of theEither, change thebase_urlto$base_url/pizzato get aLeftat the first stage.Please note that this is different from the case where anExceptionis raised, which better fits theTrystructure
described below.OptionOptionrepresents an optional value, its concrete instances are
of typeNothingorSome.As an example, let's consider the case of checking for a variable in a dictionary.
Normally, a default value ofNoneis returned if the request key is not present in the dictionary,
however this requires the user of method returning such a value to check explicitly the content of the return variable.Further, multiple calls of this type cannot be chained together, and the value needs to be checked every time.
UsingOptionwe can instead reason using the type directly, and demanding to it the checking steps.fromalgae.optionimportOptiond={"food":"Pizza"}result=Option.apply(d.get("another_key"))awesomize=lambdax:x+"is awesome"msg=result.map(awesomize)This way we didn't need to check whether the key was present in the dictionary or not.
Finally, we can get a default value to go from anOptionto astr.msg.fold("Pizza is incredible anyways!",lamdbax:x+", but fries are good too!")The finalmsgwill bePizza is incredible anyways!.If instead we had looked for thefoodkey,msgwould have beenPizza is awesome, but fries are good too!TryTryrepresents a computation that can either fail (raising an Exception) or return the resulting value.Concrete instances are of typeFailureorSuccess.As an example, let's see the case of a function that can raise an Exception:importmathdefunsafe_computation(value:int):returnmath.log(value)# this throws an Exception if value is <= 0Upon calling this function withvalue<= 0 we'll see:unsafe_computation(0)Traceback(mostrecentcalllast):File"<stdin>",line1,in<module>
ValueError:mathdomainerrorTo make this computation safe, even forvalue<= 0, we'll wrap its execution with Try:fromalgae.try_importTrysafe_result=Try.apply(unsafe_computation,0)safe_resultwill be of typeFailure, containing the Exception.
In case it was called on proper input:safe_result=Try.apply(unsafe_computation,1)safe_resultwill be of type Success and it will contain the proper result.Please notice that you need to pass the function and any function arguments, named and not, as arguments toTry.apply()rather than passingf(args).Alternatively, you can use this syntax:safe_result=Try.apply(lambda:unsafe_computation(0))UsingTry, an appropriate return type can be used for methods that might fail and raise anExeception,
leaving the user in charge of easily dealing with the subsequent behavior, for example:Try.apply(unsafe_computation,1).map(lambdax:x+1)Special thanks toDavid Cuthbertfor letting me have the "algae" name on PyPI.