Hacker News new | comments | ask | show | jobs | submit login

> In order to have a language with return (and possibly super and other similar keywords) that satisfies the correspondence principle, the language must, like Ruby and Smalltalk before it, have a function lambda and a block lambda. Keywords like return always return from the function lambda, even inside of block lambdas nested inside.

In case you want to get your google/wikipedia on, what Katz is talking about here is a "non-local return". Normal returns return from the immediately enclosing (i.e. local) lambda/fn/block/thing. Non-local ones unwind past multiple enclosing functions to cause an outer one to return.

In Smalltalk, the distinction is between methods and blocks. A return expression always returns from the enclosing method and will unwind past any blocks that the return expression is contained in.

In Common Lisp, I believe you name functions and then indicate the name of the enclosing function that you want to return from by doing `(return-from <fn name> 3)`.

I thought non-local returns were a bit impure and kind of a weird language novelty for a while. Recently, I added them to a hobby language of mine (Finch) and then wrote some code that uses them.

Holy crap are they awesome.

Being able to early return is, I think, one of the things that's really handy about imperative languages. I use it all the time in my code. But that cripples your ability to define your own flow-control-like structures that take functions.

For example, here's some code that sees if an array contains a given item using a for loop:

    function contains(array, seek) {
      for (var i = 0; i < array.length; i++) {
        if (array[i] == seek) return true;
      }

      return false;
    }
Let's say you don't like doing an explicit C-style loop and you want to make your own forEach() control-flow-like function:

    function forEach(array, callback) {
      for (var i = 0; i < array.length; i++) {
        callback(array[i]);
      }
    }
Now we try to refactor `contains` to use it:

    function contains(array, seek) {
      forEach(array, function(item) {
        if (item == seek) return true;
      });

      return false;
    }
Crap, that doesn't work. There's no easy way to make `forEach()` stop early unless you go out of your way to make it support that by having the callback return some magic sentinel value. Or you do something hideous like throw a "return" exception.

Non-local returns solve this neatly and end up being pretty delightful to use in practice.




Thanks, up voted you for the explanation via code sample. It helped click the concept for me. As you mentioned there is an ability to refactor forEach to support early termination and as you state I agree is inelegant--however, I think for different reasoning. Due to the natural language of the method name "for each" there should not be any case where the iteration should end early. That's why I think adding a sentinel return value from the call function is inappropriate. However, if the method name were forEachUntil, then the natural language of the method "for each until" suggests that control flow can, and in at least some cases, should end prematurely. In that case, a sentinel return value is no longer inelegant--its appropriate. To me, that doesn't seem like "going out of the way".

I do have a question regarding early returning the outer function scope, what happens if in your forEach definition, you actually do work on some (or all) elements that needs to be undone. If the outer forEach function is returned early by the anonymous function, how will any of the clean up code get called?


This problem doesn't exist:

    // returns true/false
    array.some(function(item){
      item === seek
    })
Your "magic sentinel value" would be a Boolean, which is exactly how `some` is implemented:

   function forEach(array, callback) {
      for (var i = 0; i < array.length; i++) {
        if (callback(array[i]) === true) break;
      }
    }


Part of the appeal of using higher order functions is that it's cleaner and more "obviously correct" than the equivalent procedural code. You can solve any problem by adding complexity -- but that completely misses the point.

The GP is creating a (toy) language and observes that non-local returns make some code easier/cleaner to write and that non-local returns are therefore not just a language wart, but something to carefully consider when designing a programming language.


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.


Your missing the point here. Maybe his example wasn't the greatest and I can't come up with something better of the top of my head. But I and many do run across the need for a return-from. I mean you could do it with a "return exception" he mentioned but its just too verbose and cumbersome and cannot be abstracted into a library.


There's a couple of JS transformers/macro languages (and other languages that compile to JS) that can implement lexical return more than one frame up the stack using continuation-passing style. But that's even uglier than using exceptions. I added RETURN-FROM to Parenscript late last year (it's in the latest release: http://common-lisp.net/project/parenscript/) and in all the interesting cases it has to use catch/throw, but it tries to optimize that into a normal return sequence when it can.


They do have problems: either you implement them partially and have gaps in your semantics so that returning from a function whose stack frame is no longer active raises an exception, or you have to implement full blown first class continuations. You can then define call/cc in terms of non-local returns:

    function callcc(f){
      f(function(x){ return x })
    }


> either you implement them partially and have gaps in your semantics so that returning from a function whose stack frame is no longer active raises an exception

Exactly right. That's the one nasty corner case I know of. I don't know how often it comes up in practice. I believe Ruby handles it by trying to make blocks not-very-first-class so that it's hard to capture one and have its extent outlast the outer stack frames.


Nah. Ruby just raises an exception in this case:

  class BlockInvoker
    def initialize(&block)
      @block = block
    end

    def invoke
      @block.call
    end
  end

  BlockInvoker.new { puts 1 }.invoke

  def get_block_invoker
    BlockInvoker.new { return }
  end

  get_block_invoker.invoke
I tried to cover this a bit in my post. For callbacks, this semantic is clunkier.


But it's not such an edge case in javascript where asynchronous programming is the norm and stacks get thrown away all the time. In fact it's exactly the scenario that people will immediately try to solve with blocks, but they will fail.

function getMyData(input) { asyncDataCall(input, {|data| return data; // fail }); }


re: non-local control transfer / return-from

And not surprisingly some mad nutter has already implemented it in Perl - https://metacpan.org/module/Scope::Escape::Sugar


And something I only realized recently is that you can _still_ get the Java-style return (block-local return) with the "next" keyword. I used it all the time, but just never thought of it like that. You can use "next" in blocks that aren't iterators.




Applications are open for YC Summer 2019

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

Search: