Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
JavaScript Getter-Setter Pyramid (staltz.com)
142 points by adgasf on Dec 19, 2018 | hide | past | favorite | 50 comments


This article seems to be making up its own terminology and patterns. In JS, a getter is a function bound to a property that is called when that property is accessed. (mdn.io/getter) and a setter is a function bound to a property that is called when the property is set (mdn.io/setter). In JS there isn't any real differentiation you need to make between a function with 0 arguments, a function with 1 argument, and a function with N arguments. They're all functions with some arity that you can call.


The structure of the article is a bunch of definitions that build off of prior definitions, and I think it's best to just view the definitions as applying within the article and overriding any external existing definitions. It's a little unfortunate and unsettling the two of the early definitions (getter and setter) already mean something else in JS, but the terminology choice is trivial in a certain sense and doesn't affect the concepts laid out in the article.

I did find it confusing at first, though. Maybe it could have used a disclaimer, or maybe the author should have chosen different terms (maybe "producer" instead of "getter" and "consumer" instead of "setter"?). I also find it unintuitive to call console.log a "setter", and "consumer" feels better to me.


This was something that tripped me up when I first started university math courses. In more elementary courses, concepts are few and simple enough that you have a fairly consistent set of terminology that applies everywhere. Then I cracked open Spivak's and other higher-level textbooks, and each one is defining its own mutually-incoherent terminology.

It took a bit of adjusting, but local terminology is a lot more readable than if everybody tried to use universally-unambiguous language.


Yeah, this is the OOP perspective on getters and setters. Andre talks from the FP perspective.


However, in JS they have a specific [read, established] meaning, in the OOP sense.

