Project

General

Profile

Actions

Feature #4102

closed

Proposal for 'let'. A new approach using block-defaults in 1.9

Added by banister (john mair) about 12 years ago. Updated over 10 years ago.

Status:
Rejected
Priority:
Normal
Target version:
-
[ruby-core:33483]

Description

=begin
This is a very simple function, it would be implemented as follows:

 module Kernel
   private
   def let() yield end
 end

First of all, do not dismiss this functionality out of hand because of
its simplicity.

Even though it is just a 'yield', when it is combined with Ruby 1.9's
block defaults and new block-variable scoping rules it is actually quite
powerful and it behaves exactly like a let* in lisp.

Some advantages of this functionality are:
(1) Gives you precise control over the scope of your variables.

I note that after the publication of "Metaprogramming in Ruby" by Paolo
Perrotta the following idiom has started to appear:

 proc do
   ..my code..
 end.call

It is used exactly as the proposed 'let' would be used, but is
syntactically much uglier.

Yes, i know an alternative is to just make shorter and smaller methods.
But is the ability to control and restrict scope ever a bad thing?

(2) Testing and teaching about blocks.

As the proposed 'let' simply yields to a block it can be used to
illustrate block behaviour and block concepts to a new Ruby programmer.
It also may be useful to an experienced programmer when trying out new
ideas.

Here are some example uses of the proposed 'let':

Example 1: Carve out a temporary scope, make 'x' local to that scope

 x = :outer
 let { |x| x = :inner } #=> :inner
 x #=> :outer

Example 2: Here we use Ruby 1.9's block-defaults to make 'y' block-local
and give it a value:

 let { |y=10| y } #=> 10

Example 3: Make 'x' and 'y' block-local and have 'y' value depend on 'x'
(equivalent to let* in lisp)

 let { |x=10, y=(2*x)| [x, y] } #=> [10, 20]

In summary, I think this proposal should succeed for the following
reasons:
(1) It is an exceptionally simple implementation.
(2) More control over scope is never a bad thing.
(3) I have seen people re-implementing this functionality themselves
using: proc { ..code.. }.call
(4) It is very useful for teaching and testing block behaviour.

Thanks,

John
=end

Actions #1

Updated by now (Nikolai Weibull) about 12 years ago

=begin
On Tue, Nov 30, 2010 at 04:47, john mair wrote:

I note that after the publication of "Metaprogramming in Ruby" by Paolo
Perrotta the following idiom has started to appear:

   proc do
     ..my code..
   end.call

Seriously? Where?

Yes, i know an alternative is to just make shorter and smaller methods.

And it’s a wonderful solution at that.

=end

Actions #2

Updated by zenspider (Ryan Davis) about 12 years ago

=begin

On Nov 29, 2010, at 19:47 , john mair wrote:

This is a very simple function, it would be implemented as follows:

module Kernel
private
def let() yield end
end

First of all, do not dismiss this functionality out of hand because of
its simplicity.

