DEV Community

Discussion on: Monad Say What? (Part 1)

Collapse
 
antonrich profile image
Anton

Well, first of all, welcome. It's nice to see more functional folks here.

I see it like this.

You write, you gather feedback, learn from your mistakes and then you write a better article.

As a novice in FP I would like to see things like: refactoring someone's imperative code.

I started writing my comment without reading the article. So I thought instead of putting it off for later I'll read it now.

At this point I've read up to this:

These posts assume an understanding of "currying", "partial application" and "function composition". If you feel a bit fuzzy on these topics, there are many resources available on the webs to get you sorted out.

I understand those concepts exactly (maybe not in JS, but certainly in Elm and Haskell. I guess I can transfer my knowledge to JS).

Let's continue reading.

But I also have to warn you that I've already read about monad on the futurelearn course about Haskell. But I can't tell that I understand the monad or that I can use it.

// data :: [ * ]
const data = [
{ id: '9CYolEKK', learner: 'Molly' },
null,
{ id: 'gbdCC8Ui', learner: 'Thomas' },
undefined,
{ id: '1AceDkK_', learner: 'Lisa' },
{ id: 3, learner: 'Chad' },
{ gid: 11232, learner: 'Mitch' },
]

Why do you have null and undefined here?


Here's where I'm getting lost.

Our examples will be defined with the following list of requirements:

  1. Accept any value of any type at its input.
  2. When the data is not a non-empty Array of at least one acceptable record, an empty Object will be returned.
  3. Return an Object of valid records keyed with a valid id from the included record, effectively filtering out any invalid records.
  4. We define a valid record as an Object with a String keyed with id.
  5. This function does not throw, no matter the input, and provides a reasonable default in the empty Object it returns.

My questions are:

Why such requirements.


Continuing.

At this point I briefly looked at Crocks.

And now I started retyping:

{ gid: 11232, learner: 'Mitch' }

is gid purposeful or a typo?

And why you have some ids like numbers, a single number, or a mix of letters and numbers? Because data like this doesn't make sense to me. Is this how JS world looks like?


At this point I went to bed to lie down. I contemplated and when I returned I deced to look you up on twitter. Didn't find one.

Looked at your Github though.

You are the creator of Crocks!

Interesting.

At this point I'm watching your youtube video "pointfree and combinators".

Comb (ai) nators - is really a weard way to pronounce that word.


At this point I jumped around the article.

I have this to say:

I didn't think that functional JS would be that weird.

But I also understood that the purpose was to clean the data. I would actually start the article with that. Like we have some unclean data and we are going to clean that up.

You have actually demonstrated that but in the middle of the article. The question is? Would it be better to start the article with that? In my opinion it would.

I'm used to challenges on Codewars. It's such a good thing that they provide input and outputs. It's the first place where I look.

P.S. I rethought my earlier statement that functional JS is weird. It's actually not that weird.

Collapse
 
evilsoft profile image
Ian Hofmann-Hicks • Edited

Wow that is some amazing feedback. Thank you for taking the time for putting it all down. I cannot tell if your question are meant to be rhetorical, but I will err on the side of they are not and try to address them.

Why do you have null and undefined here?

In my experience with the web or working in dynamic languages, the data you receive may come from multiple sources and/or not ideally normalized for proper processing. I found data like I presented above in things like service oriented architectures and even when compiling scientific data from different instrument sources (even worse for double blind studies or peer review aggregation). So while fp concepts in JS is easily explained with simple curried add functions on Number, I wanted to provide a case a bit closer to real life.

Why such requirements.

Same answer as above.

is gid purposeful or a typo?

And why you have some ids like numbers, a single number, or a mix of letters and numbers? Because data like this doesn't make sense to me. Is this how JS world looks like?

A lot of this function is to normalize the input and separate the valid data points from the noise.

I contemplated and when I returned I deced to look you up on twitter. Didn't find one.

@evilsoft on twitter

Comb (ai) nators - is really a weard way to pronounce that word.

Nice, just one of the effects of reading about them in textbooks without a professor to say the word out loud I guess.

I didn't think that functional JS would be that weird.

It is not that Functional JS is weird, it is that working in a dynamic language presents some unique challenges that Haskell and Elm (and a few others) do not have to encounter.

But I also understood that the purpose was to clean the data. I would actually start the article with that. Like we have some unclean data and we are going to clean that up.

This is where I think I may not have been clear on the intent. This is supposed to start down our path of understanding how to use ADTs in our functional JavaScript. I wanted to start by contrasting how different typical imperative JavaScript looks compared to the same functionality implemented code with ADTs in JavaScript. It seems that you were fixated on the MacGuffin that was used to drive the "plot" and be the actual focal point itself (other then to provide the contrast). So this seems to be where the failure in communication on my part lies.

I take every bit of criticism, good or bad to :heart: and use to improve my material in my video, live code and egghead lessons. So I appreciate you taking the time and passion to make sure the the content I provide is getting the message across.

I am going to make some tweaks to this posting to address a couple of the things you pointed out. And will defiantly keep some of these points in mind while I edit the other 7 posts in this series.

Thank You Again For Taking The Time,
evil.

EDIT: One thing I forgot to mention, for things like do not throw and just return an empty Object and things like that. When working in dynamic languages we tend to use a concept of Reasonable Defaults. So instead of just erroring out if life is not what we expect to be, we can signal that all is not well by returning an empty Object (in this case anyway) as anything that relies on this program would get the Object it needs with the information that there is nothing to process.

Collapse
 
antonrich profile image
Anton

I cannot tell if your question are meant to be rhetorical, but I will err on the side of they are not and try to address them.

I was writing the comment while I was reading. Because I wanted to show you my thought process "in real time". If I were recording a screencast you would have heard me asking this question.

This way I can show where the friction occurs better.

Thread Thread
 
evilsoft profile image
Ian Hofmann-Hicks

Oh sorry about that.
Thanks so much for doing this, I find feedback like this very helpful!

Also did you happen to do any of the exercises? I am curious how they aided/hindered your understanding.

Thread Thread
 
antonrich profile image
Anton • Edited

To be honest, I haven't.

The reason is... actually, it doesn't matter what the reason is.

I will do what I'm supposed to do.

I will finish what I started.


Now I will write my comment again in the same style. I have attached my gedit window with the above all other windows option. I will reread the article especially the functional part (because some of the functions I don't understand). I will narrate as I go along. I will call this "narration style".


const { isArray, isObject, isString } = require('crocks')

I have to say that I'm not that proficient in JS. So correct me if I'm wrong. What I think I see here is destructuring. Pattern matching in other words. But on the right side there is a library so I don't really see how that works. I think I'll go right now and watch something on youtube to understand that part. I'll be as thorough as possible.


I opened youtube and searched es6 destructuring. I see a lot of videos. I'm going with the fun fun function video. If necessary I will narrate my process.

Here's an example:

let animal =
{ species: 'dog'
, weight: 23
, sound: 'woof'
}

// the destructuring
let { species, sound } = animal

// and from this I understand that the order doesn't matter.

Well, that's actually all I need at the moment. If order doesn't matter I can take out any function out of the library.

That's it. The video is 10 minutes long, I stopped at 1:27. Back to the tutorial.


At this moment I'm reading the requirements to really get them.

When the data is not a non-empty Array of at least one acceptable record, an empty Object will be returned.

This part, was a bit hard to understand. Because of the use of the "double negative". I understand that it's not. But you can simplify it.

I'd go with:

When the data is an Array with at least one acceptable record, an empty Object will be returned.

Tell me if I've "refactored" your requirement correctly.


Oh, I'm looking the type signature:

// data :: [ * ]

the "*" is not a type variable. But a wildcard. It kinda expects something unpredictable inside an array.

You know I also look at this:

From these requirements, we can implement an imperative function that does the following:

Verify input is an Array, return an empty Object if it isn't
Declare a result accumulator for building our final result, defaulting it to an empty Object.
Iterate over the provided Array and do the following for each item:
    Validate the item against our record criteria
    If passed, add the record to the result, keyed by the id value on the record. Otherwise do nothing.
Return the result.

Using a third-party library called crocks for some doing our type validation, we can provide an implementation like this:

And think that this could be written as comments in the code.

And that would be may be better, or not. Is there a way to find out?

At this point, I remembered that your purpose was to talk about ADT and monad. I went to the beginning of the article.

I'm not familiar with the word embelishment. I'm looking it up.

Embelishment is a decorative detail (elaboration, or addition).

I opened my editor to retype the example. I'm going back and forth between retyping and reading

"From these requirements, we can implement an imperative function that does the following:"

I do it slowly so I understand everything thoroughly. I'm going back and forth. I read one implementation, then retype and think. Then I read another one, and then retype again.

Oh, now I've noticed you don't have semicolons in the code. That's good.

I need to download the 'crocks' library. To run the examples. I`ve just installed crocks.
npm install crocks -S

It says "All you need to do is run the following to save it as a dependency in your current project folder".

How do I save it as a dependancy? What is -S by the way?


Okay, I've got the imperative code well, I think. Now, to the functional part. Now, I think I should the functional part in a separate file. Or not?
Gosh, it's 1:43 in the morning. I'm getting so sleepy. I will continue tomorrow when I wake up.


Alright, I have woken up.
I want to make sure that I get the function composition in JS.

In Haskell we do it like this.

func1 . func2 . func3 x x

So in JS that would be

composeK(func1(Int), composeK(func2(Int), func3(Int, Int)))

This is hypothetical, but I think I get it right.

What I see that I don't understand is Maybe.of. After that I looked at the converge function. It takes a lot of functions as arguments. Right now I'm looking at the documentation on converge. The reason I'm diving deep is because I want to do the exercises.

Now I see this. I doesn't take all the arguments, so to speak. So there is partial application at play. Maybe.of takes data as an argument and then it all reduces to one value.

At this point I haven't scroll down, but I remember you have given some explanations below. Now it's time to read them.

The function Safe led me to Maybe in the library. I'm scrolling and see chain there as well. I looked at both don't really understand them.

Let's continue anyway.

So, I see the reference to the fluent api. Which doesn't tell me anything, I tried understand it, but decided that is just too much.

Conclusion: the reference to the fluent api is completely unnecessary and will create more confusion.


Now I will go through the exercises.

  1. My immediate response is why would I use reduce when I just need to filter. I have problems with Haskell's fold function. So, I have the same problem with the reduce function.

If the reduce works with numbers and these numbers will be reduced to a singe value I understand.

But when the reduce (or fold in my practice) works with different data types other than numbers I have problems.

Okay, I just thought it about.

Here we have an empty array as an accumulator, and we will push object that are satisfy a predicate.
Now should I write this predicate as a helper function?

Here's my little refactor:

`js
let result = records.reduce(filter_records(records), [])

const filter_records = records => {
return isObject(records) && isString(records.id)
}
`
I think it's not correct.

Will this work?

`js

let result = records.reduce(filter_records(sum, records), [])

const filter_records = (sum, records) => {
return sum + (isObject(records) && isString(records.id))
}
`

I unfortunately, cannot find out at the moment. Because, I have no idea of how to connect Crocks. It is installed, I've never used package.json, I've not used any build tools yet.
Here, I need some help.

2 - 5. I feel that lack of knowledge of JS is a little hindrance and that every time I do something I need to dig deeper. I wouldn't say I'm lazy. But in this particular instance I am. Maybe I will return at some point and do the exercises. I liked the "reduce" exercise.


Why did I wrote all those comments?

  • Some people say that when you get the monand you lose the ability to explain it. I want to reflect and see if I get the monad and if it's really true.
  • So, I started and I need to finish. I want to be a finisher. I want to be a man of action and a man of my word. Being a man of my word is really hard for me. I do tend to commit to a lot of things but accomplish less. So it's kind of personal growth for me.
  • Your feedback is also valuable for me.
  • I have a slight interest in functional JS (But who knows how it's gonna work out in the future). But I hope that JS will be as functional as possible. That is my little contribution to that hope.
  • Another reason is. I may have acquired a bad behavioral pattern with the read later button. I once have read on Scott H Young`s blog that it's better to learn something deeply right away when you approach the problem rather than putting it off for later. This is my attempt to do exactly that.
Thread Thread
 
evilsoft profile image
Ian Hofmann-Hicks • Edited

So good.

So there are a couple things you've shown me I need to do. First of I need to update:

These posts assume an understanding of "currying", "partial application" and "function composition". If you feel a bit fuzzy on these topics, there are many resources available on the webs to get you sorted out.

To read more like:

These posts assume an understanding of not only the JavaScript language, but how "currying", "partial application" and "function composition" is accomplished in Javascript....

Also good call on the needing of crocks for the POJ example. I need to break those out into functions like:

// isArray :: a -> Boolean
const isArray =
  Array.isArray

// isString :: a -> Boolean
const isString = x =>
  typeof x === 'string'

// isObject :: a -> Boolean
const isObject = x =>
  !!x && Object.prototype.toString.call(x) === '[object Object]'

Yeah people should not have to install crocks at this stage in the game.

When the data is an Array with at least one acceptable record, an empty Object will be returned.

How about

Unless the data is an Array with at least one acceptable record, an empty Object will be returned.

Arrays in JS can be of mixed type, so we typically denote that with [ * ], as opposed to something like [ a ] where we expect everything to be that a. Well when our functions can work with mixed typed Arrays. In reality we want to strive for [ a ] or even better something like [ Number ], but sometimes at the edge, we need the [ * ]

You got mostly the compose bits down, except you would just use compose, composeK is for Kleisli arrows ( basically >=> or fish in Haskell). Also you can use either compose or composeK like this:

// x => fn1(fn2(fn3(x)))
const fn =
  compose(fn1, fn2, fn3)

My immediate response is why would I use reduce when I just need to filter.

So this is more than just a filter, we are change types. we move from an Array to an Object. So we want to reduce with {} as our empty on the fold, then only add valid records keyed by their valid id to the accumulator. Does that make sense?

Thread Thread
 
evilsoft profile image
Ian Hofmann-Hicks • Edited

Also just to help a fellow Haskeller out here is a quick JS -> Haskell key:

  • compose -> . (but compose is n-Ary)
  • composeK -> >=> (but composeK is n-Ary)
  • [type].of -> pure for Applicative Functor / return for Monad
  • [instance].chain -> bind
  • liftA2 -> liftA2
  • converge -> S' or Phoenix Combinator.
Thread Thread
 
antonrich profile image
Anton

I mostly don't know about them. So you are telling me new stuff that I should learn : ))) That's great. Thanks.

Collapse
 
antonrich profile image
Anton • Edited

Do you think the concept "having the end mind" is applicable here?

and

So, basically you first show this at the start of the article (introduce the end goal):

indexById(null)
//=> {}

indexById([])
//=> {}

indexById([ 1, 2, 3 ])
//=> {}

indexById(data)
//=> {
//   9CYolEKK: { id: '9CYolEKK', learner: 'Molly' },
//   gbdCC8Ui: { id: 'gbdCC8Ui', learner: 'Thomas' },
//   1AceDkK_: { id: '1AceDkK_', learner: 'Lisa' }
// }

And then you just show the imperative approach and then show how to refactor that approach into the functional one and what are the benefits.