We explore a means to represent the combination of our stateful computations using familiar composition. We dive into what makes this possible by talking about what are known as Kleisli Arrows and explore some interesting properties surrounding them.
Once we understand the basics of how our stateful computations are chained, we look at how we can enlist a special compose helper named composeK
. Using composeK
, we show how we can further remove a lot of the boilerplate sometimes used to combine stateful computations.
Instructor: [00:00] A wonderful property of stateful computations is that they can be composed like any other function. To see how we can use familiar function composition to represent our flows, we'll start by getting to know this compute function.
[00:13] Compute is defined as a function that takes a number and returns a state number of number. We take this number N and lift it into a state, using this of construction helper, and then proceed to chain our various stateful interactions over our lifted value.
[00:28] For our first interaction, we have this add-state function, which takes a number and returns a state number of number. Next up is ink state, which also takes a number to a state number of number.
[00:42] Finally, we have multiply state, whose SIG-y is, you guessed it, a number to a state number of number. Let's log this out to see what compute is doing for us.
[00:53] We log the result of calling compute with the number 10 and we get back our expected state instance. When run with an initial state of 2, we get back a pair 36, 3.
[01:05] Let's see if we can figure out how we got here. We start with some N, or 10 in our case, which is lifted into the resultant of our state instance. Then, we chain add state, which adds the resultant 10 to the state 2, giving us back a new state instance with 12 in the resultant.
[01:22] We then chain in ink state, resulting in another state instance that increments our state of 2 by 1, resulting in a state of 3, while leaving our resultant untouched for a value of 12.
[01:35] Finally, we wrap up this computation by chaining multiply state, which gives us back yet another instance. This time multiplying the resultant 12 by the state 3, giving us back our pair 36, 3.
[01:48] One thing our keen eyes may have noticed is that including compute, every transaction we've defined has this signature number to a state number of number. The signature also shows up in our definition of chain.
[02:01] Using a more generic form of A to a state S of B, we can represent this signature in an even more generic form, by replacing our state S with an M, resulting in a SIG-y that reads as an arrow from any A to a given M of B.
[02:17] In this more generic form, this type of arrow is known as a Kleisli arrow, which is the namesake of a brilliant fellow by the name of Heinrich Kleisli.
[02:26] One property of these types of functions is that they can be composed, but normal composition isn't going to work here. We need a specialized function in Compose-K, which we'll bring in from crocks.
[02:38] Compose-K will compose any given Kleisli arrows together as long as our M's match up. To see how we can lean on composition for computation, we'll start with our first two functions, add state and ink state.
[02:51] Let's combine them by creating a new function called add and ink. Using our familiar signature, we'll define add and ink as a function that takes a number to a state number of number, matching the same signature of the functions being composed.
[03:06] Implementation is a breeze. We just call Compose-K, passing it our function, starting with a quick pluck and paste of ink state, as it needs to be called after our call to add state, which we'll also pluck out of our old flow and paste into our composition.
[03:22] After a little cleanup, we'll reuse one of the chains by dropping in our new composition. With a little save, we see we get back our pair 36, 3. Another beautiful property of this signature is that, much like a forklift, we can use these functions to lift our values for us, making this call to of obsolete, with a little rearrangement.
[03:43] We just take our given N and apply it to our new composition. Give it a save, and we still get back our pair 36, 3.
[03:53] Why stop there? As multiply state is chained, we can get a little cute for a second and turn the rest of this into another composition. We just use Compose-K again, arranging it so that multiply state is called after our first composition, add an ink.
[04:09] We get rid of all these nasties at the bottom and remove the arrow, as Compose-K will provide us with all the function we'll need. With a quick save, we see we still get back our pair 36, 3. Simply beautiful.