bae15b3330668bb301b8a53473feb83913050920
—
Sam A. Horvath-Hunt
3 years ago

Initial w/ preexisting

4 files changed,450insertions(+),0deletions(-) A README.md A published/20200221-fp-ts-composition.md A published/20200330-js-fp-jargon.md A published/20200602-function-domain.md

A => README.md +4 -0

@@ 1,4 @@# Writings All my blog posts, stored here as markdown files for portability and redundancy. Currently published on [dev.to/samhh](https://dev.to/samhh).

A => published/20200221-fp-ts-composition.md +111 -0

@@ 1,111 @@Preface: All functions should strive to be pure, meaning they perform no side effects - all this means is that you take some data and return some data, and you don’t do anything outside the confines of said data. This post is written with this context in mind. The most fundamental aspect of FP that brings it all together is the idea of function composition - composing functions together like LEGO. Firstly, let’s look at it quickly in abstract: ```haskell ab :: a -> b bc :: b -> c ac :: a -> c ac = bc . ab ``` For the sake of understanding composition, we don’t need to think about what specifically `a`, `b`, and `c` represent, they could be any concrete type. Think abstract, think algebraic, think generic. We have a function from `a` to `b` (`a -> b`), and a second function from `b` to `c` (`b -> c`) (implementations irrelevant). We then _compose_ these two functions together, creating a third new function from `a` to `c` (`a -> c`) (Haskell’s function composition operator, `.`, is right-to-left - Haskell is largely derived from mathematics). Here’s how we’d write this in vanilla JavaScript without composition: ```javascript const ab = (a) => /* impl returns b */; const bc = (b) => /* impl returns c */; const ac = (a) => bc(ab(a)); ``` This works, but it’s a bit hard to read with the nesting. And what happens if we add a third intermediary function `c -> d`? It becomes yet worse. This is where composition can come into play. Here’s what `ac` looks like rewritten with the [flow](https://gcanti.github.io/fp-ts/modules/function.ts.html#flow) composition function from [fp-ts](https://gcanti.github.io/fp-ts/) (thankfully left-to-right!): ```typescript const ac = flow(ab, bc); ``` As alluded to this is far friendlier to extension, and it is - once you understand how composition works - objectively far more readable; you can read it from left to right like prose. Here’s precisely what happens when we call `ac`: We pass it an `a` as an argument, and `flow` passes that onto `ab`. `ab` returns a `b`, which is then passed onto the next function in the list, `bc`. `bc` returns a `c`, and as it’s the final function in the composition, this return value is what we finally get back. So, as described earlier, this function takes an `a` and returns a `c`! It might also help to think about the composition in terms of its functions: `(a -> b), (b -> c)` When we compose, we essentially just provide whatever the first function wants and get back whatever the last function returns. In this case, that’s `a` and `c` respectively, hence `a -> c`. Were our composition `(string[] -> number), (number -> string), (string -> boolean)`, then our new function signature having composed these together would be `string[] -> boolean`. This is all `flow` is - it’s for composing functions together. Let’s move onto a more tangible example and think about how we could improve it with composition. Here, we have an array of numbers, and (for some business logic reason) we want to map over the array, adding five to each number, then doubling it, and then finally converting it to a string. Here’s a barebones, non-functional approach: ```typescript const xs = [5, 10, 25]; const ys = xs.map(x => String((x + 5) * 2)); ``` What’s wrong with this? Well, for starters, I’m finding the logic inside of the map callback really hard to read. I have to pause and think about it for a minute. So let’s improve this by writing some small functions that can encapsulate what’s happening here: ```typescript const double = (x: number): number => x * 2; const plus = (x: number) => (y: number): number => x + y; const toString = (x: number): string => String(x); const xs = [5, 10, 25]; const ys = xs.map(x => toString(double(plus(5)(x)))); ``` We’re not really seeing the fruits of our labour yet, particularly in this trivial example. But, if we introduce function composition, it starts to make some sense and look a lot better than what we had before: ```typescript const double = (x: number): number => x * 2; const plus = (x: number) => (y: number): number => x + y; const toString = (x: number): string => String(x); const xs = [5, 10, 25]; const ys = xs.map(flow(plus(5), double, toString)); ``` We can now read this in words from left-to-right - we plus five, we double, we (convert) to a string. There can be no ambiguity that we take a number, perform these operations as described, and finally get back a string. And, if we want to, we can easily abstract this logic out - it’s just an expression: ```typescript const double = (x: number): number => x * 2; const plus = (x: number) => (y: number): number => x + y; const toString = (x: number): string => String(x); const myBusinessLogic = flow(plus(5), double, toString); const xs = [5, 10, 25]; const ys = xs.map(myBusinessLogic); ``` This readability delta over the imperative approach only increases as you add extra steps and/or extra complexity (read: real-world business logic). Anyway, why am I harping on about readability so much? Why am I obsessed with type safety? Why have I been naturally drawn to pure functional programming? In my opinion, our minds are quite limited in what they can think about at any given time, and the extent to which they can think about anything infallibly. Writing code in this style allows you to take mental shortcuts as a developer that you can’t take with imperative, mutable code. It also provides inherent guardrails against common sources of bugs, be they type or logic-related (and where possible encoding logic errors into the type system, hence the existence of types like `NonEmptyArray`). Back to TypeScript, there is a second type that fp-ts provides for function composition that is only subtly different from `flow` - [pipe](https://gcanti.github.io/fp-ts/modules/pipeable.ts.html#pipe). This function is identical to `flow`, except that it takes the value to be piped through our composition immediately, whereas `flow` took it afterwards. Whilst `flow` made sense for the above example we wrote, let’s rewrite it with `pipe` so that we can build an intuition for the difference between them: ```typescript xs.map((x) => pipe(x, plus(5), double, toString)) ``` If it’s still not quite clicking, here’s flow again, also rewritten more explicitly/verbosely: ```typescript xs.map(flow(plus(5), double, toString)) xs.map((x) => flow(plus(5), double, toString)(x)) ``` So, whereas `flow` creates a new composed function that takes its first argument after-the-fact, `pipe` needs to be provided with said argument immediately as its first argument before proceeding through the pipeline. I hope this explanation helped. As with many things, I think sitting down for an hour and just playing around with it is sometimes to best way to learn something new, but that differs from person to person. Please comment if there’s anything that remains unclear. Oh, and as of time of writing, there is a [stage 1 proposal](https://github.com/tc39/proposal-pipeline-operator) for a pipeline operator in JavaScript that'd effectively replace our usage of `pipe`. Yay!

A => published/20200330-js-fp-jargon.md +266 -0

@@ 1,266 @@If you're looking into functional programming for the first time, the terminology can be really overwhelming. I think one of the easiest ways to learn is to try and map the terms to concepts that you likely already know and then branch out from there. All of these terms have _laws_ that express limitations which ensure that all instances behave reasonably. We shan't go over them here, but it's good to know that - even if we're not ready to look into them yet - they exist, that there is a rich mathematical backing to these concepts. If this piques your curiosity at all, the best resource is probably [Typeclassopedia on HaskellWiki](https://wiki.haskell.org/Typeclassopedia). All of these examples will be written in both Haskell and TypeScript. The latter will be written with the [fp-ts](https://gcanti.github.io/fp-ts) library. For some reason, different languages sometimes call the same concepts different things. For example, Haskell has the `Maybe` type, whilst Rust and fp-ts have the identical `Option` type. Equally, Haskell and fp-ts have the `Either` type, whilst Rust has opted to call it `Result`. Don't let this discrepancy throw you off, they're otherwise identical. Without any further ado let's get started! ## Functor A functor is some sort of container that allows you to map its contents. Arrays are the prototypical functor: ```haskell (*2) <$> [1, 2, 3] -- [2, 4, 6] ``` ```typescript [1, 2, 3].map(x => x * 2) // [2, 4, 6] ``` Here we've taken each item in our array and applied our function to it. The very same concept applies to types like `Option`: ```haskell (*2) <$> (Just 5) -- Just 10 (*2) <$> Nothing -- Nothing ``` ```typescript option.map(some(5), x => x * 2) // Some 10 option.map(none, x => x * 2) // None ``` If the value is `Some`, then we map the inner value, else if it's `None` then we short-circuit and essentially do nothing. There's nothing that technically says that functors have to map over `Some` in the case of `Option`, or `Right` in the case of `Either`, except it's universally expected behavior and to do otherwise would be very odd. ## Bifunctor For types with (at least) two variants which you might want to map, for example tuples, or `Either` with its `Left` and `Right` variants, there is the concept of a _bifunctor_. This is the very same as functor, except as the name implies you can map "the other side" as well: ```haskell first (*2) (Left 5) -- Left 10 first (*2) (Right 5) -- Right 5 second (*2) (Left 5) -- Left 5 second (*2) (Right 5) -- Right 10 ``` ```typescript either.mapLeft(left(5), x => x * 2) // Left 10 either.mapLeft(right(5), x => x * 2) // Right 5 either.map(left(5), x => x * 2) // Left 5 either.map(right(5), x => x * 2) // Right 10 ``` ## Monad Ah, the scary sounding one, the monad! Monads build atop functors with one important addition, the idea of _joining_ or flattening. As with the functor, we'll start by demonstrating how arrays are also monads: ```haskell join [[1, 2], [3, 4]] -- [1, 2, 3, 4] ``` ```typescript [[1, 2], [3, 4]].flat() // [1, 2, 3, 4] ``` And likewise with nested `Option`s: ```haskell join (Just (Just 5)) -- Just 5 join (Just (Nothing)) -- Nothing join Nothing -- Nothing ``` With this newfound ability to flatten things, we can now also _bind_ or chain things. Let's imagine we have a function `parse` which takes a `string`, tries to parse it as a `number`, and returns `Option<number>`, and to start with we have an `Option<string>`. Thus far, the only way we could make this work would be to map with a functor, giving us back `Option<Option<number>>`, and then join down to `Option<number>`. That works, but is a bit tedious and we can imagine needing to perform this combination of operations quite often. This is what bind is for! ```haskell Just "5" >>= parse -- Just 5 Just "x" >>= parse -- Nothing Nothing >>= parse -- Nothing ``` ```typescript option.chain(some('5'), parse) // Some 5 option.chain(some('x'), parse) // None option.chain(none, parse) // None ``` What else do we know in JavaScript-land which is monad-like? The promise! A promise is - imprecisely - a functor, a bifunctor, and a monad, among other things. When we `.then`, we're either functor mapping or monad binding depending upon whether we're returning another promise (JavaScript handles this implicitly), and when we `.catch` we're either bifunctor mapping or sort of monad binding over the left side. Promises aren't _really_ monads because of these slightly different behaviours, but they absolutely are analagous. Further, async/await is like a specialised form of Haskell's [do notation](https://wiki.haskell.org/Typeclassopedia#do_notation). In this example in Haskell, `IO` is just another monad, but _any_ monad supports this syntax: ```haskell f :: String -> IO Int f x = do res <- getData x res * 2 ``` ```typescript const f = async (x: string): Promise<number> => { const res = await getData(x); return res * 2; }; ``` Before we move on, if you were wondering why JavaScript's promise isn't a proper functor or monad, here's the legacy of that unfortunate decision: {% github https://github.com/promises-aplus/promises-spec/issues/94#issuecomment-16176966 %} It hasn't aged particularly well. This also happens to be whence the [fantasy-land](https://github.com/fantasyland/fantasy-land) specification derived its name. ## Semigroup Semigroups define how to concatenate two items of the same type. For example, arrays are semigroups: ```haskell [1, 2] <> [3, 4] -- [1, 2, 3, 4] ``` ```typescript [1, 2].concat([3, 4]) // [1, 2, 3, 4] ``` You could equally define a semigroup instance for numbers under addition and multiplication, or for booleans under conjunction and disjunction. If the underlying mathematics interest you, you can read more about semigroups on [Wikipedia](https://en.wikipedia.org/wiki/Semigroup). We can also define semigroups for arbitrary types! Let's imagine we have the type `Cocktail`, and we want to be able to combine any two of them together. Given a definition for the type as follows: ```haskell data Cocktail = Cocktail { name :: String , ingredients :: [String] } ``` ```typescript type Cocktail = { name: string; ingredients: string[]; }; ``` We can then define a formal semigroup instance which will allow us to combine any pair of cocktails together: ```haskell instance Semigroup Cocktail where a <> b = Cocktail (name a <> " " <> name b) (ingredients a <> ingredients b) mojito = Cocktail "Mojito" ["rum", "mint"] robroy = Cocktail "Rob Roy" ["scotch", "bitters"] combined = mojito <> robroy -- Cocktail { name = "Mojito Rob Roy", ingredients = ["rum", "mint", "scotch", "bitters"] } ``` ```typescript const semigroupCocktail: Semigroup<Cocktail> = { concat: (a, b) => ({ name: a.name + ' ' + b.name, ingredients: a.ingredients.concat(b.ingredients), }), }; const mojito: Cocktail = { name: 'Mojito', ingredients: ['rum', 'mint'] }; const robroy: Cocktail = { name: 'Rob Roy', ingredients: ['scotch', 'bitters'] }; const combined = semigroupCocktail.concat(mojito, robroy); // { name: 'Mojito Rob Roy', ingredients: ['rum', 'mint', 'scotch', 'bitters'] } ``` ## Monoid Like how the monad derives most of its abilities from the functor, as does the monoid from the semigroup. A monoid is a semigroup with one extra thing - an _identity_ element, which means essentially a sort of "default" element which, when concatenated with others of its type, will result in the same output. Here are some example identity elements in mathematics: - Addition/subtraction: `0`, `5 + 0 == 5` & `5 - 0 == 5` - Multiplication/division: `1`, `5 * 1 == 5` & `5 / 1 == 5` See how when we apply the identity element to an operation alongside _n_ we always get said _n_ back again. We can do the same thing with types when we're programming. Once again, let's start with arrays: ```haskell [1, 2] <> [] -- [1, 2] ``` ```typescript [1, 2].concat([]) // [1, 2] ``` If we concatenate an empty array with any other array, we'll get said other array back. The same goes for strings which can be thought of conceptually as arrays of characters, which happens to be exactly what they are in Haskell. What about our `Cocktail` type from earlier? Given the two fields are each already monoids, or easy to treat as monoids - a string and an array - this will be quite simple: ```haskell instance Monoid Cocktail where mempty = Cocktail mempty mempty ``` ```typescript const monoidCocktail: Monoid<Cocktail> = { ...semigroupCocktail, empty: { name: '', ingredients: [] }, }; ``` This is cool, but truth be told it's relatively rare that we need to concatenate only two items of an arbitrary type. What I find myself wanting to do far more regularly is fold over an array of said items, which is trivially possible using our monoid instance. Here we'll just fold over small arrays, but this can work for arrays of any size at all: ```haskell mconcat [] -- Cocktail { name = "", ingredients = [] } mconcat [mojito] -- Cocktail { name = "Mojito", ingredients = ["rum", "mint"] } mconcat [mojito, robroy] -- Cocktail { name = "Mojito Rob Roy", ingredients = ["rum", "mint", "scotch", "bitters"] } ``` ```typescript fold(monoidCocktail)([]) // { name: '', ingredients: [] } fold(monoidCocktail)([mojito]) // { name: 'Mojito', ingredients: ['rum', 'mint'] } fold(monoidCocktail)([mojito, robroy]) // { name: 'Mojito Rob Roy', ingredients: ['rum', 'mint', 'scotch', 'bitters'] } ``` This is equivalent to reducing over an array of items using the semigroup concatenation operation as the function and the monoidal identity element as the starting value. ## Sequence Here's one that's super useful but you mightn't have heard of. Sequencing is the act of inverting the relationship between two types: ```haskell sequenceA [Just 5, Just 10] -- Just [5, 10] sequenceA [Just 5, Nothing] -- Nothing ``` ```typescript const seqOptArr = array.sequence(option); seqOptArr([some(5), some(10)]) // some([5, 10]) seqOptArr([some(5), none]) // none ``` This is something you've probably done plenty of times but never knew that this is what it was - this is what you're doing when you call `Promise.all` in JavaScript! Think in terms of types: We take an array of promises, and we convert it to a promise of an array. We inverted the relationship or, as we now know to call it, we sequenced! As with `Promise.all`, the sequence will short-circuit to the fail-case if anything fails. ## Traverse Hot on the heels of sequencing is traversal, which is essentially just a combination of sequencing with a functor map after-the-fact. You'll find that operations which are very common like this often have functions predefined in the likes of Haskell. ```haskell traverse (fmap (*2)) [Just 5, Just 10] -- Just [10, 20] traverse (fmap (*2)) [Just 5, Nothing] -- Nothing ``` ```typescript const traverseOptArr = array.traverse(option); traverseOptArr([some(5), some(10)], option.map(x => x * 2)) // some([10, 20]) traverseOptArr([some(5), none], option.map(x => x * 2)) // none ``` Just like with sequencing, this will short-circuit if the type we're inverting is already in its failure state. --- I hope that's helpful to some of you reading. Let me know in the comments if there's anything in particular you'd like to see addressed that's missing.

A => published/20200602-function-domain.md +69 -0

@@ 1,69 @@Functional programming is, funnily enough, all about functions. As such, it's good to refine how we write them. This post is all about the domains of our functions. ## What's the domain? A function's domain is the set of all its possible argument values, for example: ```haskell exclaim :: String -> String exclaim x = x <> "!" ``` ```typescript const exclaim = (x: string): string => x + '!'; ``` The domain of this function is a set of all possible strings. In other words, the input `x` could be any possible string; there are no constraints on this argument except the string type. ## Totality Can you spot the problem with this function? ```haskell head :: [a] -> a head (x:_) = x ``` ```typescript const head = <A>(xs: A[]): A => xs[0]; ``` It's _partial_, meaning its implementation is not defined for all possible inputs, specifically in the case of an empty list. One way in which we can address this is to return `Maybe`/`Option`, thus making our function _total_ i.e. non-partial: ```haskell head :: [a] -> Maybe a head (x:_) = Just x head _ = Nothing ``` ```typescript const head = <A>(xs: A[]): Option<A> => O.fromNullable(xs[0]); ``` Really, totality means only ensuring that you've handled all possible inputs such that no input could cause the function to throw or otherwise unexpectedly fail. Note that property-based testing would be a good way to catch edge-case bugs such as that in our first, naive implementation, and could be used to ensure that this implementation isn't somehow flawed for some input we haven't considered. ## Constrained inputs There is an alternative technique we can employ however, one that's often preferable. That is to limit our function's domain itself through the use of more restrictive types: ```haskell head :: NonEmpty a -> a head (x:|_) = x ``` ```typescript const head = <A>(xs: NonEmptyArray<A>): A => xs[0]; ``` By constraining our function's domain we've been able to define a safe head function that's maximally ergonomic. If someone doesn't already have a decidedly non-empty list, they can _maybe_ make one and be no worse off than before. On the other hand, if the consumer's list is definitely non-empty, then they no longer have to deal with a false notion of nullability. Additionally, it's simplified our function implementation. If you ever catch yourself widening your output type to satisfy a bad input, that's a sign that you might need to constrain your domain. A very common mistake from beginners to the `Maybe` type is to discover that they need to manipulate it in their business logic path, and write a function of type `Maybe a -> Maybe b`. Never do this! This is what functors are for. ## Codomain To round up this post, a quick mention that just as `a` in `a -> b` is the domain, `b` is the _codomain_.