Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Mirah Lambda Proposal

Mirah Lambda Proposal

Lambda types

  • Stabby / Lambda
  • Block
  • Nested Methods

Stabby / Lambda

A stabby lambda or a lambda is a way to create what would be an anonymous class in Java or a lambda in Java 8. It has about the same semantics as a lambda expression in Java 8, with a few changes. Eg, it's first class and return means return to the caller not the defining scope.

Syntax

Lambdas have two syntax forms inspired by Ruby. First is what in Ruby is called the stabby proc (Stabby Lambda). It looks like ->(args) { body } where -> points out that there's a lambda here, () contains the args and {} the implementation. The arguments are optional.

Alternatively, you can use the lambda keyword syntax. It takes a type and a block. Unlike the stabby syntax, the arguments are passed within the block using block arguments syntax.

# Stabby syntax is totally Rubyish, but doesn't allow explicit typing w/o cast

-> { puts "hello"}
-> (arg1) { puts "hello, #{arg1}" }
-> (i: int) { puts i }
# ugly casting
Runnable(-> { puts "hello"})

# lambda takes a type every time, args are block args

lambda(Runnable) { puts "hello"}
lambda(Fooer) { |f: Foo| f.bar }

Semantics

First class construct

Can be assigned to variables to create anonymous classes/functions

eg

x = lambda(Runnable) { puts "hello" }

No Non-local return

When you use ->(){} or lambda{}, return means return to the caller, not to the lexically defined scope.

eg

foo -> (i: int) do
  if i % 2 == 0
    return i * 2
  end
  i - 1
end

foo lambda do |i: int|
  if i % 2 == 0
    return i * 2
  end
  i - 1
end

Closure: locals, fields and methods

lambdas close over locals, fields and methods within their scope.

eg

@one = 1
two = 2
def three; 3; end
x = -> { puts @one + two + three }
x.run

Block

Syntax

foo do |f|
  f.bar
end
foo { puts "yay blocks" }
list_o_numbers.sum {|acc, n| acc + n }

Semantics

Not First Class

Blocks go along with calls or macros, but can't be referred to by themselves

Non-local Return

Blocks support non-local return, which means that an explicit return inside a block will attempt to return to the scope it was defined in. This makes things tricky when the block is called from a different stack of execution. Ruby has this same constraint and it is often super handy, so I think it's worth it.

def isIn(haystack: IntList, needle: int): boolean
  haystack.forEach {|straw| return true if straw == needle }
  return false
end

Non-Local Return and You

TL;DR blocks have non-local return; lambdas don't.

Mirah currently supports blocks, but they're not very featureful. They don't close over the enclosing object, so you can't transparently reference instance variables or methods. You have to do annoying things like:

this = self
fn_that_takes_block do
 this.awesome_behavior
end

Which is less than desireable. They also don't have one of my favorite features of Ruby blocks, non local return. What that means is, you can't return from the outer scope from inside a block. Now, the JVM doesn't support long jumps or have stack unwind operations, which is how languages with their own VM handle it, but there are well known / understood workarounds.

def add_comment(post, comment)
  post.transaction do
    return if post.has_too_many_comments?

    post.add_comment comment
  end
end

or

def do_thing_with file_path
  File.open file_path do |f|
    return if f.empty?
    f.do_thing
  end
end

For more on why non-local return is handy, read http://yehudakatz.com/2010/02/07/the-building-blocks-of-ruby/

But, non-local return isn't appropriate for all things you might want to do with lambda-ish things.

So, my proposal is this.

Mirah should have 2 kinds of closures: Blocks and Lambdas.

Blocks look like this

foo do |bar|
  baz bar
end

They go at the end of method calls only ala Ruby. Also like Ruby, they support non local return, so

def me
  things.forEach do |t|
    return t if t.matches?
  end
end

returns the matching t from me.

I'd also like to consider supporting break / next in blocks as well, but I'm less sure of what semantics I'd like to see.

I think this is nice because then macros that take blocks and methods that take blocks will have similar behavior.

Lambdas are the other type of closure. In Ruby, they're stabby procs, or lambdas. In Mirah, we could adopt Ruby's stabby proc syntax for them which would feel pretty natural for both Rubyists and Java 8 users.

In Ruby, lambdas close over scope just like blocks, but they don't have non local return. That makes them really handy for composing small bits of behavior, like in Rails where you can do things like

validates :name, presence: true, if: -> { Date.today.tuesday? }

Or as disjoint functions

->(x,y) {
  return x * 2 if y > 4
  return x / 3 if y < 0
  return x - 12
}

Bindings

What should this snippet print? I think it should print the numbers 1 through 5. What that would mean is that the closure -> { puts i } should not share the same binding object as the closure it is in. Which I think, isn't the case right now.

list = []
5.times do |i|
  list.add -> { puts i }
end
list.each {|l| l.run}

Return semantics

Blocks have Non Local Return which means that regardless of whether each is an intrinsic or a method call, the following works the same

def method
  @list.each do |l|
    if matches_cond
      return l
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment