Hacker Newsnew | comments | show | ask | jobs | submit login

Let's modify the GP's example--instead of checking if an array contains something, he wants to return a copy where a callback is applied to each element. Kind of like "map". However, if the array contains other arrays, he wants to return an empty array.

No problemo in Ruby:

  def map_unless_nested(arr)
    arr.map {|v| return [] if v.is_a? Array; yield v}
  end

  map_unless_nested([2,3,4,5]) {|v| v+1 }
  # => [3,4,5,6]
  map_unless_nested([2,[3,4],5]) {|v| v+1 }
  # => []
This starts looking ugly in JavaScript if we want to generalize "map". Either we can keep "map" clean and wrap our callback so it throws exceptions on array input:

  function map(arr, callback) {
    var ret = [];
    for (var i = 0; i < arr.length; i++) {
      ret.push(callback(arr[i]));
    }
    return ret;
  }
  
  function map_unless_nested(arr, callback) {
    try {
      return map(arr, function(v) { 
        if (v instanceof Array) throw "nested!"; 
        return callback(v);
      });
    } catch (e) {
      if (e==="nested!") { return []; }
      throw e;
    }
  }

  map_unless_nested([2,3,4,5], function(v){ return v + 1; })
  // => [3,4,5,6]
  map_unless_nested([2,[3,4],5], function(v){ return v + 1; })
  // => []
Or, if we want to keep the callback clean, we have to start fiddling with map, and then it's no longer really just map (it's map with halting parameters), and then you're using special return values to inform calling functions that the halting condition was met.

  function map_unless_test(arr, callback, test) {
    var ret = [];
    for (var i = 0; i < arr.length; i++) {
      if (test(arr[i])) { return false; }
      ret.push(callback(arr[i]));
    }
    return ret;
  }
  
  function map_unless_nested(arr, callback) {
    return map_unless_test(arr, callback, function(v) { 
      return v instanceof Array;
    }) || [];
  }
If you still think that exceptions are the best language feature to solve this example, consider the situation where you want to do something with those nested arrays. For example, let's take the Ruby example and modify it so it will return a copy of the first-encountered innermost array with the callback applied.

  def map_first_innermost(arr, &block)
    arr.map do |v|
      return map_first_innermost(v, &block) if v.is_a? Array
      yield v
    end
  end
That was easy. You can see that non-local returns become useful very quickly.



I think you didn't make a fair translation from ruby to javascript there, here goes my attempt:

    function map_unless_nested(arr){                                                                      
        return arr.filter(function(it){ if (it instanceof Array) return it }).length?
            function(){ return [] } : function(fn){ return arr.map(fn) };
    }

    console.log(map_unless_nested([1,2,3])(function(n){ return n + 1 }));
    console.log(map_unless_nested([1,2,[1,2],3])(function(n){ return n + 1 }));
edit: also a translation for the last example

    function map_first_innermost(arr){
        return function(fn){
            return arr.map(function(v){
                if (v instanceof Array) 
                    return map_first_innermost(v)(fn);       
                return fn(v);
            });
        };
    }

    console.log(map_first_innermost([1,2,[1,2],3])(function(n){ return n + 1 }));

-----


They look useful from a specific mindset. How about these?

    function map_unless_nested(arr, cb){
      var res = []
      var has_nested = arr.some(function(item){ 
        if (item instanceof Array) return true
        res.push(cb(item))
      })
      return has_nested ? [] : res
    }

    function map_first_innermost(arr, cb){
      var res = []
      var has_nested = arr.some(function(item){ 
        if (item instanceof Array) return true
        res.push(cb(item))
      })
      return has_nested
        ? map_first_innermost(res[res.length-1], cb)
        : res
    }

-----


Well, now you're dodging the spirit of the problem, which was to work through a single generalization of "map" (or, as tweaked in the second try, "map_unless_test") that is responsible for executing the callback and collecting the results into a new array. Here you are doing the result-collection on your own. You can't express these functions elegantly if you are required to silo the .push(cb(item))-ing into a separate "map" or map-like function.

That's not an outlandish requirement; imagine that the callback can be expensive, and we would like to be able to adjust a centralized "map" function later so it farms things out to different processes/workers/etc.

To solve this for map_first_innermost, you'd have to throw an exception with the nested array, but this is just getting hideously ugly.

  function map_first_innermost(arr, callback) {
    try {
      return map(arr, function(v) { 
        if (v instanceof Array) throw {itWasNested: v}; 
        return callback(v);
      });
    } catch (e) {
      if (e.itWasNested) { 
        return map_first_innermost(e.itWasNested, callback); 
      }
      throw e;
    }
  }

-----


I think you're inventing specific requirements to win a debate. So essentially "I can imagine a scenario that would hard for you to accomplish". Are you saying there is no scenario you can fathom that's difficult to accomplish in ruby? If that's that's the case, you should probably be lobbying to replace javascript with ruby. Not to spend lots of time and effort and pain to turn javascript into ruby.

-----


Of course there are scenarios that are difficult in either language. And lobbying to replace JavaScript with Ruby (I assume you mean in web browsers?) is simply madness for a host of reasons that aren't worth repeating. Lobbying for blocks in JavaScript is sensible, though, since it is actually being considered for the new spec.

It sounds like you think my requirements are contrived. If you prefer to use functions as iterators in JavaScript, and who doesn't (unless you like polluting methods with counter variables?) ... there is no way to have iterators halt early without throwing exceptions or coming up with special return values. Every programmer needs to iterate and break out of loops. It is easy to break from iterator functions in Python and Ruby, since the languages natively support this concept. JavaScript doesn't--that's all we're saying, and it would be nice.

-----




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact

Search: