I've implemented a rudimentary extension on Hash
with delegation via the forwardable
module, in the following source code (public domain here)
module OptionsConstants
THIS = lambda { |a| a.itself }
end
require('forwardable')
class AssocHash
extend(Forwardable)
include(Enumerable)
def_delegators(:@table,
:[], :delete, :each, :keys, :values,
:length, :empty?,
:include?, :has_key?, :key?, :member?
)
def self.table_default(whence)
return lambda { |hash, key| raise("Found no key #{key} in #{whence.class} #{whence.object_id}") }
end
def initialize(&keytest)
@table = Hash.new(&self.class.table_default(self))
@keytest = ( keytest || OptionsConstants::THIS )
end
def add(obj, overwrite = false)
usekey=key(obj)
if @table.member?(usekey)
if (obj == @table[usekey])
return obj
elsif ! overwrite
raise("An object is already registered for key #{usekey} in #{self.class} #{self}")
end
end
@table[usekey]=obj
return obj
end
def get(key)
return @table[key]
end
def key(obj)
@keytest.call(obj)
end
end
I'm still studying the syntax and semantics of the Ruby language. I think I've read a little about how &
might be handled in method argument lists, in the documentation for the Proc
class in Ruby 2.6. I'm not sure if I'm entirely clear about how this &procarg
syntax may operate, however. Perhaps it's nothing like a pointer in C?
For implementing the AsssocHash
class, I'd like to be able to pass two lambda objects to the initialize
method, ideally with the second being optional, such that the second lambda object (or general proc) would be then used to provide a 'default' proc for the encapsulated hash @table
. It would need to be provided as a proc
to the Hash
initializer. Subsequently, the default
proc could then be provided from any calling API, in addition to the @keytest
proc used directly in AssocHash
.
While working on the API for this, locally, it seemed that there may be some limitations entailed when using a &procarg
syntax?
- Cannot use two
&procarg
in a method signature? - Cannot use any arg after a
&procarg
in a method signature? - Does not provide a conventional required parameter, in an argument list?
Presently, I may only be able to guess as to how &
might be used when passing any object/lambda/proc to another method. While it may have "Just worked" to add &
to the expression for providing a default proc for the Hash
initializer in the present implementation, in all candor I'm not certain as to why it was needed there, or how it affects what the Hash
initializer receives. This might seem tangential to the question of how to provide any two proc expressions to the top-level initializer, in this code?
I'm not certain if it's the most accurate terminology, to refer to it as a &procarg
. To my own albeit limited understanding of this aspect of Ruby syntax, the &
syntax seems to be required for the parameter that provides the value of @keytest
to the initializer for AssocHash
, such that the @keytest
can then be used later in the expression, @keytest.call
Is there some way to provide a second proc to the initializer method, such that then could be used as a proc default for the Hash
initializer?
I'll try out a few more iterations on the syntax, here. I'm afraid there may not seem to be a lot of documentation around, about this &
qualifier in Ruby method signatures. I'm sure that it's thoroughly documented in the source code, however.
Update
Perhaps &
may not be required for passing a lambda or proc to a method?
I believe that the following code may serve towards addressing the question
module TestConstants
NOKEY = lambda { |h,k| return "No key: #{k}" }
end
class Test
def mkhash(fn = TestConstants::NOKEY)
Hash.new(&fn)
end
end
subsequently, under irb:
irb(main)> t = Test.new
=> #<Test:0x0000557c6fe2e460>
irb(main)> h = t.mkhash
=> {}
irb(main)> h[:a]
=> "No key: a"
irb(main)> h = t.mkhash(lambda { |h,k| return "Not found: #{k}" })
=> {}
irb(main)> h[:b]
=> "Not found: b"
If it's possible to pass a lambda or proc to a method without denoting the parameter for it with &
, then the earlier AssocHash
code can probably be updated as to how each lambda value will be passed in to the method.
Perhaps that would be independent to how each lambda would then be stored within any instance variables and/or passed to the Hash
constructor.
At least, it might serve towards addressing a question of how to revise that single class. This &
qualifier is certainly an interesting bit of syntax, imo.
CodePudding user response:
Methods in Ruby may not take more than one block. However, you can bind any arbitrary number of blocks as procs (or lambdas) and pass them as regular arguments. You don't get the nice block sugar syntax (do...end
), but it works just fine.
In Ruby, blocks (the anonymous functions passed to methods via {|...| }
or do ... end
syntax) are one of the very few exceptions to the "everything is an object" rule. Ruby makes special allowances for the fact that it is not uncommon to want to pass a callback or anonymous function to a method. You pass that with the block syntax, then can declare that you want access to the block as a Proc by adding the &block
parameter as the last argument in your arg list, which takes your passed anonymous function and binds it to a Proc instance stored in the variable block
. This lets you call it (by calling block.call
) or pass it on as a normal object instance (as block
) or as a block (as &block
). You can also implicitly call it with yield
without binding it to a Proc.
def debug(obj = nil, &block)
p [:object, obj]
p [:block, block]
end
def pass_block_as_proc(&foo)
puts "Passing as foo"
debug(foo)
puts "Passing as &foo"
debug(&foo)
end
pass_block_as_proc {}
Output:
Passing as foo
[:object, #<Proc:0x000055ee3a724a38>]
[:block, nil]
Passing as &foo
[:object, nil]
[:block, #<Proc:0x000055ee3a724a38>]
So, if we want to pass multiple callbacks to a method, we can't rely on the anonymous block sugar. Instead, we have to pass them as bound Proc instances:
def multiple_procs(value, proc1, proc2)
proc2.call(proc1.call(value))
end
puts multiple_procs "foo",
->(s) { s.upcase },
->(s) { s s }
# => FOOFOO
This link is some additional useful reading to understand the difference between blocks and procs.