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
&procargin a method signature? - Cannot use any arg after a
&procargin 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.