- Stabby / Lambda
- Block
- Nested Methods
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.
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 }
Can be assigned to variables to create anonymous classes/functions
eg
x = lambda(Runnable) { puts "hello" }
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
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
foo do |f|
f.bar
end
foo { puts "yay blocks" }
list_o_numbers.sum {|acc, n| acc + n }
Blocks go along with calls or macros, but can't be referred to by themselves
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
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 lambda
s. 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
}
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}
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