[It's a term established in the language specification.]


> [It's a term established in the language specification.]

I'd say JS getters/setters are syntactic sugar for the OOP concept the author is talking about.


It's the main mechanism in JS to define functional accessors, at least since ECMA-Script 3. Before this, the terms have been used loosely, as well, for explicit accessor methods (like in Java).

[Edit] Correction: should have been, at least since ECMA-262 5th Ed.


Setters have no equivalent in (pure) FP.


Saying console.log is a setter seems horrifically wrong, console.log abstracts out IO and doesn't necessarily mutate any state that is within the program. It leaves the happy confines of the program's abstraction/memory model and puts data out into The Real World.

That all said, within the model being established by the author, everything does make sense, eventually, and the difference between IO output functions and setters, for the sake of the abstractions that the author builds, is non-existent.


State monad?


Author here: the inspiration for this article came primarily from Erik Meijer and this talk https://www.youtube.com/watch?v=sTSQlYX5DU0 where he talks a lot about Getters and Setters in Scala, although he's not focused on Scala (Erik has worked on C#, Haskell, and is now working on a new PHP-like language at Facebook).


Yeah. source/sink would have been a more appropriate terminology IMO.


Hey Gus, this actually does make some mathematical sense. See https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Ke...


I'm not saying it doesn't make sense, just that it ignores existing terminology of the language, which just leads to confusion down the road.


There are also existing terms which are much more fitting, e.g. provider and consumer.


This is a pretty interesting ontology of one path generalization.

I find the getter/setter nomenclature a little imprecise. It makes me think of interacting with fixed state, rather than input and output into completely abstract "systems" on the other side of the function calls. To me, provider/consumer seems a bit more on-the-mark.

It seems to me that there are other paths of generalization you could follow, as well (e.g. https://leanpub.com/combinators). Even though we all know that you can model...well...anything with functions, it's still enlightening and mindbending to explore the patterns for actually doing so.


> Writing the above code example with none of the abstractions in the Getter-Setter Pyramid requires more amount of code, which is also harder to read.

    let usersRange = (start, end, result, doneCb, errorCb) => {
      let xhr = new XMLHttpRequest();
      xhr.responseType = 'json';
      xhr.open('GET', 'http://jsonplaceholder.typicode.com/users/' + start)
      xhr.send();
      xhr.onerror = ev => errorCb();
      xhr.onload = ev => {
        result.push(xhr.response);
        if (start < end)
          return usersRange(start + 1, end, result, doneCb, errorCb);
        done(result);
      }
    };

    usersRange(0, 10, [], /* completion callbacks */);
Simpler, not really harder to read (half of it is just boilerplate around XHR setup, which is abstractable away). The meat of it is onload handler.


Not sure, but I think an async function with fetch is easier to read.

    const usersRange = async (start, end) => {
      var result = [];
      for (let current = start; current < end; current++) {
        result.push(await fetch(`http://jsonplaceholder.typicode.com/users/${current}`).then(r => r.json())
      }
      return result;
    }


    let usersRange = (start, end, result, done) => {
      if (start == end)
        return done(null, result);

      GET('http://jsonplaceholder.typicode.com/users/' + start, 
        res => usersRange(start + 1, end, (result.push(res), result), done),
        err => done(err)
      )
    };
GET() just wraps the XHR code in my original post and provides access to onload/onerror callbacks. I used XHR, to avoid using fetch/Promise, which is part of the pyramid.

I mean, I prefer async/await. The point is that it's just not inherently that much more verbose to write callback based async code. Composition would be more complicated. You'd have to use some abstract helper methods, or nest the callbacks. For example:

    chain(
      (_, next) => usersRange(0, 10, [], next),

      ([err, res], next) => {
         if (err)
            // blabla
    
         usersDoSomething(res),
      }
    )
vs:

    try {
       let res = await usersRange(0, 10),
       usersDoSomething(res),
    } catch(ex) {
       if (err)
          // bla bla
    }
You can do some pretty fun stuff with functions, extending JS syntax so it has simulated goto for example:

    let users = [], nextUser = 0;
    labelledChain(
      (_, next) => GET('/users', next),

      'got-users', ([err, res], next) => {
         users = res;
         next();
      },

      'get-next-user', ([err, res], next) => {
         if (users[nextUser])
            GET('/users/' + users[nextUser].id, next);
         else
            doneCallback(null, users);
      },

      'got-next-user', ([err, res], next) => {
         users[nextUser] = res;
         nextUser++;
         next.goto('get-next-user');
      },
    )
Fun challenge implementing labelledChain so that this all works. :D


> extending JS syntax so it has simulated goto for example

JavaScript has goto, it's the `continue` statement[1].

1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...


That's not a real goto. You can only use a label for breaking out of outer loops. Useful for that, but not for other kinds of control flow control.


I know it's not too bad to write callback based async... been doing it for a couple decades (first java, then flash adapters or hidden frames, then xhr). I just find if looks/feels much cleaner with async functions and promises.

I have to admit, I didn't like promises and preferred node style callbacks until async/await and have been using it with babel since before the name change.


Shouldn't

    return nextUser(...
be

    return usersRange(...  ?


Yes. Sorry, it was originaly all named nextUser, but I did miss that one rename.


Similar in concept to the General Theory of Reactivity, GTOR: https://github.com/kriskowal/gtor/


I'm interested in this simple closure code from the article:

     function getGetNext() {
       let i = 2;
       return function getNext() {
         const next = i;
         i = i * 2;
         return next;
        }
     }
As a lisp coder [and not a JS coder], I'm wondering: why does the function getNext() have to be named? Why not just say something like:

     function getGetNext() {
       let i = 2;
       return function() {
         const next = i;
         i = i * 2;
         return next;
        }
     }


It doesn't have to be named, your second example still works


naming anonymous functions can provide some clarity in stack traces however


I got the syntax right? Was completely guessing.


Yep. Nowadays you'd be more likely to see it as:

    return () => {
      const next = i;
      i = i * 2;
      return next;
    }
But in this case the two are equivalent.


Or as an alternative one can use a generator, they are built for that purpose AND are supported by a wide range of other JS constructs without any need to create iterators manually and all that.

   function * getNext(){
       let i = 2;
           while(true){
               i = i * 2;
               yield i;
           }
   }
Here, no closure needed.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guid...


I think that's no longer getter-getter and the example was to show a function that explicitly returns a function.


The name is optional, and probably just to help explain the code.


There is a great talk by Erik Meijer (who invented Rx) showing this very neatly: https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Ke...


What would AsyncObservable look like in JS?


It would be like an Observable, where the Observer's next/error/complete are changed from X=>() to X=>Promise where the Promise indicates the Observer is done with the consumption, telling the producer that it can continue producing more values.


If the producer is ready to produce one/more values prior to the Observer's being ready to consume them, do those values go into a queue?

If there is more than one Observer of the same AsyncObservable, is there a separate queue for each of them?

I'm thinking of AsyncSink[1] used by AsyncIterableX[2] (both of IxJS[3]) and how they might be conceptually related to implementations of AsyncObservable.

[1] https://github.com/ReactiveX/IxJS/blob/master/src/asyncsink....

[2] https://github.com/ReactiveX/IxJS/blob/master/src/asyncitera...

[3] https://github.com/ReactiveX/IxJS


I thought this was going to be about lenses implemented in JavaScript, if it was I didn't get it. The Haskell lens[1] package (which implements a type of lenses referred to as van Laarhoven lenses after their discoverer Twan van Laarhoven) builds up a hierarchy (pyramid if you will) of lenses with varying degrees of generality and power.

[1]: http://hackage.haskell.org/package/lens-4.17#readme


There is no copyright on terms 'getter', 'setter' etc.

For the first part I didn't like the article since it's somewhere between fp and oop, vague or foggy. But it's just my interpretation based/biased with previous knowledge.

Once I assumed the article is conceptual and in harmony with it's definitions, everything got much more sense.

I like it.


My OCD kicked in at the end of the article looking at that pyramid. He should have put a triangle on top :)


Maybe it should've been called "the getter-setter ziggurat."


Same, maybe he is going to complete it later with something special?


I'm sure the author knows more about the topic than me. However, it reads like someone trying to rationalize something that isn't quite rational.

IE: Walking into an old antique store and attempting to explain the genius logic behind it's organization. Shortly after the owner comes out and says, "No, I just throw shit wherever."


> The cornerstone of JavaScript is the function.

JavaScript is specified in terms of objects, has richer facilities for dealing with objects than functions, functions are objects but not vice-versa. So, objects are the cornerstone. Callers are free to ignore a function's arity even.


Yup, everything is nicer in Javascript if you're working with objects.

There's no type checking or syntactic sugar for dealing with first-class functions, so complex functional programming leads to brutal stack traces and if you ask a debugger what any part of this "pyramid" is, it tells you, "well, it's a function."

If I'm using a parser combinator in Haskell, for instance, I can just say:

   liftA2 (+) parseInteger parseInteger
It's pretty clear (if you're familiar with functors) that I'm parsing two integers and adding the result. And it has to typecheck, so 90% of my stupid mistakes are caught by the compiler.

But, also, if I go into ghci, I can see what it is:

     :t liftA2
     Applicative f => (a -> b -> c) -> f a -> f b -> f c
So I can see the first argument (a -> b -> c) is a function with two arguments, and the second, f a, and third, f b, are boxed in the Applicative, and the result, f c, is boxed in the Applicative. That's how you can understanding that it's combining two parsers using + and then the result is itself a parser.

Javascript, even though it supposedly has all its type information at runtime, really doesn't know any of this because a function is a function and that's all it knows. You could replicate liftA2 in Javascript, but it would be entirely mysterious what's going on.


Is this the write-up of his talks?

I remember this one, from the Uphill Conference: https://www.youtube.com/watch?v=fdol03pcvMA


Yes, though depending on how long ago you were present at such a talk, the talk might have covered just part of this.


[flagged]


> lol. nice and detailed april fools article. just wish it wasn't posted in December.

> > avoid global state by having a global name that hold the state

That state is not global though, you can use multiple getters from a gettergetter in parallel.


JavaScript was a mistake.


JavaScript is truly the new Java. It even has design pattern articles now.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: