Hacker News new | ask | show | jobs
by TazeTSchnitzel 4115 days ago
Being able to do

  foo = bar.x || 3;
rather than

  foo = bar.hasOwnProperty('x') ? bar.x : 3;
is nice.
2 comments

Personally, I would say

    foo = ('x' in bar) ? bar.x : 3;
instead. The problem with your code is that if the property bar.x exists, but is one of any number of values, like 0 or false, your code will still set foo to 3. Requiring properties to be explicitly created means that you're separating existence from value, which are two very different things in my book.
Which are some of the most insidious bugs possible in JS. I shudder every time I see the syntax in your post's parent. It's a red flag. Your solution is good. I also use a lot of typeof(bar.x) !== 'undefined' since it's very explicit.
Every time?

I mean, very many times (for example) you're pulling in some JSON from a server app, which will have types properly enforced at the database and/or application level. There are still gotchas around defaulting to true, whether or not an empty string is a valid value and so on, but there are many cases where foo || bar is safe enough.

I have to disagree with you there. It's simply not a good idea to have a works-sometimes syntax for "if property is unset". The brevity doesn't make up for the fact that you sometimes have to fallback on the explicit check anyway. It may look clever but it's an abusage, and you're eventually going to get a production error, unless you're testing for it, and if you've got tests for that, brevity has already lost.
Well, I'm not responsible for your code, so you do whatever you want. But this, for me, is like being Van Halen and seeing brown M&Ms in the bowl. It's a red flag when I see that syntax in code that I should be much more defensive about what's happening everywhere else. There are no assurances about data, especially about data coming across the network.
Agreed. And unless I'm reading the document wrong, this would also be prohibited in strong mode:

  let x = {
    keyA: "valueA"
  };
  x.keyB = "valueB";
I think they're trying to push onto [Maps].

[Maps]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Yep, they're explicitly trying to do that.

For what it's worth, you can still use "options hashes" in your argument APIs in strong mode using objects; you just write a library function something like:

    function options(options, defaults) {
      // the final args start off as a clone of the default args
      let args = Object.clone(defaults);

      // we then loop through the keys and copy in any overrides
      for(let key of Object.keys(args)) {
        // ignore inherited properties and skip missing ones
        if(args.hasOwnProperty(key) && options.hasOwnProperty(key)) {
          args[key] = options[key];
        }
      }

      // args now has all of the overrides from options
      return args;
    }
And then in all your functions that take options objects:

    function bakeBread(ingredientOverrides) {
      let ingredients = options(ingredientOverrides, {
        flourType: 'whole wheat',
        sugarAmount: '3 tbsp',
        waterAmount: '1 cup',
        milkAmount: '0.3 cups',
        flourAmount: '4 cups'
      });

      let batter = mix(ingredients);
      return bake(batter);
    }
JS is still quite dynamic, even in strong mode — you can define arbitrary objects and types at runtime, and easily inspect/reflect on them — it's just a little harder to silently corrupt data.

The neat thing about using named arguments with objects is that in typed variants of JS — for example, TypeScript, or perhaps someday SoundScript — you can actually typecheck them! Maps can't do that in any language I know of: by design they can contain anything.

The Java (5+) Map interface can be typed using generics (which -- if JS eventually introduces static typing -- I hope is implemented).

  Map<String, Integer> = new HashMap<String, Integer>();
That declares a Map with a String key and Integer value. Is this what you're thinking of?
Not what I was thinking of, but I should've been more explicit. Using generics you can type the keys and values of Maps in any language with generic support that I'm familiar with: certainly C++ and Java can. But you can't make type assertions about certain values existing under certain keys: that's what structs/classes/etc are for. But, those constructs can't easily be generated at runtime, and are typed nominally: even just as a caller you have to explicitly say they inherit (or in Java, implement) a specific type. However, with TypeScript-like structural subtyping, you can do:

    interface BreadIngredientOptions {
      flourType?: String; // this is the syntax for optional strings
      sugarAmount?: String; // ditto: it's the ? that makes it optional
      // ...
    }

    function bakeBread(ingredientOverrides: BreadIngredientOptions) {
      // ...
    }

    // callers don't need to explicitly inherit or implement to be type checked
    // however, since all properties are optional, this is less interesting
    bakeBread({
      flourType: 'white'
    });
But you can do even better than that example shows. One common problem with maps-as-named-arguments is that you can't easily determine which arguments are required and which are optional. With typed optional properties and structural subtyping you can enforce that at compile time, as follows:

    interface MyArgumentInterface {
      requiredArg: number;
      optionalArg?: number;
    }

    function f(args: MyArgumentInterface) {
      // ...
    }

    // This works:
    f({
      requiredArg: 10,
      optionalArg: 5
    });

    // This also works:
    f({ requiredArg: 50 });

    // This statically throws at compile time:
    f({ optionalArg: 10 });
It's a combination of the simple object literal syntax from raw JS that makes it easy to create objects of arbitrary types, with structural subtyping. I'm not aware of any language with the same features (but would love to be corrected!).