{"id":25773198,"url":"https://github.com/airtucha/amonad","last_synced_at":"2025-02-27T04:46:01.379Z","repository":{"id":57177378,"uuid":"196631402","full_name":"AIRTucha/amonad","owner":"AIRTucha","description":"Experimental implementation of Maybe and Result monads compatible with await.","archived":false,"fork":false,"pushed_at":"2020-09-09T20:36:24.000Z","size":76,"stargazers_count":30,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-08-08T22:20:56.607Z","etag":null,"topics":["async-await","await","error-handling","fp","functional-programming","javascript","js","maybe","monad","option","ts","typescript"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/amonad","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AIRTucha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-12T19:02:16.000Z","updated_at":"2024-07-11T13:54:17.000Z","dependencies_parsed_at":"2022-09-14T02:10:24.814Z","dependency_job_id":null,"html_url":"https://github.com/AIRTucha/amonad","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIRTucha%2Famonad","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIRTucha%2Famonad/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIRTucha%2Famonad/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AIRTucha%2Famonad/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AIRTucha","download_url":"https://codeload.github.com/AIRTucha/amonad/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240980888,"owners_count":19888344,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async-await","await","error-handling","fp","functional-programming","javascript","js","maybe","monad","option","ts","typescript"],"created_at":"2025-02-27T04:46:00.822Z","updated_at":"2025-02-27T04:46:01.359Z","avatar_url":"https://github.com/AIRTucha.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Amonad\n[![Build Status](https://travis-ci.org/AIRTucha/amonad.svg?branch=master)](https://travis-ci.org/AIRTucha/amonad) [![Coverage Status](https://coveralls.io/repos/github/AIRTucha/amonad/badge.svg?branch=master)](https://coveralls.io/github/AIRTucha/amonad?branch=master)\n\nImplementation of *Maybe* and *Result* monads compatible with async/await. Learn more about monads [here](https://dev.to/airtucha/functors-and-monads-in-plain-typescript-33o1).\n\n*Maybe* is a container dedicated for the handling of a data which might be missing. Typically, it is used for representation of optional values. It allows prevent usage of Nullable objects. *Result* is an expansion of *Maybe* which can additionally carry the reason of unavailability. It is mainly utilized to represent the output of an operation which might fail since *Result* is also capable of containing an error message. \n\nBoth of them implements a fraction of Promise's functionality. It allows us to model them via Promise. Therefore by the implementation of PromiseLike interface, they became compatible with async/await syntax.\n\n## Get started \n\nThe package is available via *npm*. It has to be installed as a local dependency:\n\n    npm install amonad\n\nEach of them can be represented by values of two types: *Just* and *None* for *Maybe*, *Success* and *Failure* for *Result*. The values have dedicated factory functions with correspondent names. They can also be created by a *smart factories* which are just *Maybe* and *Result*. They correctly create the monads based on the provided argument.\n\nA primary way to access inclosed values is *then()* method. It expects two optional arguments which are represented by functions which perform operations over the internal state of the containers. The first one processes the data, and the second is used for handling of its absents.\n\nAlso, there is an API for checking of an object type. It consist of *isMaybe*, *isJust*, *isNone*, *isResult*, *isSuccess* and *isFailure* functions which accept any object as an argument and returns result of the assertion as boolean value. Moreover, there are *isJust* and *isNone* methods for *Maybe*. Correspondingly, there are *isSuccess* and *isFailure* methods for *Result*. \n\nThe carried information can be accessed via *get* and *getOrElse* methods. The first one returns the value inclosed inside of the container. The second one returns only the primary value from the monad and falls back to the provided argument if the primary value is not available.\n\n## Usage\n\nTypical usage of *Maybe* and *Result* is very similar. Sometimes it is hardly possible to make a choice, but there is a clear semantic difference in the intention behind each of them. \n\n*Maybe*, primary, should represent values which might not be available by design. The most obvious example is return type of *Dictionary*:\n\n```typescript\ninterface Dictionary\u003cK, V\u003e {\n    set(key: K, value: V): void\n    get(key: K): Maybe\u003cV\u003e\n}\n```\n\nIt can also be used as a representation of optional value. The following example shows the way to model a *User* interface with *Maybe*. Some nationalities have a second name as an essential part of their identity other not. Therefore the value can nicely be treated as *Maybe\u003cstring\u003e*.\n\n```typescript\ninterface Client {\n    name: string\n    secondName: Maybe\u003cstring\u003e\n    lastName: string\n}\n```\n\nComputations which might fail due to obvious reason are also a good application for *Maybe*. Lowest common denominator might be unavailable. That is why the signature makes perfect sense for *getLCD()* function:\n\n```typescript\ngetLCD(num1: number, num2: number): Maybe\u003cnumber\u003e\n```\n\n*Result* is mainly used for the representation of value which might unavailable for an uncertain reason or for tagging of a data which absents can significantly affect execution flow. For example, some piece of class’s state, required for computation, might be configured via an input provided during life-circle of the object. In this case, the default status of the property can be represented by *Failure* which would clarify, that computation is not possible until the state is not initialized. Following example demonstrates the described scenario. The method will return the result of the calculation as *Success* or “Data is not initialized” error message as Failure. \n\n```typescript\nclass ResultExample {\n  value: Result\u003cValue, string\u003e = Failure(“Data is not initialized”)\n  \n  init( value: Value ) {\n    this.value = Success(value) \n  }\n\n  calculateSomethingBasedOnValue(){\n    return this.value.then( value =\u003e\n        someValueBasedComputation( value, otherArgs)\n     )\n  }\n}\n```\n\nMoreover, monadic error handling is able to replace exceptions as the primary solution for error propagation. Following example presents a possible type signature for a parsing function which utilizes Result as a return type.\n\n```typescript\nparseData( str: string ): Result\u003cData\u003e\n```\n\nThe output of such a function might contain processed value as *Success* or *Throwable* with an explanation of an error as *Failure*.\n\n### Creation\n\nAs I already said it is possible to instantiate *Maybe* and *Result* via factory functions. Different ways to create the monads is presented in the following code snippet.\n\n```typescript\nconst just = Just(3.14159265)\nconst none = None\u003cnumber\u003e()\nconst success = Success\u003cstring, Error\u003e(\"Iron Man\")\nconst failure: Failure\u003cstring, Error\u003e = Failure( new Error(\"Does not exist.\"))\n```\n\nNaN safe division function can be created using this library in the way demonstrated below.\n\n```typescript \nconst divide = (numerator: number, quotient: number ): Result\u003cnumber, string\u003e =\u003e \n    quotient !== 0 ?\n        Success( numerator/quotient )\n    :\n        Failure(\"It is not possible to divide by 0\")\n```\n\n*Smart Maybe factory* is handy if there is Nullable object which has to be wrapped by *Maybe*.\n\n```typescript \nconst maybe = Maybe(map.get())\n```\n\n*Smart Result factory* expects a function *() =\u003e T | Success\u003cT, E\u003e | Failure\u003cT, E\u003e* which might throw object of E type as exception. *Result* wraps around the output accordingly.\n\n```typescript\nconst data = Result( () =\u003e JSON.parse(inputStr) )\n```\n\n### Data handling\n\nThe first argument of *then()* method is handler responsible for processing of expected value. It accepts two kinds of output values: value of arbitrary, *monad* of the same type.\n```typescript \n// converts number value to string\nconst eNumberStr: Maybe\u003cstring\u003e = Just(2.7182818284)\n    .then( \n        eNumber =\u003e `E number is: ${eNumber}` \n    )\n// checks if string is valid and turns the monad to None if not\nconst validValue = Just\u003cstring\u003e(inputStr)\n    .then( str =\u003e \n        isValid(inputStr) ?\n            str\n            :\n            None\u003cstring\u003e()\n    )\n```\n\nThe content can also be access by *get()*, *getOrElse()* and *getOrThrow()* methods. *get()* output a union type of the value type and the error one for *Result* and the union type of the value and undefined for *Maybe*. The issue can be resolved by validation of the monad type by *isJust()* and *isSuccess()* methods or functions.\n\n```typescript\nif(maybe.isJust()) { // it is also possible to write it via isJust(maybe)\n    const value = maybe.get(); // return the value here\n    // Some other actions...\n} else {\n    // it does not make sense to call get() here since the output is going to be undefined\n    // Some other actions...\n}\n\nif(result.isSuccess()) { // it is also possible to write it via isSuccess(result)\n    const error = result.get(); // return the value here\n    // Some other actions...\n} else {\n    const value = result.get(); // return the error here\n    // Some other actions...\n}\n```\n\nThe main advantage of the library against other existing implementations of *Maybe/Option* and *Result/Try* monads for JavaScript is compatibility with *async/await* syntax. It is possible to *await* *Result* and *Maybe* inside of *async* functions. Anyway, the output is going to be wrapped by *Promise*.\n\n```typescript\nconst someStrangeMeaninglessComputations = async (num1: number, num2: number, num3: number ): Promise\u003cnumber\u003e =\u003e {\n    const lcd = await getLCD(num1, num2) // will throw undefined in case LCD does not exist\n    return await divide(lcd, num3)\n}\n```\n\n### Error handling \n\nThe second argument of the *then()* method is a callback responsible for the handling of unexpected behavior. It works a bit differently for *Result* and *Maybe*. *None* has no value, that's why its callback doesn't have an argument. Additionally, it doesn't accept mapping to the value, since it should produce another *None* which also cannot contain any data. But returning of Just might be utilized to recovery *Maybe*. It is also possible to pass a void procedure to perform some side effect, for example, logging. *Failure* oriented handler works a bit more similar to the first one. It accepts two kinds of output values: the value of Throwable and *monad* of the *Result* type.\n\n```typescript \n// tries to divide number e by n, recoveries to Infinity if division is not possible\nconst eDividedByN: Failure\u003cstring, string\u003e = divide(2.7182818284, n)\n    .then( \n        eNumber =\u003e `E number divided by n is: ${eNumber}`,\n        error =\u003e Success(Infinity)\n    )\n// looks up color from a dictionary by key, if color is not available falls back to black\nconst valueFrom = colorDictionary.get(key)\n    .then( \n        undefined,\n        () =\u003e Just(\"#000000\")\n    )\n```\n\nIt is also possible to verify if the monads are *Failure* or *None*, it can be done via *isNone()* and *isFailure()* methods and functions.\n\n```typescript\nif(maybe.isNone()) { // it is also possible to write it via isNone(maybe)\n    // it does not make sense to call get() here since the output is going to be undefined\n    // Some other actions...\n} else {\n    const value = maybe.get(); // return the value here\n    // Some other actions...\n}\n\nif(result.isFailure()) { // it is also possible to write it via isFailure(result)\n    const value = result.get(); // return the error here\n    // Some other actions...\n} else {\n    const error = result.get(); // return the value here\n    // Some other actions...\n}\n```\n\nAwaiting of *None* and *Failure* led to throwing of an exception inside of async function. *None* doesn't have inclosed value. Therefore *undefined* is thrown. *Failure* conveniently store *Throwable* object which fulfils its purpose in such an occasion. Beyond *async* function the error is propagated as rejected *Promise*.\n\n```typescript\nconst someStrangeMeaninglessComputations = async (num1: number, num2: number, num3: number ): Promise\u003cnumber\u003e =\u003e {\n    try {\n        const lcd = await getLCD(num1, num2) // will throw undefined in case LCD is not available for the values\n        return await divide(lcd, num3) // throws \"It is not possible to divide by 0\" in case num3 is 0\n    } catch(e) {\n        // it is possible to recovery normal workflow here\n        return someFallBackValue\n    }\n}\n```\n\n## API\n\nThe interfaces contains an up to a certain degree shared API as well as specific ones for *Maybe* and *Result*.\n\n### Commune\n\n#### Monad.prototype.then()\n\nAccordingly, applying the handlers produces a new Monadic as a container for the output of called function\n\nSignature for *Maybe* is:\n\n```typescript\nMaybe\u003cT\u003e.prototype.then\u003cTResult1 = T, TResult2 = never\u003e(\n    onJust?: (value: T) =\u003e TResult1 | Maybe\u003cTResult1\u003e,\n    onNone?: () =\u003e Maybe\u003cTResult2\u003e\n): Maybe\u003cTResult1 | TResult2\u003e \n```\n\nSignature for *Result* is:\n\n```typescript\nResult\u003cT, E\u003e.prototype.then\u003cTResult1 = T, EResult1 extends Throwable = E, TResult2 = never, EResult2 extends Throwable = never \u003e(\n    onSuccess?: (value: T) =\u003e TResult1 | IResult\u003cTResult1, EResult1\u003e,\n    onFailure?: (reason: E) =\u003e EResult2 | IResult\u003cTResult2, EResult2\u003e\n): Result\u003cTResult1 | TResult2, EResult1 | EResult2\u003e\n```\n\n#### Monad.prototype.get()\n\nIt returns the value enclosed inside a container.\n\nSignature for *Maybe* is:\n\n```typescript\nMaybe\u003cT\u003e.prototype.get(): T | undefined\n```\n\nSignature for *Result* is:\n\n```typescript\nResult\u003cT, E\u003e.prototype.get(): T | E\n```\n\n#### Monad.prototype.getOrElse()\n\nIt returns the inclosed primary value or the one provided as an argument.\n\nSignature for *Maybe* is:\n\n```typescript\nMaybe\u003cT\u003e.prototype.getOrElse( value: T ): T \n```\n\nSignature for *Result* is:\n\n```typescript\nResult\u003cT, E\u003e.prototype.getOrElse( value: T ): T  \n```\n\n#### Monad.prototype.getOrThrow()\n\nIt returns the value for *Just* and *Success* and throws the throwable for *Failure*, *None* throws an *undefined*.\n\n```typescript\nMonad\u003cT, E\u003e.prototype.getOrThrow( value: T ): T \n```\n\nIt might be useful for refactoring of a legacy codebase, since it simplifies implementation of interfaces with exceptions based error handling.\n\n### Maybe\n\n*Maybe\u003cT\u003e* itself represents a union type of Just\u003cT\u003e and None\u003cT\u003e.\n\nIt is also a *smart factory* which turns Nullable object to *Just\u003cT\u003e* or *None\u003cT\u003e* accordingly.\n\n```typescript\nMaybe\u003cT\u003e(value: T | undefined | null) =\u003e Maybe\u003cT\u003e\n```\n\n#### Just\n\nIt represents the value of a specified type. It can be created via a factory which wraps the value with *Just\u003cT\u003e*\n\n```typescript\nJust\u003cT\u003e(value: T) =\u003e Maybe\u003cT\u003e \n```\n\n#### None\n\nIt represents an absents of value with the specified type. It can be created via a factory with the specified type.\n\n```typescript\nNone\u003cT\u003e() =\u003e Maybe\u003cT\u003e \n```\n\n#### isJust\n\nIt exists as a stand-alone function which checks whether an object of any type is *Just*\n\n```typescript\nisJust\u003cT\u003e(obj: any): obj is Just\u003cT\u003e\n```\n\nMoreover *Maybe* has a method dedicated to the same goal.\n\n```typescript\nMaybe\u003cT\u003e.prototype.isJust(): obj is Just\u003cT\u003e\n```\n\n#### isNone\n\nIt exists as a stand-alone function which checks whether an object of any type is *None*\n\n```typescript\nisNone\u003cT\u003e(obj: any): obj is None\u003cT\u003e\n```\n\nMoreover *Maybe* has a method dedicated to the same goal.\n\n```typescript\nMaybe\u003cT\u003e.prototype.isNone(): obj is Just\u003cT\u003e\n```\n\n### Result\n\n*Result\u003cT, E\u003e* itself represents a union type of Success\u003cT, E\u003e and Failure\u003cT, E\u003e.\n\nIt is also a *smart factory* which calls provided function and stores its output as *Success\u003cT, E\u003e* or *Failure\u003cT, E\u003e* accordingly.\n\n```typescript\nMaybe\u003cT, E extends Throwable\u003e(action: () =\u003e T | Result\u003cT, E\u003e) =\u003e Result\u003cT, E\u003e\n```\n\n#### Success\n\nIt represents the value of a specified type. It can be created via a factory which wraps the value with *Success\u003cT, E\u003e*\n\n```typescript\nSuccess\u003cT, E extends Throwable\u003e(value: T) =\u003e Result\u003cT, E\u003e\n```\n\n#### Failure\n\nRepresents an error which explains an absents of a value. It can be created via a factory with the specified type.\n\n```typescript\nFailure\u003cT, E extends Throwable\u003e(error: E) =\u003e Result\u003cT, E\u003e\n```\n\n#### isSuccess\n\nIt exists as a stand-alone function which checks whether an object of any type is *Success*\n\n```typescript\nisSuccess\u003cT\u003e(obj: any): obj is Success\u003cT\u003e\n```\n\nMoreover, *Result* has a method dedicated to the same goal.\n\n```typescript\nResult\u003cT, E\u003e.prototype.isSuccess(): obj is Success\u003cT, E\u003e\n```\n\n#### isFailure\n\nIt exists as a stand-alone function which checks whether an object of any type is *Failure*\n\n```typescript\nisFailure\u003cT, E\u003e(obj: any): obj is Failure\u003cT, E\u003e\n```\n\nMoreover, *Result* has a method dedicated to the same goal.\n\n```typescript\nResult\u003cT, E\u003e.prototype.isFailure(): obj is Failure\u003cT\u003e\n```\n\n## Contribution guidelines\n\nThe project is based on *npm* eco-system. Therefore, development process is organized via *npm* scripts.\n\nFor installation of dependencies run\n\n    npm install\n\nTo build application once\n\n    npm run build\n\nTo build an application and watch for changes of files\n\n    npm run build:w\n\nTo run tslint one time for CI\n\n    npm run lint\n\nTo unit tests in a watching mode are performed by \n\n    npm run test\n    \nTo execute a test suit single time\n\n    npm run test:once\n\nTo execute a test suit single time with coverage report\n\n    npm run test:c\n\nTo execute a test suit single time with coverage report submitted to *coveralls*\n\n    npm run test:ci\n\nEverybody is welcome to contribute and submit pull requests. Please communicate your ideas and suggestions via *issues*.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fairtucha%2Famonad","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fairtucha%2Famonad","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fairtucha%2Famonad/lists"}