We take a closer look at the get
construction helper and see how we can use it to lift a function that maps the state portion and updates the resultant with the result. Using get
in this fashion, we then demonstrate how we can make accessors that can then be extended to create more complex interactions.
As there are times that we only want to pull the resultant for a given computation, we take a look at running our instances with the evalWith
method. evalWith
will run our computations, throwing away the state, returning the resultant.
Instructor: [00:00] We start by destructuring this get function from the State constructor which, when called, will provide us with a State instance with the state echoed in the resultant. We also bring in this Object named burgers which is just an Object with the property of burgers on it with the value of 4.
[00:16] With our players on the field, let's kick this off by first creating a State instance which we'll call getBurgers. We'll define getBurgers to be a State instance that has an Object in both the State and the resultant. To implement, we'll reach for get to echo the State into the resultant and verify our instance by passing getBurgers to this log function.
[00:39] As it needs some initial State to run, let's run it with our burgers Object and see what happens. Running our instance gets us back a pair Object Object. We used Pair's snd method to pluck out our State and fst for the resultant, verifying they are equivalent.
[00:56] While getting our State's identity is exciting in some circles, I think we could spice it up a bit and have some fun. Let's update our definition so the resultant can vary to any type a. We want to pluck off our burgers value from our State and throw it into the resultant. To do this, we'll bring in a function named prop.
[01:15] Prop gives us the value of a given property on an Object wrapped in a Just, but if that property doesn't exist, we get back Nothing. With our State in the resultant, we could reach for map to apply it to our prop function, which we'll partially apply with the string burgers. Give it a save. We see our resultant is now Just 4.
[01:36] As is, getBurgers will always return a Maybe of something, so let's wrap our a in a Maybe. When runWith(burgers), we get back a Just. For fun, let's see what would happen if we used tacos as our data instead.
[01:50] Before we send it through our State, we first need to bring tacos into scope by destructuring it off of our data Object like we did with burgers. Now we just replace burgers with tacos and see our resultant is Nothing. By pulling the State with snd, we see that it is indeed our 10 tacos.
[02:08] For this example, we're really only concerned with the resultant and long hair don't care about the value of the State. For this use case, State provides an evalWith method that will unwrap the resultant, tossing aside the tacos returning our Nothing. Passing burgers to evalWith throws away our burgers, giving us our Just 4.
[02:27] Building State accessors like getBurgers is so common that get has a little trick up its sleeve. If we pass it a function instead of unit, get will map over the State and substitute it with the result, removing the additional map. We'll replace burgers with tacos and verify that we still get our Nothing.
[02:45] When we originally defined getBurgers, Maybe was never mentioned. In order to unwrap the Maybe, we need to provide a reasonable default for Nothing. We can fold this value out by optioning it with the default value.
[02:59] To do this, we can reach for option, which is a point(3) function provided by crocks that takes a default value followed by a Maybe. Option either unwraps a Just returning its value or returns our pointed value in the case of Nothing.
[03:14] As our accessor currently gives us a Maybe, we can lift option into our instance using the map method on it, partially applying 0as our default, which in turn unwraps our four burgers for us. Now, when we pass in our tacos, we see that we can never get burgers from tacos, and we get back our default of 0Going back to burgers and then looking at our implementation, we'll notice that prop and option both map over our resultant with option taking the result of our accessor and replacing the Maybe with some type a.
[03:46] We should be able to roll our option into get by utilizing function composition. We'll first bring in the compose helper function provided by crocks to build our composition. Compose takes any number of functions and returns us a new function that will pipe our data through each function right to left, returning us the result.
[04:05] Even though these functions are specific to this flow, let's see if we can get a little green and make something that can be reused, which we'll call defaultProp. We can define defaultProp as a function that takes a tuple String and any type a followed by an Object, and returns us any type b.
[04:25] As this is JavaScript, the value in our Object is not guaranteed to be of type a, which is why defaultProp may return another type b that differs from our default. To implement, we bring in our target key and our default def, returning a composition that accepts an Object and returns any type b. Option pointed at our default called after a partially applied prop defines our composition.
[04:51] Now we can use our newly created function in our instance downstairs. We'll get rid of this excessive mapping and replace the function passed to get with a call to defaultProp, calling with a key of burgers and a default of 0When we save this down, we see we get our expected 4. If we run this with tacos, our expected 0is the result.
[05:13] Now we have ourselves an accessor that not only matches our definition but also communicates its intent, making future us giggle with glee. We can now use our accessor as the starting point to make our reality a little bit better.
[05:27] What if there was a shadow of our world that you could always get tacos from burgers? We can represent this shadow with a State instance we'll call burgersToTacos. We define burgersToTacos as a State instance of Object.
[05:41] To implement, we first pull in our accessor. We want an instance that will take our value off of burgers and wrap it in an Object with a key of tacos. We can map over the resultant, passing in a function to do the work for us.
[05:56] Crocks provides a function well suited to this task in the form of objOf. ObjOf takes the desired key as its first argument and the value to be wrapped as its second, returning us the resulting Object. Now we just lift the result of applying tacos to objOf into our State instance using map.
[06:15] Jump back downstairs and replace getBurgers with burgersToTacos evaluating with tacos, which gets us back 0tacos, as we started with tacos. But if we start with burgers, we see we now get back four tacos, thus defining a reality in which I would love to reside.
@Dev
Three videos in and I still have no idea why would anyone use that…
There are many uses for it. You can reach for it when you need to combine computations that update or read from some shared state. Think of things like Redux application state or a pure random number generator, or all functions that work on some record type in a database.
When you see that a family of functions (reducers, rng seed, or user records from a database) that need to update or modify a shared state, you typically need to hold that state internally or provide it as a global in a file some where.
In the case of working on say a User record, you may have MANY operations that read from that record type or modify it in some way (changePassword, validatePassword, updateName, getEmail, etc). most of these functions either work on a shared copy or have to pass user
around if you are taking a more functional approach. In these functions there tends to be a lot of destructuring off of the recordType to read from that "state", "restructuring"/merging to update that "state". This can appear all over related functions.
So what State can provide is a means to abstract away all of that state management into a simple pattern that lets your functions just focus on the computation part and not the management. Turns functions like updateName :: String -> User -> User
to only care about the context and not the state, it then becomes updateName :: String -> State User ()
.
This initial course, mostly focuses on what is going on internally with the State datatype, and give an introduction to the API. I am working on a course now as a follow up to this that will demonstrate how to use State
with Redux which will provide more of the Why. This is more the What.
What I miss in this videos so far is a little background information. It has a lot of assumptions that it's clear why the getter functions are called fst
and snd
(Probably first
and second
) but why aren't they named first
and second
then or getState
getValue
.
If one has never heard of crocks
it's also hard to guess what comes in from where.
To me it so far mostly feels like "This is how you follow a recipe" but I miss the explanatory parts that allow to execute upon the presented code.
I think you present it in a good way, just some more clarification about why to do some of the things would be great. <3
@Roman
Sorry about the :corn:fusion there. I kept most of those details out of these lessons because they are more about Pair
then State
, but as State
is a Product Type, we needed to touch on it a bit. I would like to do a course on Pair
as it is an amazing type, so I will more than likely address those bit within that context.
... clear why the getter functions are called fst and snd ...
They come from the names of (2) functions in Haskell, which can be found here. I kept them the same in crocks
.
... (Probably first and second) ...
in crocks first
and second
are methods used on two other types (Arrow
and Star
), you can see their documentation on Arrow
here if you are curious, but I warn you, if you are not familiar with ADTs these may seem super duper :corn:fusing
... but why aren't they named first and second then or getState getValue.
Pair is it's own type, while state
and resultant
/value
are specific meanings for State
. These first few lessons use the "raw" Pair
methods for extraction, but as we progress through the course we will use the State
specific functions evalWith
and execWith
.
... To me it so far mostly feels like "This is how you follow a recipe" but I miss the explanatory parts that allow to execute upon the presented code.
I thought providing all of that context in these lessons would cloud the intent and people would seek out things that they felt were gaps in their understanding. These initial lessons are meant to show the learner what is going on inside of the datatype, and that there is not really any magic going on when we get into some of the other "helper" methods.
Three videos in and I still have no idea why would anyone use that…