34 | Not sure what purify is? Check out the{' '}
35 | Getting Started page. The package was
36 | renamed from `pure-ts` because of NSFW search results.
37 |
38 |
39 | NonEmptyList
40 |
41 | The new NonEmptyList ADT is a list that is guaranteed to have at least one
42 | value. Because of it's utility there is an implementation of this data
43 | structure in pretty much all ML languages, which is why it's now a part of
44 | purify too. Let's look at some example code:
45 |
46 | {`import { NonEmptyList, head } from 'purify-ts/adts/NonEmptyList'
47 |
48 | // Create functions with a contract - the caller has to verify that the input is valid instead of the callee
49 | // Since the list parameter is guaranteed to have at least one element, this function will always return a value
50 | const getRandomElement = (list: NonEmptyList): T =>
51 | list[Math.floor(Math.random() * list.length)]
52 |
53 | // Doesn't compile
54 | getRandomElement(NonEmptyList([]))
55 |
56 | // Compiles, you don't need to check for elements if the list length is known at compile time
57 | getRandomElement(NonEmptyList([1]))
58 |
59 | // For runtime values, you have to deal with a Maybe
60 | const numbers: number[] = getArrayFromForm()
61 | const randEl: Maybe = NonEmptyList.fromArray(numbers).map(getRandomElement)
62 | `}
63 |
64 |
65 |
66 | Maybe and Either predicates narrow the type
67 |
68 | v0.11 makes a lot of improvements to type safety. Using one of
69 | TypeScript's more unique features - type predicates, the compiler can now
70 | know when it's safe to extract a value from a Maybe or Either.
71 |
72 | {`const sometimesValue: Maybe = ...
73 |
74 | sometimesValue.extract() // number | null
75 |
76 | if (sometimesValue.isJust()) {
77 | // Because extract() is in a block that guarantees the presence of a value, it's safe to return a number instead of a nullable number
78 | sometimesValue.extract() // number
79 | }
80 | `}
81 |
82 |
83 |
84 | Wildcard pattern for pattern matching
85 |
86 | You can now use a wildcard when pattern matching a Maybe, Either or any
87 | other ADT that supports pattern matching.
88 |
89 | {` // v0.10
90 | adt.caseOf({ Just: value => 0, Nothing: () => 0})
91 |
92 | // Now
93 | adt.caseOf({ _: () => 0 })`}
94 |
95 |
96 |
97 | Tuple support for more array behaviour
98 |
99 | Tuples now implement the Iterable and ArrayLike interfaces.
100 |
101 |
102 |
103 | {` const [ fst, snd ] = Tuple(1, 2)`}
104 |
105 |
106 | New Maybe and Either methods
107 |
108 | Check out the docs for Maybe and Either to find out more about the
109 | following methods:
110 |
119 |
120 |
121 | Improved pretty printing
122 |
123 | When using toString on ADT instances now it displays the
124 | constructor name. Keep in mind that this behaviour is strictly for pretty
125 | printing, in the case of JSON.stringify it strips out any ADT
126 | info and leaves only relevant JSON data.
127 |
128 | {`const val = Just(5)
129 | console.log(val.toString()) // "Just(5)"
130 | console.log(JSON.stringify(val)) // "5"`}
131 |
132 |
133 |
134 |
135 | All functions with multiple arguments support partial application
136 |
137 |
138 |
139 | Added partial application support to: List#at
140 |
151 | Removed Semigroup and Ord instances because they
152 | were not sound and making them typesafe required using very confusing
153 | type definitions.
154 |
32 | Not sure what purify is? Check out the{' '}
33 | Getting Started page. Not sure if you
34 | want to introduce purify as a dependency to your project? Check out the
35 | new FAQ page!
36 |
37 |
38 | Before starting, I want to thank everyone that contributed to the project
39 | with bug reports, fixes and suggestions ⭐️.
40 |
41 |
42 | MaybeAsync and EitherAsync
43 |
44 | Dealing with asynchronous values was a huge pain point and I've spent a
45 | lot of time prototyping different solutions.
46 |
47 | The general approach to error handling in imperative languages is to throw
48 | exceptions, which didn't fit into the functional nature of purify.
49 |
50 | At the same time, TypeScript's type system made expressing functional
51 | patterns cumbersome, which didn't leave me with a lot of options.
52 |
53 | Despite those challenges I believe the final APIs for{' '}
54 | MaybeAsync and{' '}
55 | EitherAsync turned out fairly elegant
56 | and easy to use, please let me know your opinion!
57 |
58 |
59 | Complete rewrite
60 |
61 | Put simply, the library had too many issues mainly because of the
62 | "single-class" implementation of the ADTs, which have since been rewritten
63 | into plain functions and objects.
64 |
65 | This removed a whole class of errors (pun not intended), like a strange
66 | bug that prevented functions returning a Nothing to be annotated with the
67 | proper Maybe type (so strange I've filed{' '}
68 |
69 | an issue
70 | {' '}
71 | )
72 | This change is completely under the hood, the public API remains the same.
73 |
74 |
75 | Proper fantasy-land support
76 |
77 | All data types provided by purify now include a proper implementation of
78 | the `constructor` property which points to the type representative.
79 |
80 | As a bonus, there is also a Foldable instance for Tuple now!
81 |
82 |
83 |
84 | Typeclasses - scrapped.
85 |
86 | Id and Validation - removed.
87 |
88 |
89 | Old versions of purify exported interfaces which were designed to serve
90 | the purpose of typeclasses.
91 |
92 | There were numerous issues though - typeclasses like Monad could be easily
93 | represented as object methods, but functions like Applicative's `pure` (or
94 | `of` in fantasy-land) are meant to go on the type representative, not on
95 | the object. A Monad instance requires an Applicative instance which was
96 | unrepresentable in TypeScript's type system without resorting to
97 | techniques that don't fit into the "interfaces with generics" model.
98 | There's also the issues with typeclasses like Ord, Setoid and Semigroup
99 | which don't make much sense in JavaScript where you can compare all
100 | values.
101 |
102 |
103 | All of these things led to the removal of typeclasses from the library.
104 | With that went the Id datatype which serves no need anymore.
105 |
106 | Since typeclasses were also the justification for having folders in the
107 | library exports, now the folder structure is flat.
108 |
109 | This means that imports like{' '}
110 | {`import { Maybe } from 'purify-ts/adts/Maybe`} are now just{' '}
111 | {`import { Maybe } from 'purify-ts/Maybe'`}.
112 |
113 | The Validation module was removed for a completely different reason though
114 | - the API was just too limiting and ad-hoc, hopefully it will return soon
115 | in a better, more generic form.
116 |
117 |
118 | New Maybe methods
119 |
120 | The original focus for this release was better JS interop and before the
121 | implementation of MaybeAsync and EitherAsync took most of my time working
122 | on this project, two new methods were added to the Maybe data type.
123 |
124 |
125 |
126 | Maybe#chainNullable - The same as Maybe#chain but for
127 | functions that return a nullable value instead of Maybe.
128 |
129 |
130 | Maybe#extract - Now returns an undefined instead of null as
131 | undefined is used more often to reprent missing values.
132 |
133 |
134 | Maybe#extractNullable - The new name of Maybe#extract from
135 | previous versions of purify
136 |
137 |
138 |
139 |
140 | Other changes
141 |
142 |
143 |
144 | There is now a "Guides" section for each data type which will
145 | hopefully include a lot of useful information in the near future. Stay
146 | tuned.
147 |
148 |
149 | Docs are now part of the npm package, which means you should be
150 | getting more information in your editor during autocomplete.
151 |
152 |
153 | Fixed bug where Just(null) would be treated as{' '}
154 | Nothing.
155 |
32 | Not sure what purify is? Check out the{' '}
33 | Getting Started page. Not sure if you
34 | want to introduce purify as a dependency to your project? Check out the{' '}
35 | FAQ page!
36 |
37 | This release is a small one, it includes mostly utilities and typesafe
38 | versions of already existing JavaScript functions.
39 |
40 |
41 | New Function module
42 |
43 | Purify now has a general utility module called Function, it includes
44 | things like the identity function. As of this release it's quite small but
45 | hopefully it grows as TypeScript starts supporting more and more
46 | functional utilities like compose and pipe,{' '}
47 | check it out!
48 |
49 |
50 | More List functions
51 |
52 | The main goal of the List module is to provide typesafe alternatives of
53 | the built-in Array.prototype methods.
54 |
55 | With that in mind, List now includes find, findIndex and
56 | also immutable List.sort.
57 |
58 |
59 | Faster implementation
60 |
61 | Purify went throught another redesign in this release, the new class-based
62 | solution is what the original rewrite of purify in{' '}
63 | 0.12 should've been.
64 |
65 | Like last time, this redesign does not affect the public API of the
66 | library.
67 |
68 |
69 | )
70 |
71 | export default v013
72 |
--------------------------------------------------------------------------------
/site/src/pages/changelog/0.14.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import Link from 'gatsby-link'
4 | import Layout from '../../components/Layout'
5 | import SyntaxHighlighter from 'react-syntax-highlighter'
6 | import highlightStyle from 'react-syntax-highlighter/dist/esm/styles/hljs/googlecode'
7 |
8 | const Title = styled.h1`
9 | margin-bottom: 0;
10 | `
11 |
12 | const Subtitle = styled.div`
13 | padding-bottom: 30px;
14 | `
15 |
16 | const Topic = styled.h2`
17 | font-weight: normal;
18 | `
19 |
20 | const TopicDescription = styled.div`
21 | padding-right: 15%;
22 |
23 | @media only screen and (max-width: 768px) {
24 | padding-right: 0;
25 | }
26 | `
27 |
28 | const v014 = (props) => (
29 |
30 | Purify v0.14
31 | December 16, 2019
32 |
33 | Not sure what purify is? Check out the{' '}
34 | Getting Started page. Not sure if you
35 | want to introduce purify as a dependency to your project? Check out the{' '}
36 | FAQ page!
37 |
38 |
39 |
40 | New Codec module
41 |
42 | Purify now has a JSON validation/deserialization module called Codec. It's
43 | inspired by JSON decoders and encoders from Elm and various TypeScript
44 | libraries like io-ts and runtypes.
45 |
46 | Check it out!
47 |
48 |
49 | New, optimized build
50 |
51 | In 0.13.1, the build target was changed to "es5" to support IE11 and other
52 | older browsers.
53 | To balance out all of the regressions that resulted from this
54 | (bigger bundle, slower performance) GitHub user{' '}
55 | imcotton added an ES6 build
56 |
57 | which you can now use as of 0.14 if your project runs on the server or you
58 | don't care about browser compatibility.
59 |
60 |
61 | {`//Before
62 | import { Either } from 'purify-ts/Either'
63 |
64 | //After
65 | import { Either } from 'purify-ts/es/Either'`}
66 |
67 | The new "es" build is 30% smaller and hopefully a little bit faster.
68 |
69 |
70 | A lot of improvements to this site
71 |
72 | Although this is not related to the update, this website received a lot of
73 | typo/stylistic fixes, examples and notes added, and some embarrassing
74 | copy-paste errors were removed.
75 |
76 |
77 | Other changes
78 |
79 |
80 |
81 | The implementation of Either.lefts and Either.rights was refactored
82 |
83 |
84 | {`The Just constructor now returns the correct type (Maybe) instead of Just`}
85 |
86 |
87 | Added a .prettierrc file to the repository for consistency across
88 | contributions
89 |
34 | Not sure what purify is? Check out the{' '}
35 | Getting Started page. Also check out
36 | the FAQ page!
37 |
38 |
39 |
40 | Breaking change inside Codec
41 |
42 |
43 | Codec had a undefinedType codec
44 | which was insufficient as there was no way to create optional properties
45 | on an object.
46 |
47 | This was brought up in an issue and to resolve this I removed this codec
48 | in favor of a optional combinator, please check out{' '}
49 | the docs on how to use it.
50 |
51 | An updated Either/MaybeAsync API
52 |
53 | When I first started designing the API for Either and MaybeAsync I wanted
54 | to bring the ease of use of proper error handling within IO, just like it
55 | is in languages with do-notation and for-comprehensions.
56 | That's why the original API in version{' '}
57 | 0.12 only worked with async/await and I
58 | thought and this will be enough for most people.
59 |
60 | Turns out most people started creating wrappers to make it more chainable
61 | and issues started piling in GitHub, no one liked the "imperative" API.
62 |
63 | That's why I decided to spend some time brainstorming a new API, that
64 | didn't force people to use async/await, and this is the result:
65 |
66 | {`// async/await
67 | const deleteUser = (req) =>
68 | EitherAsync(async ({ liftEither, fromPromise, throwE }) => {
69 | const request = await liftEither(validateRequest(req))
70 |
71 | try {
72 | const user = await fromPromise(getUser(request.userId))
73 | } catch {
74 | throwE(Error.UserDoesNotExist)
75 | }
76 |
77 | return deleteUserDb(user)
78 | })`}
79 |
80 |
81 | {`// new API
82 | const deleteUser = (req) =>
83 | liftEither(validateRequest(req))
84 | .chain(request => fromPromise(() => getUser(request.userId)))
85 | .mapLeft(_ => Error.UserDoesNotExist)
86 | .chain(user => liftPromise(() => deleteUserDb(user)))`}
87 |
88 | This is stripped down version of the code, just to demonstrate the
89 | similarities, for the full version check out the updated documentation of{' '}
90 | EitherAsync and{' '}
91 | MaybeAsync.
92 | Both APIs will exist simultaneously and you're free to use whichever you
93 | like, much like how you can use both async/await and Promise.then.
94 |
95 | Other changes
96 |
97 |
24 | Not sure what purify is? Check out the{' '}
25 | Getting Started page. Also check out
26 | the FAQ page!
27 |
28 |
29 | This is a huge release with a lot of changes and I'm really exited! 0.16
30 | will be the final 0.x version before the official 1.0.0 release, you can
31 | think of 0.16 as a Version 1 RC.
32 |
33 | Breaking: Codec
34 |
35 |
36 |
37 | Renamed GetInterface to GetType.
38 |
39 |
40 | Running decode will not populate a field inside an object
41 | it's undefined, instead it will just leave it out.
42 |
43 |
44 |
45 | Breaking: Either and Maybe
46 |
47 |
48 |
49 | Removed internal __value property inside both Either and
50 | Maybe. It was not supposed to be used anyway so there shouldn't be any
51 | breakages.
52 |
53 |
54 | Running Either#unsafeDecode used to throw a generic error if
55 | the value inside was Left. That error is still there, but if the value
56 | is an instance of Error, it will throw the value instead.
57 | This makes debugging and logging easier.
58 |
66 | Removed liftPromise from both EitherAsync and MaybeAsync.
67 | With the addition of PromiseLike support this utility is just
68 | an alias for the normal constructors, making it redundant.
69 |
70 |
71 | Since PromiseLike is now supported in both modules you should
72 | be using the special constructors liftEither,{' '}
73 | liftMaybe and fromPromise way less now.
74 |
75 | Because of that they are now static methods (e.g. to use run{' '}
76 | EitherAsync.liftEither or MaybeAsync.fromPromise)
77 |
78 |
79 |
80 | Additions: EitherAsync and
81 | MaybeAsync (there are a lot)
82 |
83 |
84 |
85 | Both EitherAsync and MaybeAsync now extend and support the{' '}
86 | PromiseLike interface. This means you can await them and you
87 | can interchange *Async and PromiseLike in most utility methods.
88 | This is a huge win for productivity and reducing boilerplate, I hope
89 | we get to see cool examples of how this helps people.
90 |
91 |
92 | Both EitherAsync and MaybeAsync are now fantasy-land compatible.
93 |
114 | EitherAsync now has a looser type definition for{' '}
115 | EitherAsync#chain as it will merge the two errors together in
116 | an union type instead of showing a compiler error if the error types
117 | are different.
118 |
119 |
120 |
121 | Additions: Either and Maybe
122 |
123 |
128 | Added static methods to Either - isEither and{' '}
129 | sequence.
130 |
131 |
132 | Either now has a looser type definition for Either#chain as
133 | it will merge the two errors together in an union type instead of
134 | showing a compiler error if the error types are different .
135 |
136 |
137 | Either now has a runtime tag so that values are easier to debug
138 | (previously when you logged an Either you couldn't tell if it's Left
139 | or Right).
140 |
141 |
142 |
143 | Additions: Codec
144 |
145 |
146 |
147 | Added new codecs and combinators: nullable,{' '}
148 | enumeration, intersect.
149 |
150 |
151 | Added a new property of each codec - schema, it returns a
152 | JSON Schema V6 of that codec so that you can reuse validation in
153 | non-JavaScript environments (tools, other languages etc.).
154 |
155 |
156 | Added a new utility type, FromType, that helps with creating
157 | codecs based on existing types.
158 |
159 |
160 | Added a new utility function - parseError, that takes an
161 | error string generated after running decode on a value and
162 | returns a meta object
163 |
164 | which you can use to create all kinds of stuff - from custom error
165 | reporters to recovery mechanisms.
166 |
167 |
168 | If you use the maybe codec combinator inside a{' '}
169 | Codec.interface it will handle optional properties just like{' '}
170 | optional.
171 |
172 |
173 |
174 | Additions: Other
175 |
176 |
177 |
178 | Added two new methods to Tuple - some and every that
179 | act like the Array methods of the same name.
180 |
181 |
182 | Added a new utility function to NonEmptyList - tail.
183 |
184 |
185 |
186 | Bugfixes: Codec
187 |
188 |
189 |
190 | Fixed a critical bug in oneOf that caused encoding to fail.
191 |
10 | The library's overall goals are to continue bringing popular patterns
11 | from FP languages into TypeScript.
12 |
13 |
14 |
Q: How are new features decided and planned?
15 | Most of the development stems from dogfooding the library in personal
16 | projects.
17 | User feedback is also extremely important - check out the question at
18 | the bottom to see why.
19 |
20 |
21 |
22 | Q: Is this library intended to be used on the front-end or the
23 | back-end?
24 |
25 | Purify is intended to be used both on the front-end and the back-end.
26 |
27 | Everything from the build config to the API choices and features
28 | included is made with this in mind.
29 |
30 |
31 |
32 | Q: Is this library intended to be part of an ecosystem of likeminded
33 | libraries or handle additional functionality itself?
34 |
35 | Purify is intented to be a single library with a focus on general
36 | purpose API.
37 |
38 | Official bindings to popular libraries like React, Angular, Express etc.
39 | are not planned and most likely won't happen.
40 |
41 |
42 |
43 |
Q: What is the timeline for the new releases?
44 | There are no exact dates for upcoming versions of Purify.
45 |
46 | Now that the library is post-v1 you can expect a more irregular release
47 | schedule.
48 |
49 |
50 |
Q: Should I expect breaking changes?
51 | Situations in which you can definitely expect breaking changes are:{' '}
52 |
53 | There is a new TypeScript release that allows for more type safety
54 |
55 | There is a new TypeScript release that makes expressing certain
56 | constructs (like HKTs) possible
57 |
58 | ECMAScript proposals that allow for a more elegant API (like the
59 | pipeline operator) reach stage 3
60 | TL;DR - Breaking changes will be rare, but if the language evolves in a
61 | way that makes FP code easier to write then there will be changes for
62 | sure.
63 |
64 |
65 |
Q: What should future contributors focus on?
66 | Pull request for bugfixes or documentation improvements are always
67 | welcome, but trying to add new methods via a PR most likely won't be
68 | accepted.
69 |
70 | Sharing use cases or pain points are the fastest way to see something
71 | implemented in the library and I'll greatly appreciate it.
72 |
73 |
26 | Purify is a library for functional programming in TypeScript. Its purpose
27 | is to allow developers to use popular patterns and abstractions that are
28 | available in most functional languages. It is also{' '}
29 |
30 | Fantasy Land
31 | {' '}
32 | conformant.
33 |
Core values
34 |
35 |
36 | Elegant and developer-friendly API - Purify's design decisions
37 | are made with developer experience in mind. Purify doesn't try to
38 | change how you write TypeScript, instead it provides useful tools for
39 | making your code easier to read and maintain without resolving to
40 | hacks or scary type definitions.
41 |
42 |
43 | Type-safety - While purify can be used in vanilla JavaScript,
44 | it's entirely written with TypeScript and type safety in mind. While
45 | TypeScript does a great job at preventing runtime errors, purify goes
46 | a step further and provides utility functions for working with native
47 | objects like arrays in a type-safe manner.
48 |
49 |
50 | Emphasis on practical code - Higher-kinded types and other
51 | type-level features would be great additions to this library, but as
52 | of right now they don't have reasonable implementations in TypeScript.
53 | Purify focuses on being a library that you can include in any
54 | TypeScript project and favors clean and readable type definitions
55 | instead of advanced type features and a curated API instead of trying
56 | to port over another language's standard library.
57 |
58 |
59 |
How to start?
60 | Purify is available as a package on npm. You can install it with a package
61 | manager of your choice:
62 |
63 | $ npm install purify-ts $ yarn add purify-ts
64 |
65 | On the left sidebar you can find all of purify's contents, each page
66 | contains a guide on how to start using it.
67 | You can start by visiting the page about{' '}
68 | Maybe, one of the most popular data types.
69 |
70 | If you are worried about the future of the project, because perhaps you
71 | are evaluating its usage in a large project, consider checking out the{' '}
72 | FAQ.
73 |
74 |
75 | )
76 |
77 | export default GettingStarted
78 |
--------------------------------------------------------------------------------
/site/src/pages/guides/maybe-api-guide.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import Layout from '../../components/Layout'
4 | import { HL } from '../../components/HL'
5 | import Link from 'gatsby-link'
6 | import SyntaxHighlighter from 'react-syntax-highlighter'
7 | import highlightStyle from 'react-syntax-highlighter/dist/esm/styles/hljs/googlecode'
8 |
9 | export const Note = styled.div`
10 | display: inline-block;
11 | background-color: #fcf4cd;
12 | border: 0 solid #f7e070;
13 | border-left-width: 8px;
14 | padding: 10px;
15 | margin: 10px 0;
16 | overflow-x: auto;
17 |
18 | @media only screen and (max-width: 768px) {
19 | display: block;
20 | }
21 | `
22 |
23 | const MethodName = styled(Link)`
24 | font-size: 17px;
25 | font-weight: bold;
26 | color: #3b74d7;
27 | margin-top: 5px;
28 | display: inline-block;
29 | text-decoration: none;
30 |
31 | &:hover {
32 | text-decoration: underline;
33 | }
34 | `
35 |
36 | const SmallMethodName = styled(MethodName)`
37 | font-size: initial;
38 | font-weight: initial;
39 | margin-top: 0;
40 | `
41 |
42 | const MaybeApiGuide = (props) => (
43 |
44 |
Which Maybe method am I supposed to use now? (API guide)
45 | We've all been in that research phase where we're still learning the API of
46 | library and deciding if it suits our usecases.
47 | The purpose of this guide is to make that process easier by grouping all
48 | available methods for the Maybe data type.
49 |
50 | Scenario #1 - I want to use Maybe but my codebase already has
51 | null/undefined all over the place
52 |
53 | One of purify's main goals is great interoperability with existing code.
54 | That is why the API for Maybe is rich in utility methods for working with
55 | nullable values.
56 |
57 | One might question the usage of Maybe (and purify) if you are still going
58 | to use nulls, there are already a lot of utility libraries like ramda and
59 | lodash that allow you to do that.
60 | With purify you can start using ubiquitous data structures that come with
61 | a lot of literature and examples in various programming languages (in this
62 | case Maybe)
63 | without sacrificing coding style or ease of interop, that's why using it
64 | instead of other libraries might be a good idea.
65 |
66 |
67 |
68 | Maybe.fromNullable
69 | /{' '}
70 | Maybe.fromFalsy /{' '}
71 | Maybe.fromPredicate{' '}
72 | / Maybe.encase
73 |
74 | These methods allow you to construct Maybe values from, as the names
75 | suggest, nullable and falsy values or in the case of the{' '}
76 | encase method -
77 | from a function that may throw an exception.
78 | `fromPredicate` is on the list because it can be used to cover all kinds of
79 | complicated checks, for example:
80 |
81 | {`const _ = Maybe.fromPredicate(x => x && x.length > 0, value)`}
82 |
83 | chainNullable
84 |
85 | Now that you have constructed your Maybe out of an optional value, you may
86 | want to transform it with a function that returns yet another optional
87 | value.
88 | If you are already familiar with the{' '}
89 | chain method
90 | (a.k.a. bind, flatMap or {'>>='}) you may think
91 | of using it in combination with any of the methods mentioned above:
92 |
93 | {`myMaybe.chain(x => Maybe.fromNullable(transform(x)))`}
94 |
95 | There's nothing wrong with that approach, but there's a helper method called
96 | `chainNullable` that does exactly the same thing
97 | without you having to manually construct a Maybe out of the return value of
98 | the transformation function.
99 |
100 | {`myMaybe.chainNullable(x => transform(x))
101 | // or just straight up
102 | myMaybe.chainNullable(transform)`}
103 |
104 | extract /{' '}
105 | extractNullable /{' '}
106 | unsafeCoerce
107 |
108 | Sometimes you have to interact with code that expects a nullable value, in
109 | that case you can just unwrap a Maybe down to a primitive value like null or
110 | undefined using the methods above.
111 |
112 | Please note that while you may be tempted to wrap and unwrap manually
113 | every time you encounter a nullable value,
114 | consider that code designed with Maybe in mind is easier to maintain and
115 | use in the long term.
116 | Try to keep usage of the methods mentioned in this part of the guide low
117 | and only for compatibility reasons.
118 | Don't be afraid to start returning or expecing Maybe values in functions,
119 | you'll notice some benefits you haven't considered before!
120 |
121 |
Scenario #2 - I'm not sure how to check if a value exists or not
122 | There are numerous ways to check if a value exists with purify, but I want
123 | to focus on the fact that you rarely need to do so explicitly.
124 |
125 | Try to split up your code into functions and then find ways to combine them
126 | using many of the available transformation methods like
127 |
128 | Maybe#map or{' '}
129 | Maybe#chain or{' '}
130 | Maybe#extend or{' '}
131 | Maybe#filter...
132 | you get the point.
133 |
134 | There are so many methods you can chain so that your code is nice and
135 | declarative that you'll almost never have to unpack a Maybe and check
136 | manually.
137 |
138 | There are some cases where that is needed though, let's go through them:{' '}
139 |
140 | Maybe#isJust /{' '}
141 | Maybe#isNothing
142 |
143 | The most primitive of the bunch, these methods enable us to do JS-style
144 | checking if a value is missing or not.
145 |
146 | The method names are pretty self-explanatory so we won't go into much
147 | details, but it's generally not recommend to use those methods.
148 |
149 | Better choices are almost always available.
150 |
151 |
152 | Maybe#caseOf /{' '}
153 | Maybe#reduce
154 |
155 | caseOf is the
156 | go-to choice when none of the other methods seem good enough.
157 |
158 | Since pattern matching is still not available (yet) in JavaScript, caseOf
159 | tries to mimic this behaviour, allowing you to branch your logic by asking
160 | you for two functions that will handle each case.
161 |
162 | reduce is very,
163 | very similar, in fact it's so similar that it looks almost useless. The goal
164 | of reduce is to provide an instance for the Foldable typeclass for Maybe.
165 |
166 | If you like the minimalism of reduce and you don't care about Foldable or
167 | you haven't heard of it - no problem, you can use it instead of caseOf just
168 | fine!
169 |
170 |
171 | )
172 |
173 | export default MaybeApiGuide
174 |
--------------------------------------------------------------------------------
/site/src/pages/guides/maybeasync-eitherasync-for-haskellers.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Layout from '../../components/Layout'
3 | import { HL } from '../../components/HL'
4 | import SyntaxHighlighter from 'react-syntax-highlighter'
5 | import highlightStyle from 'react-syntax-highlighter/dist/esm/styles/hljs/googlecode'
6 | import { Note } from './maybe-api-guide'
7 |
8 | const MaybeApiGuide = (props) => (
9 |
10 |
MaybeAsync and EitherAsync for Haskellers
11 |
12 | Keep in mind a lot of stuff have changed since this was written (back in
13 | January 2019), Either and MaybeAsync evolved to be more distant than the
14 | monad transformer design in late 2020
15 | and instead adopted the PromiseLike interface to make it easier to
16 | work with and reduce the large amount of boilerplate the original
17 | implementation required.
18 |
19 |
20 | As mentioned in the description of those data types, MaybeAsync and
21 | EitherAsync are funky Promise-specialized monad transformers for Maybe and
22 | Either.
23 |
24 | Some things may feel out of place and that is completely intentional,
25 | porting monad transformers over to TypeScript was just not practical,
26 | especially the higher-kinded types and typeclasses part.
27 | A lot of thought went into designing the APIs and I believe that the
28 | result is satisfactory. In fact, even though the implementation is
29 | completely different, code written in mtl style looks pretty similar! Here,
30 | take a look:
31 |
32 | {`tryToInsertUser user = runExceptT $ do
33 | validatedUser <- liftEither $ validateUser user
34 | userExists <- lift $ doesUserAlreadyExist validatedUser
35 |
36 | when userExists (throwE UserAlreadyExists)
37 |
38 | maybeToExceptT ServerError $ do
39 | updatedUser <- MaybeT $ hashPasswordInUser user
40 | lift $ insertUser updatedUser`}
41 |
42 | Keep in mind this code is not representative of the perfect or cleanest
43 | implementation for such a feature, I tried to shove as much functions, that
44 | are also possible in Maybe-EitherAsync, as I could.
45 |
46 | Here's the same logic implemented with purify in TypeScript:
47 |
48 | {`const tryToInsertUser = user =>
49 | EitherAsync(async ({ liftEither, throwE, fromPromise }) => {
50 | const validatedUser = await liftEither(validateUser(user))
51 | const userExists = await doesUserAlreadyExist(validatedUser)
52 |
53 | if (userExists) throwE('UserAlreadyExists')
54 |
55 | return fromPromise(MaybeAsync(async ({ fromPromise }) => {
56 | const updatedUser = await fromPromise(hashPasswordInUser(user))
57 | return insertUser(updatedUser)
58 | }).toEitherAsync('ServerError').run())
59 | })`}
60 |
61 | One important thing to understand about Maybe and EitherAsync is that the
62 | docs and the API create the illusion that code is running in some custom
63 | magical context that lets you safely unwrap values.
64 |
65 | Is it referred to as "MaybeAsync context" or "EitherAsync context", but in
66 | fact there's no magic and the only real context is the async/await block.
67 |
68 | That allows us to simulate do-notation using await and what those "lifting"
69 | function actually do is return Promises that get rejected when a value is
70 | missing.
71 | The `run` function will later on catch all those rejections and return a
72 | proper Maybe/Either value.
73 |
78 | liftEither/Maybe = liftEither/Maybe (MaybeT/ExceptT . return in
79 | Haskell, but nothing like that in purify, they function the same though)
80 |
81 |
82 | fromPromise = the MaybeT/ExceptT constructor (you only need to wrap the
83 | IO action with the newtype in Haskell, in purify it's not as simple)
84 |