Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exercise #2 #2

Open
nicc opened this issue Aug 20, 2019 · 4 comments
Open

exercise #2 #2

nicc opened this issue Aug 20, 2019 · 4 comments

Comments

@nicc
Copy link

nicc commented Aug 20, 2019

Well done with reduce!! (high five)

The next exercise is simpler. It builds on the previous one.

Reduce provides the conceptual foundation for all recursive list computation. Your next few tasks will be to set about proving this. I've heard people say that anything can be computed with reduce. I think this is hyperbolic but still, it's a strong endorsement.

First up: implement myMap using only a single call to myReduce. Your whole function body can only be return myReduce(_______); It's all about the parameters you pass in.

Unfortunately I think you will need to use an array method. Just be sure that whatever you use is non-destructive. We're still not allowed to mutate state.

@CLTPayne
Copy link
Owner

This doesn't feel like much of a success as it took me so much trial and error to get there but https://github.com/CLTPayne/reduce/blob/master/map1.js

@nicc
Copy link
Author

nicc commented Aug 27, 2019

Well you landed on the right idea!

My only feedback is that you don't need to destructure the params. You actually just restructure them again when you pass them to myReduce anyway so it's kind of pointless. This would be fine:

const myMap = function (coll, f) {
    return myReduce(coll, (accumulator, item) => {
        return accumulator.concat(f(item))
    }, [])
}

I also renamed callback because that nomenclature is a little misleading. It stems from JavaScript's single-threaded environment. The way callbacks are used relates to achieving non-blocking asynchronous processes. They're more about time than anything else. They say "do this when you're done". That mode of thought is appropriate for general JavaScript'ness but limiting for FP since higher-order functions are a more fundamental idea. Callbacks are just JS leaning heavily on higher-order funcs to achieve asynchronous control-of-flow in a single-threaded environment (not all of that is important as such, just, yeah... let go of callbacks and just think of them as functions).

So let's dive into how you felt it was a process of trial-and-error. This is quite important since the whole idea is to give you a new way to think about stuff. I'd love to hear what you found opaque or troublesome. You did the right thing in the end. Does it make sense to you now?

The way I like to think of it is that reduce abstracts general list computation and the others (map, etc) use that abstraction to achieve more specific things with lists. The fact that you wanted to destructure the collection passed to myMap seems to indicate that you didn't intuitively leverage the abstraction afforded by reduce. That's totally understandable. FP isn't very clear on design practice. With classes and objects you can draw and see a structure and reason about it in familiar terms. With FP you have this flow of data through many discreet transformations and structure is harder to see. The irony is that FP abstractions can be way more powerful. They're just harder to grasp. Reduce, for example, should give you the freedom to forget about the structure of a list. It gives you this notion that you have an accumulating value and an item. All you need to do is merge the item with the accumulating value. For map we achieve that with cancatenation, whereas to sum we use addition. In both cases, however, the list itself has been abstracted away. At no point do we need to address or reference specefic elements of the list anymore. It takes some time to really feel this but the abstraction curve in FP is extremely steep and the payoff can be very big.

Okay so I've just info-dumped on you a bit. Shall we set you up with another exercise? I'll make it a two-parter if you're keen. We'll keep the first exercise in a similar space so you can rework the same ideas a bit. The second part will be more challenging. Maybe let's grab a call one evening before you tackle part two? As always, please feel free to ask questions to challenge or clarify anything I've said.

@CLTPayne
Copy link
Owner

Hmmmmm, regarding the destructuring of the array in to head and rest, I assumed this was necessary, thinking I would need have head defined in order to have it available to use as a parameter in the function. That perhaps shows some gaps in my understanding of scoping and params?

On the naming of the function that is passed in as callback - that's really interesting. So I think I understand you to mean that really a callback should be referring something that goes in the callback queue and waits to for the callstack to clear so that the event loop can pass it on to the callstack to execute? Is that correct? I have seen and some people use the following definition though: "Any function that is passed as an argument is called a callback function."?

In your version, does coll refer to a collection? I've not seen that abbreviation before. I definitely prefer using item but feel that f is maybe a little too brief. Had a look around for some standard functional programming abbreviations / naming conventions but couldn't find anything that seemed to match up with head and tail from your version of the reduce and coll. Do you know of one?

