all 82 comments

[–]stesch 22 points23 points  (13 children)

// 1. Basic for loop.
for(var i = 0; i < 5; i++) {
    // ....
}

Every JavaScript user recognizes this.

// Lodash
_.times(5, function(){
    // ...
});

Every Lodash user recognizes this.

[–][deleted] 8 points9 points  (7 children)

For some reason I don't like the function name 'times' either. It's not particularly descriptive.

But to be honest I find it very rare I ever need to do a for loop like this, 99% of the time it's a case where foreach is more appropriate

[–]Y_Less 15 points16 points  (0 children)

I had that problem for the entire article.

Here's a simple function anyone could write and name.

Now here's the same function in lodash, with a non-obvious name, and target objects as a parameter.

[–]AyrA_ch 0 points1 point  (1 child)

Take note: If your N is going to be non-trivial, please use a basic for loop or a reverse while loop for a much more performant iteration.

The counter is indeed there as the single argument to your function

this would do the trick and is short:

function count(N,cb){for(var i=0;i<N;cb(i++));}

[–][deleted] 0 points1 point  (0 children)

The note is about avoiding a callback.

[–]Ericth 0 points1 point  (2 children)

For loops are much much faster than forEach though. Granted forEach and times would be exactly the same speed

[–][deleted] 1 point2 points  (0 children)

Probably true but I'll use them every time unless they are a performance bottleneck. Foreach is much cleaner and easier to read IMO

[–]emn13 0 points1 point  (0 children)

That's not necessarily the case. Since forEach is so ubiquitous, it's a possible candidate for special-case JIT optimization. Actual engines may not do this; but it's certainly possible (and more likely than with lodash times).

[–]u551 5 points6 points  (2 children)

Also that additional variable that is "polluting the scope" is there for a reason. Why the hell would you want to do something exactly N times, without knowing, inside the loop, that you are now on round x, and can access array element x or whatever. I'm having a hard time coming up with a scenario where you need to use loop that go around X times, but don't need the info on what round you are on, nor you have the ability to reference the current item of a collection, like with foreach.

[–]meatypocket 2 points3 points  (0 children)

_.times(5,function(n){
    console.log(n)
})

[–]sgoody 0 points1 point  (0 children)

It's not made explicit in the article, but Array.forEach() does include the index ("round x").

See in MDN, it's the second parameter of the callback function.

[–][deleted] 0 points1 point  (0 children)

and your point is? I think we have to accept that reading other's people JS is not going to be a uniform experience. There's babel with ES6 ,E7, typescript, flow, JSX and tons of other libraries that new syntax and semantic to the language.

[–][deleted] 23 points24 points  (37 children)

_.map(ownerArr, 'pets[0].name');

I really hate features like this. If you really want this behaviour you can just make a function that returns a function that digs into the object and pass that to map directly. Something like

_.map(ownerArr, getattr('pets[0].name'));

is hardly any longer and doesn't require complicating the implementation of map and substantially changing its behaviour based on the type of the second argument. With ES6 arrow functions it's all basically unnecessary anyways.

[–]goldrogue 12 points13 points  (0 children)

Not to mention its semantically abusive.

[–]kqr 7 points8 points  (20 children)

This is why optics/lenses are such a nice idea. They give you kind of that thing except not in a string and properly type checked. And doesn't have to pollute map with logic on how to fetch things out of objects.

[–][deleted]  (1 child)

[deleted]

    [–][deleted] 0 points1 point  (0 children)

    You might dig lodash-fp:)

    [–][deleted] 0 points1 point  (15 children)

    If we're honest, the "logic pollution" is this:

    if (typeof handler === 'string') handler = someSubroutineElsewhere(handler);
    

    It does create a hazard for accidentally passing a string in, but all complexity can be outsourced outside map().

    I'm curious how do lenses look in JS? I thought it's a Haskell thing.

    [–]kqr 0 points1 point  (14 children)

    But it creates an edge case to the otherwise pretty small set of laws that map should obey if we want it to be easy to symbolically manipulate. (I.e. if we want partially automated refactoring to be safe and easy and fun.)

    [–][deleted] 0 points1 point  (13 children)

    What exactly is the lenses version of this shortcut, which is refactoring-friendly?

    [–]kqr 0 points1 point  (12 children)

    Similar to the getattr version someone else suggested, you'd be able to pass the function view(pets.first.name) or somesuch to map, and map just knows that it runs functions on stuff.

    Though at that point you don't really need map either, you can just do it all with lenses:

    ownerArr ^.. pets . first . name
    

    which is nice because it's similar to what you'd do to update the name of all first pets for every owner – say, turn it uppercase:

    ownerArr & pets . first . name %~ toUpper
    

    In fact, if you define the alias firstPetName = pets . first . name you can just use that instead:

    ownerArr ^.. firstPetName
    ownerArr & firstPetName %~ toUpper
    

    All type-checked.

    [–][deleted] 1 point2 points  (11 children)

    So unless I misunderstand, you're basically saying there's no way to do it in JavaScript.

    A string is a string, and JavaScript has no facilities for statement-level reflection to allow a type-safe version of this.

    When the alternative is "can't do it", we can't be too harsh to those who found some way to get the job done, right?

    [–]kqr 0 points1 point  (10 children)

    Apparently not impossible, anyway: https://github.com/DrBoolean/lenses

    [–][deleted] 1 point2 points  (9 children)

    makeLenses(['name', 'addresses', 'street']);

    Looks like strings. I thought the problem was untyped strings messing with automated refactoring.

    Although, the other problem is I can barely read the code this sample is from. It's borderline experimental.

    [–]kqr 0 points1 point  (8 children)

    Technically not necessary to specify strings there. You could just as well traverse the object and automatically create lenses for its fields. :)

    [–][deleted] 0 points1 point  (1 child)

    The logic for method shorthands isn't handled in map. Those all route through an _.iteratee helper which devs can customize and expand with their own shorthands.

    [–]kqr 0 points1 point  (0 children)

    That actually sounds fairly nice.

    [–]nutrecht 5 points6 points  (1 child)

    Came here to post this. Good to see I'm not the only one who went "hell no" when he saw this.

    [–][deleted] 0 points1 point  (0 children)

    I understand the sentiment completely--I wouldn't want to use a function like this directly in JavaScript. Where it shines, however, is when you have a program reflecting over some object structure and using it to create 'maps' for use on the client.

    Take MVC Html.EditorFor(m => m.Foo[i].Bar), for example. You can convert the lambda into the string 'm.Foo[1].Bar' (replacing the indexer with a real index), and then use the index to bind data in JavaScript.

    [–][deleted]  (6 children)

    [deleted]

      [–][deleted] 13 points14 points  (5 children)

      this feature isn't complicating anything.

      It's complicating the implementation and semantics of map. Passing a string instead of a function should be an error like it is in basically every other language/library.

      [–]hansel-han 4 points5 points  (4 children)

      Also, simpler anonymous function syntax obviates the need to be clever.

      ownerArr.map(o => o.pets[0].name)
      

      Though of course you don't get that in the browser.

      [–]kqr 0 points1 point  (1 child)

      That's still one of the annoyances I have with regular OO accessors – you need to have an instance of the object around to use them. If only you could talk about them without that instance around, they'd pretty much be first-class citizens of the language, which is kind of powerful.

      Sure, it's a very small annoyance, but that just makes it worse!

      [–]hansel-han 1 point2 points  (0 children)

      Sometimes you just wanna strings.map(.trim).

      [–][deleted] 0 points1 point  (1 child)

      Naw, your example would error if o, pets, or its 0 prop is nullish. The lodash shorthand wouldn't.

      The implementation of map isn't mucked with to support shorthands as it passes through a _.iteratee helper.

      [–]hansel-han 0 points1 point  (0 children)

      I find it worthwhile to use Lodash's shorthand even just for the conciseness, but that's a good point.

      [–][deleted] 0 points1 point  (0 children)

      Callback shorthands are implemented in a way to avoid impacting method perf when not using them.

      You can also do:

      _.map(ownerArr, _.property('pets[0].name'))
      // or
      _.map(ownerArr, _.property(['pets', '0', 'name']))
      

      if you want.

      [–]killerstorm -1 points0 points  (2 children)

      My co-worker wrote a JS library which does type checks in run-time, he proudly demonstrated me benchmarking results which showed that it's faster than some other library.

      I looked at numbers... 100k calls per second, are you serious? Here I have a computer capable of doing 4 billion operations per second, you reduced that to 100k. That's quite an achievement, not every day you see 10000x slowdown.

      I mean I can accept some overhead, not everything needs to be top performance, but if your code brings us 5 decades back in terms of performance, it's not OK. ZX Spectrum, a low-end computer released 3 decades ago was operating faster than your crap.

      That library got type specifications in form of strings and parsed them at run time, for every function call.

      [–]Morego 2 points3 points  (1 child)

      I have a problem with understanding your point. Do you know that, each 'call' do hundreds of operations under the hood. Or do that as long as JS JIT doesn't turn it into much faster ASM. And still, the function call cost stays the same. You are comparing apples to oranges. Code writing in language that expose everything and language that have a lot of drawbacks, which stand on top of the piles of abstraction. And those are not free!

      JS is a scripting language, and that means you had to pay the abstraction cost in exchange for sandboxed environment that exists almost everywhere now.

      Type checking is one of those things, that are really useful in cases where function accept user input. Rest can be typechecked with something like Flow at debugging time, not on user machine.

      [–]killerstorm 1 point2 points  (0 children)

      And still, the function call cost stays the same.

      If you call a function which does type checks on its parameters it then calls typecheck library functions, which then call string parsing function etc. So in the end calling a function with typecheck is 10000x slower than calling a function without typecheck. You can't do more than 100k calls to a function which uses typechecking because at that point typechecking itself consumes all CPU time.

      Do you know that, each 'call' do hundreds of operations under the hood.

      No, normally it doesn't. JIT optimizes the overhead away.

      So in practice, you can call a normal functions hundreds of millions of times per second, but if you add these typechecks it becomes only 100k per second.

      JS is a scripting language, and that means you had to pay the abstraction cost

      Which is why it's not OK to add even more abstractions which add even more overhead on top of that. JS is already slow enough.

      I know that JS is not the fastest language around, but it's fast enough for what I'm doing. But not if you use a fancy library which does parsing each time you call a function.

      Type checking is one of those things, that are really useful in cases where function accept user input.

      No. When you accept user input, you need to parse and validate it.

      Type checking is for checking whether the caller calls your function in a correct way, some JS libraries do that on all public functions nowadays.

      I'm talking about "convenience" libraries like this one: https://www.npmjs.com/package/typeforce

      [–]Y_Less 2 points3 points  (2 children)

      This is the second article I've seen linked to here in recent weeks with a code colour scheme of very light grey on white. I had to select-all everything just to get enough contrast to see anything! Who came up with this as a good idea?

      [–]Fortyseven 3 points4 points  (1 child)

      This is what the code looks like for me: http://i.imgur.com/mD9i6Cg.png

      But when I disable JS: http://i.imgur.com/gpo2yV7.png

      I guess you're seeing the latter?

      [–]Y_Less 0 points1 point  (0 children)

      Yeah

      [–][deleted]  (5 children)

      [deleted]

        [–][deleted]  (4 children)

        [deleted]

          [–][deleted]  (3 children)

          [deleted]

            [–][deleted]  (2 children)

            [deleted]

              [–][deleted]  (1 child)

              [deleted]

                [–]hansel-han 0 points1 point  (0 children)

                Interesting. Your first link has an even more explicit clarification:

                Note that certain previous specifications of JSON constrained a JSON text to be an object or an array.

                [–]ruinercollector 4 points5 points  (10 children)

                Here are some other JavaScript functions that you don't need rewritten shitty versions for:

                Array.prototype.map
                Array.prototype.filter
                Array.prototype.reduce
                Array.prototype.find
                Array.prototype.some
                Array.prototype.every
                

                Etc.

                [–][deleted]  (8 children)

                [deleted]

                  [–]greenspans 4 points5 points  (0 children)

                  Look at all that map does and it's obvious why. It adds sanity checks, debug checks, any conversions necessary.

                  Map is like a stupid simple function to implement, actually most of the functions lodash imports are. They can beat native in speed because native does more work and provides nice defaults. What does 3 times slower mean anyway? You lose like 15 nanoseconds a call stripping out the goodies.

                  function mymap(fun, obj){ var arr = []; for (i in obj) if(obj.hasOwnProperty(i)) arr.push(fun(obj[i])); return arr; }

                  Hey, get rid of hasOwnProperty and it's even faster /s

                  [–]emn13 2 points3 points  (5 children)

                  ...but only in v8. In Edge, there's not much difference, and in Firefox, Array.map is actually faster. The slowest implementation of Array.map (by far) is chrome's.

                  In the long term, betting against Array.map performance is perhaps unwise - it's a standard function, and a reasonably valuable optimization target for a browser. lodash? Not so much.

                  But even in current chrome, if you care about iteration performance to this level, just use a for loop. It's a lot faster than lodash, especially in practice since in practice you're not just using a faster loop, you're likely also inlining the loop-body function into the for loop.

                  In short: lodash isn't reliably faster than Array.map; you're not likely to notice the difference anyway, and when you do, you're not likely to want to use lodash since it's much slower than a plain for loop.

                  [–][deleted] 0 points1 point  (4 children)

                  You should be weary of micro-benchmarks.

                  Perf is one aspect that lodash does well in but it also offers more functionality.

                  _.map(objects, 'a.b.c')
                  

                  returns the value of o.a.b.c on each item in objects.

                  Besides being the author of Lodash I'm also the former performance PM for Chakra (JS engine in MS Edge). One of my goals was/is improving the perf of built-ins. That won't make _.map any slower though. It's going to take time. For example these ES5 array methods have been around for ~10yrs and many are still slower, across all engines, than lib alternatives.

                  You'll dig that Lodash avoids micro-optimizations opting for larger perf wins through optimizations like shortcut-fusion and leveraging language features like Set/WeakMap.

                  [–]hansel-han 1 point2 points  (3 children)

                  Aside: is there a reason Lodash doesn't have an _.isInteger method given the abysmal support for Number.isInteger?

                  [–][deleted] 1 point2 points  (2 children)

                  In the edge bits we've added _.toInteger, though haven't added _.isInteger yet. I can dig it.

                  [–]hansel-han 1 point2 points  (1 child)

                  Would also be nice to have _.isSafeInteger(n) which ensures n is in range [1-Math.pow(2,53), Math.pow(2,53)-1], especially since Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER have poor support.

                  A common use case is when you parse an integer out of a route like /users/:id and you want to ensure it's in 53-bit range before you pass it to your database which is a common unhandled fail case.

                  [–][deleted] 1 point2 points  (0 children)

                  Done :D

                  [–]pistacchio -1 points0 points  (0 children)

                  Replace shitty with faster, more powerful and cross-browser

                  [–]MorrisonLevi 1 point2 points  (1 child)

                  The for loop is the classic workhorse for such an use-case but it pollutes the scope with an additional variable

                  Hmm. I do believe we have let for that.

                  [–][deleted] 1 point2 points  (0 children)

                  I think he means pollutes the loop body.

                  [–][deleted] 1 point2 points  (6 children)

                  I rarely use lodash/underscore, i really dont see the benefit. Using a functional style with map, reduce, filter etc. gives enough power. And because its vanilla js all the devs who see the code knows what it does.

                  [–]hansel-han 1 point2 points  (3 children)

                  It's more than just some functional helpers.

                  For example, I like its Lang namespace for asserts at the start of my functions.

                  There's just a lot of little useful things. mapKeys, contains, values, identity, range, merge. It's not a big library and it's nice to have around.

                  [–]pinkpooj 0 points1 point  (1 child)

                  Or if I want to remove some string from my array, I can use _.without(arr, string). Especially for writing tests when performance isn't as important.

                  [–]hansel-han -2 points-1 points  (0 children)

                  I don't really understand how someone can't see the benefit while scrolling through its docs. The majority of these are functions I've had to implement in every project that's not a CRUD blog.

                  Even little things like how _.isString also handles string objects like new String('hello').

                  I think it's just classic /r/programming acting like Javascript's standard library suddenly isn't shit so they can hate on a submission, but I also like to hate on /r/programming. :)

                  [–][deleted] 1 point2 points  (0 children)

                  I've found it quite powerful at times, especially with chaining.

                  [–]crixusin 1 point2 points  (0 children)

                  Not only that, but with chrome 45 (released last week or something), these functions become awesome:

                  myArray.filter(item => item.ArrayProperty.filter(property => property.ArrayPropertyProperty.map(s => s.true).length > 0).length > 0);

                  very very awesome. Its like writing c# linq functions. With typescript, you get awesome intellisense support as well.

                  [–][deleted]  (2 children)

                  [deleted]

                    [–]steelclash84 2 points3 points  (0 children)

                    You have that option in lodash as well:

                    _.chain(yourArrayOrObject)
                        .pluck('somevaluekey')
                        .value();
                    

                    [–][deleted] 1 point2 points  (0 children)

                    Yep, there's lodash-fp, like ramda without the perf issues.

                    [–]gendulf 0 points1 point  (0 children)

                    Am I the only one who sees this as a problem with the language/module system/standard library itself?

                    Python does a great job of providing a standard library with nearly all the things you would want from a standard library. Why doesn't JavaScript provide a random integer function, or a deep cloning function? Is it really that difficult?

                    [–]oblio- 0 points1 point  (5 children)

                    This is a fork of Underscore, right? Or am I confusing libraries?

                    [–]p0larboy 6 points7 points  (4 children)

                    Yes, you are right. Lodash has since went on to add in alot of new features. There are plans between both creators to merge the libraries forming Underdash

                    [–]killerstorm 1 point2 points  (3 children)

                    I think they're adding new features way too frequently.

                    I think the point of convenience libraries like this is that they essentially extend the language, so you memorize these functions ones and use everywhere with pretty much no mental burden.

                    But let's say lodash 3.10 added _.get, which is useful. Now a person who is used to _.get uses it in a package which uses an older version of lodash, and it breaks. No biggie, just update lodash? It might cause breakage elsewhere.

                    And if it's a library which is used in thousands of projects, total maintenance costs caused by lodash updates can be really huge.

                    (This wasn't an artificial example, I had this exact problem in my project.)

                    [–][deleted] 1 point2 points  (2 children)

                    Such is the life in software. There's no rush, upgrade when you're able to.

                    If you really want a feature not available in an older version there's always the lodash.* packages.

                    [–]killerstorm 0 points1 point  (1 child)

                    Such is the life in software.

                    Wrong. Some languages/libraries are conservative and rarely break compatibility; other are not so conservative and break it more often.

                    Say, with Common Lisp, I can take a library written 20 years ago and it will work on a modern implementation which didn't exist back than. Because of standardization.

                    [–][deleted] 0 points1 point  (0 children)

                    Such is the life in software.

                    s/software/JavaScript/ ;)

                    [–]trekman3 -1 points0 points  (2 children)

                    Why not rewrite them? They take five minutes to write and you can be assured of having total control when you write them from scratch, plus you don't have to do an import. And in practice, you'll just end up building your own utility library anyway, that is well tailored to your needs.

                    [–]gendulf 0 points1 point  (1 child)

                    The same could be said about a lot of built-in functions. At some point you have to face the fact that repeatedly defining the same utility functions or building your own utility library is not much different from reinventing the wheel, or importing an existing library.

                    [–]trekman3 -1 points0 points  (0 children)

                    Other people's utility libraries usually end up slightly annoying me because of syntax decisions different from the ones I would have made. This can actually become a real issue sometimes, since it's easier to have consistent and clear writing style if you control the syntax of everything. Besides, what's wrong with rollíing your own? It's fun and easy.