exercism / javascript

Exercism exercises in JavaScript.
https://exercism.org/tracks/javascript
MIT License
553 stars 605 forks source link

[closures] Concept examples and explanation needs improvements #1529

Open Jasstkn opened 2 years ago

Jasstkn commented 2 years ago

Hi. While I was learning concepts for JavaScript I found out that closures theory isn't very helpful for solving the suggested practical exercise: Coordinate Transformation.

I would like to propose to improve it by

Let me know what do you think. Connected links are here: https://exercism.org/tracks/javascript/concepts/closures https://exercism.org/tracks/javascript/exercises/coordinate-transformation

junedev commented 2 years ago

Creating an issue about improving the closure concept was on my todo list. 🙂

In addition to adding to the existing concept documents I think it would also be good to maybe rework/simplify some of the existing content.

Do you want to work on this?

Jasstkn commented 2 years ago

Creating an issue about improving the closure concept was on my todo list. 🙂

In addition to adding to the existing concept documents I think it would also be good to maybe rework/simplify some of the existing content.

Do you want to work on this?

Yes. I can try :)

khendrikse commented 2 years ago

Hi @junedev! I don't know if this is the place for this, but I notice something about the closure explanation.

In the explanation it says:

"Closures are a programming pattern in JavaScript which allows variables from an outer lexical scope to be used inside of a nested block of code. JavaScript supports closures transparently, and they are often used without knowing what they are."

// Top-level declarations are global-scope
const dozen = 12;

{
  // Braces create a new block-scope
  // Referencing the outer variable is a closure.
  const twoDozen = dozen * 2;
}

// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
  return dozen * n;
}

But most explanations, including the one on MDN define a closure as being A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. (source: MDN on Closures

In the explanation it says that referencing the outer variable from the first block scope would already be a closure. Looking at the docs from MDN, that would then not seem to be correct, as that first block scope is not a function?

I'm mostly asking because it is confusing me as well, and as closures are a hard concept to explain, I wonder if we might need to make sure this explanation is correct.

junedev commented 2 years ago

Great question! I am not 100% sure myself. @SleeplessByte, what's your opinion on this? Would you can the block example a closure as well?

In any case, I would recommend making all the main content about the function version of closures as this is what most people think of when they hear the term and how they are mostly used. Then if we decide the blocks count as closures as well we can add that as an additional knowledge nugget in about.md.

khendrikse commented 2 years ago

I'm curious what @SleeplessByte thinks (if only to learn ;)), if y'all think it's better to delete the first shown block-scope, let me know, I'll remove it and open a PR.

junedev commented 2 years ago

@khendrikse Either @Jasstkn will already incorporate this in their current re-work of the concept files or it can be addressed afterwards. In any case, I would like to avoid you and @Jasstkn working on the same files at the same time.

khendrikse commented 2 years ago

That is also fine :)

SleeplessByte commented 2 years ago

Sooooo. 👋🏽

Definitions are as difficult as naming is (I wrote articles about similar subjects in the past, for example: JavaScript, Ruby, and C are not call by reference), which is what makes this even more interesting. Yes, in general closures are often defined as enclosing values inside a function, which is often possible in languages that "do not treat functions as a special case" (meaning functions are first-class citizen, and can be passed around, just like other values). This is usually mentioned because if you can pass functions around, it means you can often execute the function on a completely different call site then where it was defined. The operational bonus is then that that function has access to variables it had access to when it was defined despite the call site NOT (necessarily) being able to access those.

Okay. Great.

In JavaScript blocks (the first example) cannot be passed around, but in some other language (hello there Ruby), some forms of blocks are callable (effectively making them similar or the same as functions), ánd can be passed around.

Because the block from the example is static/fixed in place, its power to enclose the variable is "lost". It doesn't matter. Therefore I would agree to bin the example, despite you probably being able to find references where a writer argues that any enclosure is "a closure". I'm not willing to say that we absolutely cannot call this a closure, but in general, when people who know JavaScript or similar languages talk about closures, they will almost always refer to instances of functions that enclosed variables, and thus not talk about blocks.

TL;DR; correctness of the first example doesn't matter. It's use-case is basically none (other than creating a new block which can be great when writing switch statements and their cases. I would vote to remove it.


✨ Everything that follows is just extra information and should not be included in this exercise

Are there other ways to enclose other than using a function?

Yes! For example classes can do this too.

const definedOutside = 'hello, world!'

export class Shout { 
  // This function is technically a closure
  call() { console.log(definedOutside); }

  // This variable is, once this proposal becomes language,
  // technically enclosing a value from outside this class, but will
  // probably not be called a closure
  #privateVar = definedOutside
}

const utterance = new Shout()

utterance
// => Shout {#privateVar: 'hello, world!'}

utterance.#privateVar
// => SyntaxError Private field '#privateVar' must be declared in an enclosing class

utterance['#privateVar']
// => undefined

utterance.call()
// => 'hello, world!'

Classes can in fact enclose values passed from the outside in their constructor, variables created in their constructor, or even variables attached to themselves

export class Enclosing {
  constructor(passedValue) { 
    let increment = 0

    this.magicNumber = 21
    const self = this;

    // Return a new object that does NOT have Enclosing as its prototype

    return {
      // encloses variable defined in constructor
      increase() { 
        increment += 1
        return increment;
      },

      // encloses variable passed in constructor
      call() { console.log(passedValue); },

      // encloses the entire object that was created by the constructor
      ref() { return self.magicNumber * 2 }
    }
  }
}

// Now try it out
const enclosed = new Enclosing('Goodbye, Mars!')

enclosed.magicNumber
// => undefined

enclosed.increase()
// => 1

enclosed.call()
// => "Goodbye, Mars!"

enclosed.ref()
// => 42
khendrikse commented 2 years ago

Thanks so much @SleeplessByte ☺️ I learned a lot and this made it very interesting! I am glad I decided to ask. (Also, this read as well as a blog already haha).

SleeplessByte commented 2 years ago

No problem! Gewoon blijven vragen :)

junedev commented 2 years ago

@Jasstkn How is it going? Do you still want to work on this?

junedev commented 2 years ago

@Jasstkn Since I haven't heard from you in a long time, will un-assign you from this issue for now so someone else could pick this up.

SnowJambi commented 2 years ago

+1 on this. I'm doing this exercise for the first time, and I had no idea what I was being asked to do, and I couldn't manage to glean much context from the test cases either.

I haven't finished it yet, but particularly task 1 seems to be lacking information for someone who has never encountered this before.

Intuitively, if I were to write the translate2d function myself, I would write something like

function translate2d([x, y], [dx, dy]) {
  return [x + dx, y + dy]
}

let coords = [1, 2]
console.log(translate2d(coords, [2, 3])
// [3, 5]

Ok, cool, but now looking at the task 1 information

const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]

My first thought is how on earth does the arguments from moveCoordinatesRight2Px get passed in to translate2d? This is something that hasn't been seen so far in the learning track, and it's dropped on us with no explanation apart from stating that translate2d should return a function.

It wasn't until I somehow just happened to stumble upon the answer, that I was able to work backwards from there and understand what is happening.

function translate2d(dx,dy) {
  return function translate(x, y) {
    return [x + dx, y + dy]
  }
}

console.log(translate2d(2,3))
// function translate(x, y) { return [x + dx, y + dy]; }
// ^ Aha!
// From that...
console.log(translate2d(2,3)(1,1))
// [3,4]

I'm not sure if I accidentally missed a lesson before this one or if I'm just not the brightest around, but some more information would be extremely useful, maybe as @Jasstkn mentioned an example of returning a function to give the user some more guidance towards the solution, because as it is, I'm pretty sure if I didn't have some prior experience and this was my first time learning I would be hard stuck here for sure.

SleeplessByte commented 2 years ago

My first thought is how on earth does the arguments from moveCoordinatesRight2Px get passed in to translate2d? This is something that hasn't been seen so far in the learning track, and it's dropped on us with no explanation apart from stating that translate2d should return a function.

You haven't seen it thus far, because we make sure that you don't "see" something until you've had the concept about it, which this exercise is supposed to provide.

However, the current introduction.md and instructions.md not providing enough information is exactly why we want it to be improved! So all input is welcome :)

sahaankit commented 1 year ago

hey @junedev I would love to work on this issue!

junedev commented 1 year ago

@sahaankit I am sorry but we currently don't except community contributions. Here the official notice with a link with more info.

We are currently in a phase of our journey where we have paused community contributions to allow us to take a breather and redesign our community model. You can learn more in this blog post.

safwansamsudeen commented 1 year ago

Hi @junedev, I think you still don't accept community contributions, but is it OK if I open a topic on the forum to discuss this and then submit a PR based on the opinions? I feel this is an important part of improving the JS syllabus, along with promises!

junedev commented 1 year ago

@safwansamsudeen A forum thread to get to a good plan for this issue is definitely appreciated and a good idea. As for the PR, I can make no guarantees at the moment. The JavaScript track does not have any active maintainers currently.

If you are very confident, that you understand enough about how concept exercises on Exercism are supposed to look like, have very good JavaScript and English skills, you can make a PR and I can quickly check and approve. (Probably good to ping me with whatever the final ideas where from the forum thread beforehand.) However, if the PR needs a lot of review rounds to get in shape, I will not have time for that and it is unlikely to get merged in the current maintainer situation.

Zumh commented 1 year ago

Is this still available?

safwansamsudeen commented 1 year ago

@Zumh here's the forum thread for it. I'd appreciate it if you could voice your ideas there.

I didn't actually work on it, as I didn't really know much about Exercism back then, but I'm willing to give it a go now.

If you think you can do it, though, please work on it (as long as junedev approves, see previous comment) - and if you find it too hard, I'll take over 🙂.

DPirate commented 5 months ago

Therefore I would agree to bin the example, despite you probably being able to find references where a writer argues that any enclosure is "a closure".

Won't that dilute learner's understanding of how the language is designed? Understanding closures helped me digest the idea of classes. Also have some fun defining a function conditionally, instead of running whole logic of selecting every time the function runs I could just run the favourable flavour of the function.