Personally, I like it... I think it is a very illustrative tool with a lot of utility (mostly for teaching, but that's still very useful).

But I think you should release it as a gem first and let it make the rounds.

And hey... if you write it, rails will find a way to use it! DBT :D

That's how the tap method came to be in core...

=end

Actions #3

Updated by Eregon (Benoit Daloze) about 12 years ago

=begin
On 30 November 2010 04:47, john mair wrote:

Here are some example uses of the proposed 'let':

None of this example does actually "speak" to me.

Could you maybe elaborate the usefulness and show a "real" example
(i.e. how you would use it) ?

=end

Updated by wardrop (Tom Wardrop) over 11 years ago

=begin
Here's an example I just encountered where #let (a self executing proc) would have been useful. Here's a method I've just defined in a project I'm working on, including how that same method would look with #let.

http://pastie.org/1827489

The difference is small (does away with the #call at the end), but it results in cleaner and clearer code. It can be easy to miss the #call method at the end of the proc, as it's just not something you're use to seeing.

I find the main use case for #let is where you want to do something mid-expression. In this example, I want to define a method on a new object as part of an ||= assignment.
=end

Updated by judofyr (Magnus Holm) over 11 years ago

=begin
You can use begin/end-blocks or #tap:

 # Begin
 def fnames
   @fnames ||= begin
     hash = Hash.new { |h,k| k = k.to_s; h[k] = generate_fname(k) }
     def hash.[](key)
       super(key.to_s)
     end
     def hash.[]=(key, value)
       super(key.to_s, value)
     end
     hash
   end
 end

 # Tap
 def fnames
   @fnames ||= Hash.new { |h,k| k = k.to_s; h[k] = generate_fname(k)

}.tap do |hash|
def hash.
super(key.to_s)
end
def hash.[]=(key, value)
super(key.to_s, value)
end
end
end

// Magnus Holm

On Sun, Apr 24, 2011 at 08:07, Tom Wardrop wrote:

Issue #4102 has been updated by Tom Wardrop.

Here's an example I just encountered where #let (a self executing proc)
would have been useful. Here's a method I've just defined in a project I'm
working on, including how that same method would look with #let.

http://pastie.org/1827489

The difference is small (does away with the #call at the end), but it
results in cleaner and clearer code. It can be easy to miss the #call method
at the end of the proc, as it's just not something you're use to seeing.

I find the main use case for #let is where you want to do something
mid-expression. In this example, I want to define a method on a new object
as part of an ||= assignment.

Feature #4102: Proposal for 'let'. A new approach using block-defaults in
1.9
http://redmine.ruby-lang.org/issues/4102

Author: john mair
Status: Open
Priority: Normal
Assignee:
Category:
Target version:

This is a very simple function, it would be implemented as follows:

module Kernel
  private
  def let() yield end
end

First of all, do not dismiss this functionality out of hand because of
its simplicity.

Even though it is just a 'yield', when it is combined with Ruby 1.9's
block defaults and new block-variable scoping rules it is actually quite
powerful and it behaves exactly like a let* in lisp.

Some advantages of this functionality are:
(1) Gives you precise control over the scope of your variables.

I note that after the publication of "Metaprogramming in Ruby" by Paolo
Perrotta the following idiom has started to appear:

proc do
  ..my code..
end.call

It is used exactly as the proposed 'let' would be used, but is
syntactically much uglier.

Yes, i know an alternative is to just make shorter and smaller methods.
But is the ability to control and restrict scope ever a bad thing?

(2) Testing and teaching about blocks.

As the proposed 'let' simply yields to a block it can be used to
illustrate block behaviour and block concepts to a new Ruby programmer.
It also may be useful to an experienced programmer when trying out new
ideas.

Here are some example uses of the proposed 'let':

Example 1: Carve out a temporary scope, make 'x' local to that scope

x = :outer
let { |x| x = :inner } #=> :inner
x #=> :outer

Example 2: Here we use Ruby 1.9's block-defaults to make 'y' block-local
and give it a value:

let { |y=10| y } #=> 10

Example 3: Make 'x' and 'y' block-local and have 'y' value depend on 'x'
(equivalent to let* in lisp)

let { |x=10, y=(2*x)| [x, y] } #=> [10, 20]

In summary, I think this proposal should succeed for the following
reasons:
(1) It is an exceptionally simple implementation.
(2) More control over scope is never a bad thing.
(3) I have seen people re-implementing this functionality themselves
using: proc { ..code.. }.call
(4) It is very useful for teaching and testing block behaviour.

Thanks,

John

--
http://redmine.ruby-lang.org

=end

Updated by wardrop (Tom Wardrop) over 11 years ago

Thanks for the tips Magnus, they're very handy. I forget that begin ... end can be used just about anywhere to encapsulate multiple expressions that lead to a single result. The #tap method I simply had no idea about.

Cheers

Updated by nahi (Hiroshi Nakamura) over 10 years ago

  • Description updated (diff)
  • Category set to core
  • Assignee set to matz (Yukihiro Matsumoto)
Actions #8

Updated by shyouhei (Shyouhei Urabe) over 10 years ago

  • Status changed from Open to Assigned

Updated by trans (Thomas Sawyer) over 10 years ago

It would conflict with RSpec's definition of #let.

Other then as a teaching tool for block scope what are the use cases? This seems to have very limited use. I find defining a new method is better way to get clean scope.

Updated by matz (Yukihiro Matsumoto) over 10 years ago

  • Status changed from Assigned to Rejected

The definition of let would be: "def let() yield; end"; I don't think it's worth to add.

Matz.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0