For the trial and error process - initially I was quite fixated on the idea that I needed to create the new array (i.e. to stop the reduce returning a single "reduced" value from the array) in the 3rd param position where the empty array should be passed in (instead of the default 0). Then when I moved on to realising that this all needed to happen in a wrapper around the passed in function, initially I had my params and naming mixed up so that I was concatenating with an empty array and thus getting a string value of [ '101010' ]. Then when I got it to stop coercing to a string, I was only ever calling the reduce with the first item in the array due to some other error with the params i think. So I think you're right - I'm definitely not seeing the flow of data clearly, let alone intuitively yet - hence also all the console.log() which helped me realised I had the function being called with the same first value every time. But that lack of current understanding i ds probably even more reason to do these exercises?

@nicc
Copy link
Author

nicc commented Aug 28, 2019

destructuring: ah I think I see what you mean. No you don't need to worry about that. The param var assignment will happen inside myReduce and be locally scoped there. The function that you pass in to myReduce can get its own param names. It'll generally be something like accumulator and item but you're free to make them specific to each situation. head and tail only apply to myReduce. In fact you'd have a hard time leveraging lower-level abstractions if you had to maintain the params transparently all the way up the call stack. Once you've got a function defined you can (and I think you should) think of it as self-contained so you can forget about what happens inside it as far as possible. I hope I've understood you correctly!

callbacks: yeah, something like that. I won't make too strong an assertion about what the 'correct' definition of a callback is but I do think it's important to distinguish higher-order functions from their idiomatic application in JavaScript. JS callbacks are used to get around the fact that you're in a non-blocking, asynchronous runtime. You know how you get onSuccess and onFailure callbacks in an http library? Well that's because JS doesn't block while performing long-running stuff like network IO. When you make an http call the JS interpreter just carries on with the next line of code while waiting on the network. This means you can't guarantee that the http data has been returned yet. To get around this we pass in a callback. The http function will 'call back' with the result when it's finished. This way you know the data is available. That is 100% a higher-order function but it's a very specific use of one. It enforces your desired execution sequence because JS is weird. It's even in the name; you're asking your function to "call back" to this place in the code when it's done. So when you say 'callback' the pattern that you're used to comes to mind and that constrains how you might think of using higher-order functions. So I'm suggesting you keep that word reserved for that pattern but also adopt the more general view that they're really just another function to be used as you wish. In these exercises, for example, we've been using them to abstract away data structures and common computational patterns across those structures. What's useful is the separation of concern between the data structure (the list) and the transformations you want to apply to the elements within that structure (the passed in function). map and reduce have nothing to do with sequencing. There's no notion of "when you're done". That's why I say the word "callback" is something of a cognitive hurdle in this context. It's misleading.

param naming: yep, coll is for collection. I tend to use that because it's the most generalised notion of a list. Also applies to hashmaps, sets, vectors, etc. Param names are generally pretty terse in FP. I tend to favour that but it has annoyed people I've worked with in the past. It's generally a good idea to follow the idioms of whatever language you're using. JS is quite descriptive so go with that. The challenge with replacing f is coming up with a general name for it in the context of the function you're writing. For these list functions I sometimes just verb the thing and call it mapper or reducer. In the case of myFilter you might call it pred or predicate though. Hope that helps!

trial-and-error: Ah okay yeah that makes sense. And yep, I think it's part of the deal with this stuff. It wouldn't be useful if we weren't learning! To maybe set the scene on this whole set of exercises a bit more... I don't really expect you to go out and write code like this immediately. In fact please don't use primitive recursion lightly! Don't use it at all in most languages. All I'm trying to do is make a little piece of your brain click into a new mode of thought. That will grow on its own over time and you'll need to see for yourself where it fits and how it can help you. To put it in context, I did some of this stuff in Haskell about ten years ago when I was still working in Ruby. After that it still took years for me to grasp FP 'properly' and of course I'm still learning. Also, if you're not using a real functional language (and the vast majority of languages aren't) then these concepts can only lend a flavour to your code. You can't adopt this entirely if the language doesn't like it. But subtle things will become tighter and cleaner in any language and hopefully you'll rely on state just a little bit less. That's enough. Don't put too much pressure on yourself and trust that paradigmatic stuff like this will take a life of its own. It's all about planting a seed that allows you to think differently. Time will do the rest. You're getting this stuff in early!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants