Create an object/Native demonstration

From Rosetta Code
Revision as of 13:35, 17 January 2014 by rosettacode>Gus.massa (Add Racket Task)
Create an object/Native demonstration is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Create a Hash/Associative Array/Dictionary-like object, initialized with some default key/value pairs using the languages native method of object creation. The object should behave like a native Hash/Associative Array/Dictionary of the language, if any, but with the following differences:

  • No new item can be added;
  • Item cannot be deleted, (but native delete method may used to reset the item's value to default) ;

If the language supports Magic Methods, then show how these work.

D

Translation of: Python

<lang d>struct DefaultAA(TK, TV) {

   TV[TK] standard, current;
   this(TV[TK] default_) /*pure nothrow*/ {
       this.standard = default_;
       this.current = default_.dup;
   }
   alias current this;
   void remove(in TK key) pure nothrow {
       current[key] = standard[key];
   }
   void clear() /*pure nothrow*/ {
       current = standard.dup;
   }

}

void main() {

   import std.stdio;
   auto d = DefaultAA!(string, int)(["a": 1, "b": 2]);
   writeln(d);                // ["a":1, "b":2]
   d["a"] = 55; d["b"] = 66;
   writeln(d);                // ["a":55, "b":66]
   d.clear();
   writeln(d);                // ["a":1, "b":2]
   d["a"] = 55; d["b"] = 66;
   writeln(d["a"]);           // 55
   d.remove("a");
   writeln(d);                // ["a":1, "b":66]

}</lang>

Output:
["a":1, "b":2]
["a":55, "b":66]
["a":1, "b":2]
55
["a":1, "b":66]

J

Given a list of keys and an associated list of values, the idiomatic way of expressing this concept in J would be:

<lang j>lookup=: values {~ keys&i.</lang>

For example:

<lang j> lookup=: 10 20 30 40 50 {~ (;:'this is a test')&i.

  lookup ;:'a test'

30 40</lang>

Notes:

1) While the result can not be modified or deleted, the name used to refer to it can be made to refer to something else, and once all references are lost it will be garbage collected.

2) In the above example, we have 5 values and 4 keys. The extra value is used when no key is found. If no extra value was provided, the "key not found" case would be an error case.

3) In J, objects are always referenced, but all data is passed by value. This means that objects can never be passed to a function -- only a reference to an object (its name) can be passed. This means that objects exist only in the way things are named, in J. So for the most part, we do not call things "objects" in J, and this task has nothing to do with what are called "objects" in J. However, this does demonstrate how things are created in J -- you write their definition, and can use them and/or assign to names or inspect them or whatever else.

JavaScript

This is a first demonstration of the task, but only implemented the functionality, not any native behavior, eg indexing. JavaScript experts may want to replace this one.

Works with: JavaScript version 1.7

<lang javascript>var keyError = new Error("Invalid Key Error (FixedKeyDict)") ;

function FixedKeyDict(obj) {

   var myDefault = new Object() ;
   var myData    = new Object() ;
   for(k in obj) {
       myDefault[k] = obj[k] ;
       myData[k]    = obj[k] ;
   }
   var gotKey = function(k) {
       for(kk in myDefault) {
           if(kk == k) return true ;
       }
       return false ;        
   } ;
   this.hasKey = gotKey ;
   var checkKey = function(k) {
       if(!gotKey(k))
           throw keyError ;
   } ;
  
   this.getItem = function(k) {
       checkKey(k) ;
       return myData[k];
   } ;
   
   this.setItem = function(k, v) {
       checkKey(k) ;
       myData[k] = v ;
   } ;
   
   this.resetItem = function(k) {
       checkKey(k) ;
       myData[k] = myDefault[k] ;      
   } ;
   
   this.delItem = this.resetItem ;
   
   this.clear   = function() {
       for(k in myDefault)
           myData[k] = myDefault[k] ;
   } ;
   
   this.iterator = function() {
       for(k in myDefault)
           yield (k);            
   } ;
   
   this.clone    = function() {
       return new FixedKeyDict(myDefault) ;
   }
   
   this.toStr = function() {
       var s = "" ;
       for(key in myData)
           s = s + key + " => " + myData[key] + ", " ;
       return "FixedKeyDict{" + s + "}" ;
   } ; 

}</lang>

Test run:

<lang javascript> const BR = "
\n"

var pl = function(s) {

   document.write(s + BR) ;

} ;

pl("

") ;

var o = { foo:101, bar:102 } ;

var h = new FixedKeyDict(o) ;
pl("Fixed Key Dict Created") ;
pl("toString   : " + h.toStr()) ;
pl("get an item: " + h.getItem("foo")) ;
pl("check a key: " + h.hasKey("boo")) ;
pl("ditto      : " + h.hasKey("bar")) ;
h.setItem("bar", 999) ;
pl("set an item: " + h.toStr()) ;
pl("Test iterator (or whatever)") ;
for(k in h.iterator())
    pl("  " + k + " => " + h.getItem(k)) ;
var g = h.clone() ;
pl("Clone a dict") ;
pl("  clone    : " + g.toStr()) ;
pl("  original : " + h.toStr()) ;
h.clear() ;
pl("clear or reset the dict") ;
pl("           : " + h.toStr()) ;
try {
    h.setItem("NoNewKey", 666 ) ;
} catch(e) {
    pl("error test : " + e.message) ;
}
</lang>

output :

<pre>
Fixed Key Dict Created
toString   : FixedKeyDict{foo => 101, bar => 102, }
get an item: 101
check a key: false
ditto      : true
set an item: FixedKeyDict{foo => 101, bar => 999, }
Test iterator (or whatever)
  foo => 101
  bar => 999
Clone a dict
  clone    : FixedKeyDict{foo => 101, bar => 102, }
  original : FixedKeyDict{foo => 101, bar => 999, }
clear or reset the dict
           : FixedKeyDict{foo => 101, bar => 102, }
error test : Invalid Key Error (FixedKeyDict)

Mathematica

<lang Mathematica>a[1] = "Do not modify after creation"; a[2] = "Native demonstration"; Protect[a];</lang> Example usage:

a[3] = 2
->Set::write: Tag a in a[1] is Protected. >>

Perl

<lang perl>package LockedHash; use parent Tie::Hash; use Carp; use strict;

sub TIEHASH { my $cls = shift; my %h = @_; bless \%h, ref $cls || $cls; }

sub STORE { my ($self, $k, $v) = @_; croak "Can't add key $k" unless exists $self->{$k}; $self->{$k} = $v; }

sub FETCH { my ($self, $k) = @_; croak "No key $k" unless exists $self->{$k}; $self->{$k}; }

sub DELETE { my ($self, $k) = @_; croak "No key $k" unless exists $self->{$k}; $self->{$k} = 0; }

sub CLEAR { } # ignored sub EXISTS { exists shift->{+shift} }

sub FIRSTKEY { my $self = shift; keys %$self; each %$self; }

sub NEXTKEY { my $self = shift; each %$self; }

sub lock_hash(\%) { my $ref = shift; tie(%$ref, __PACKAGE__, %$ref); }

1;

my %h = (a => 3, b => 4, c => 5);

  1. lock down %h

LockedHash::lock_hash(%h);

  1. show hash content and iteration

for (sort keys %h) { print "$_ => $h{$_}\n"; }

  1. try delete b

delete $h{b}; print "\nafter deleting b: b => $h{b}\n";

  1. change value of a

$h{a} = 100; print "\na => $h{a}\n";

  1. add a new key x: will die

eval { $h{x} = 1 }; if ($@) { print "Operation error: $@" }</lang>output:<lang>a => 3 b => 4 c => 5

after deleting b: b => 0

a => 100 operation error: Can't add key x at test.pl line 14

       LockedHash::STORE('LockedHash=HASH(0x8cebe14)', 'x', 1) called at test.pl line 66
       eval {...} called at test.pl line 66</lang>

Perl 6

Works with: rakudo version 2013-02-22

Here we use delegation to handle all the normal hash methods that we don't need to override to define our new class. <lang perl6>class FixedHash {

   has $.hash handles *;
   method new(*@args) { self.bless: *, hash => Hash.new: @args }
   method at_key(FixedHash:D: $key is copy) is rw { 
       $!hash.exists($key) ?? $!hash.at_key($key) !! Nil;
   }
   method delete($key) { $!hash.{$key} = Nil }

}

  1. Testing

my $fh = FixedHash.new: "a" => 1, "b" => 2; say $fh<a b>; # 1 2 $fh:delete; say $fh<a b>; # 1 Nil $fh = 42; say $fh<a b>; # 1 42 say $fh<c>; # Nil $fh<c> = 43; # error</lang>

Output:
1 2
1 Nil
1 42
Nil
Cannot assign to a non-container
  in block  at freezehash:21

Python

<lang python> from collections import UserDict import copy

class Dict(UserDict):

   
   >>> d = Dict(a=1, b=2)
   >>> d
   Dict({'a': 1, 'b': 2})
   >>> d['a'] = 55; d['b'] = 66
   >>> d
   Dict({'a': 55, 'b': 66})
   >>> d.clear()
   >>> d
   Dict({'a': 1, 'b': 2})
   >>> d['a'] = 55; d['b'] = 66
   >>> d['a']
   55
   >>> del d['a']
   >>> d
   Dict({'a': 1, 'b': 66})
   
   def __init__(self, dict=None, **kwargs):
       self.__init = True
       super().__init__(dict, **kwargs)
       self.default = copy.deepcopy(self.data)
       self.__init = False
   
   def __delitem__(self, key):
       if key in self.default:
           self.data[key] = self.default[key]
       else:
           raise NotImplementedError
   def __setitem__(self, key, item):
       if self.__init:
           super().__setitem__(key, item)
       elif key in self.data:
           self.data[key] = item
       else:
           raise KeyError
   def __repr__(self):
       return "%s(%s)" % (type(self).__name__, super().__repr__())
   
   def fromkeys(cls, iterable, value=None):
       if self.__init:
           super().fromkeys(cls, iterable, value)
       else:
           for key in iterable:
               if key in self.data:
                   self.data[key] = value
               else:
                   raise KeyError
   def clear(self):
       self.data.update(copy.deepcopy(self.default))
   def pop(self, key, default=None):
       raise NotImplementedError
   def popitem(self):
       raise NotImplementedError
   def update(self, E, **F):
       if self.__init:
           super().update(E, **F)
       else:
           haskeys = False
           try:
               keys = E.keys()
               haskeys = Ture
           except AttributeError:
               pass
           if haskeys:
               for key in keys:
                   self[key] = E[key]
           else:
               for key, val in E:
                   self[key] = val
           for key in F:
               self[key] = F[key]
   def setdefault(self, key, default=None):
       if key not in self.data:
           raise KeyError
       else:
           return super().setdefault(key, default)</lang>

Racket

Translation of: D

This task is implemented as a new fenced-hash time with an interface similar to the native hash. Also it can be used a native dict.

Implementation of functions that handle fenced-hash: <lang Racket>

(struct fenced-hash (actual original) ...)

(define (fenced-hash-ref dict

                        key 
                        [default (lambda () (error "key not found" key))]) 
 (hash-ref (fenced-hash-actual dict) key default)) 

(define (fenced-hash-set! dict key val)

 (unless (hash-has-key? (fenced-hash-actual dict)  key)
   (error "unable to add key" key))
 (hash-set! (fenced-hash-actual dict) key val)) 

(define (fenced-hash-remove! dict key) ;reset the value!

 (unless (hash-has-key? (fenced-hash-actual dict) key)
   (error "key not found" key))
 (hash-set! (fenced-hash-actual dict) 
            key
           (hash-ref (fenced-hash-original dict) key))) 

(define (fenced-hash-clear! dict) ;reset all values!

 (hash-for-each (fenced-hash-original dict) 
                (lambda (key val) (hash-set! (fenced-hash-actual dict) key val))))

(define (fenced-hash-has-key? dict key)

 (hash-has-key? (fenced-hash-actual dict) key))

(define (fenced-hash-count dict)

 (hash-count (fenced-hash-actual dict)))

(define (fenced-hash-iterate-first dict)

 (hash-iterate-first (fenced-hash-actual dict)))

(define (fenced-hash-iterate-next dict pos)

 (hash-iterate-next (fenced-hash-actual dict) pos))

(define (fenced-hash-iterate-key dict pos)

 (hash-iterate-key (fenced-hash-actual dict) pos))

(define (fenced-hash-iterate-value dict pos)

 (hash-iterate-value (fenced-hash-actual dict) pos))

(define (*fenced-hash-print dict port mode)

       ;private custom-write ;mode is ignored
    (write-string "#fenced-hash" port)
    (write (hash->list (fenced-hash-actual dict)) port))</lang>

Definition of the actual structure and a “public” creator: <lang Racket>(struct fenced-hash (actual original)

 #:extra-constructor-name *fenced-hash ;private constructor
 #:omit-define-syntaxes ;not sure this is a good idea
 #:methods gen:custom-write 
 [(define write-proc *fenced-hash-print)]
 #:methods gen:dict 
 [(define dict-ref fenced-hash-ref)
  (define dict-set! fenced-hash-set!) 
  (define dict-remove! fenced-hash-remove!)
  (define dict-has-key? fenced-hash-has-key?) ;unused in 5.6.3
  (define dict-count fenced-hash-count)
  (define dict-iterate-first fenced-hash-iterate-first)
  (define dict-iterate-next fenced-hash-iterate-next)
  (define dict-iterate-key fenced-hash-iterate-key)
  (define dict-iterate-value fenced-hash-iterate-value)])


(define (fenced-hash . args) ; public constructor

 (define original (apply hash args))
 (*fenced-hash (hash-copy original) original))</lang>

Example: Use the fenced-hash functions: <lang Racket>(define d (fenced-hash "a" 1 "b" 2))

(displayln d) (fenced-hash-set! d "a" 55) (fenced-hash-set! d "b" 66) (displayln d) (fenced-hash-clear! d) (displayln d) (fenced-hash-set! d "a" 55) (fenced-hash-set! d "b" 66) (displayln d) (fenced-hash-remove! d "a") (displayln d)</lang>

Output:
#fenced-hash(("b" . 2) ("a" . 1))
#fenced-hash(("b" . 66) ("a" . 55))
#fenced-hash(("b" . 2) ("a" . 1))
#fenced-hash(("b" . 66) ("a" . 55))
#fenced-hash(("b" . 66) ("a" . 1))

Example (continued): Use the same object as a dict. The dict-clear! method is not defined, so we must call fenced-hash-clear! instead. <lang Racket>(fenced-hash-clear! d) (displayln d) (dict-set! d "a" 55) (dict-set! d "b" 66) (displayln d) (fenced-hash-clear! d) ;dict-clear is not defined (displayln d) (dict-set! d "a" 55) (dict-set! d "b" 66) (displayln d) (dict-remove! d "a") (displayln d)</lang>

Output:
#fenced-hash(("b" . 2) ("a" . 1))
#fenced-hash(("b" . 66) ("a" . 55))
#fenced-hash(("b" . 2) ("a" . 1))
#fenced-hash(("b" . 66) ("a" . 55))
#fenced-hash(("b" . 66) ("a" . 1))

Ruby

This example is under development. It was marked thus on 15/February/2011. Please help complete the example.

TODO: Write comments for FencedHash::new, FencedHash#delete and related methods. Add more methods (merge, merge!, reject, reject!, select, select!, update). Explain why FencedHash#replace and FencedHash#shift will not exist.

<lang ruby># fencedhash.rb require 'forwardable'

  1. A FencedHash acts like a Hash, but with a fence around its keys.
  2. After the creation of a FencedHash, one cannot add nor remove keys.
  3. Any attempt to insert a new key will raise KeyError. Any attempt to
  4. delete a key-value pair will keep the key but will reset the value to
  5. the default value.

class FencedHash < Object

 extend Forwardable
 include Enumerable
 #--
 # @hash: our Hash inside the fence
 # @default_proc: passes self, not @hash
 #++
 def_delegators(:@hash, :[], :assoc,
                :compare_by_identity, :compare_by_identity?,
                :default, :empty?, :fetch, :flatten,
                :has_key?, :has_value?, :hash, :include?,
                :key, :key?, :keys, :length, :member?,
                :rassoc, :size, :to_a,
                :values, :values_at, :value?)
 attr_reader :default_proc
 # Acts like Hash::[] but creates a FencedHash.
 def self.[](*args)
   allocate.instance_eval do
     @hash = Hash[*args]
     self
   end
 end
 # call-seq:
 #   FencedHash.new(obj=nil [,keys])               -> fh
 #   FencedHash.new([keys]) { |fh, key| block }    -> fh
 #
 # Creates a FencedHash.....
 def initialize(*args, &block)
   n = args.length
   if block_given?
     raise ArgumentError, "wrong number of arguments" if n > 1
     @default_proc = block
     @hash = Hash.new { |hash, key| block[self, key] }
     if n > 0
       args[0].each { |key| @hash[key] = nil }
       clear
     end
   else
     raise ArgumentError, "wrong number of arguments" if n > 2
     default = if n > 0 then n[0] else nil end
     @hash = Hash.new(default)
     if n > 1
       args[1].each { |key| @hash[key] = default }
     end
   end
 end
 def initialize_copy(orig)
   super
   @hash = @hash.dup
 end
 # Clears all values. For each key-value pair, this retains the key
 # but resets the value to default.
 #--
 # The line "@hash = @hash" checks that _self_ is not frozen, because
 # Object#freeze only freezes _self_ and not @hash.
 #++
 def clear
   @hash = @hash
   @hash.each_key { |key| delete key }
   self
 end
 # .....
 def default=(obj)
   @default_proc = nil
   @hash.default = obj
 end
 # .....
 def default_proc=(proc_obj)
   # Convert _proc_obj_ to a block parameter.
   proc_obj = proc &proc_obj
   @hash.default_proc = proc { |hash, key| proc_obj[self, key] }
   @default_proc = proc_obj
 end
 # Deletes the value of the key-value pair for _key_.
 #
 # If _key_ is in the fence.....
 def delete(key)
   @hash = @hash
   begin
     original_value = @hash.fetch(key)
   rescue IndexError
     # _key_ is not in the fence.
     if block_given?
       yield key
     else
       nil
     end
   else
     # _key_ is in the fence.
     if @default_proc
       @default_proc[self, key]
     else
       @hash[key] = @hash.default
     end
     original_value
   end
 end
 # .....
 def delete_if
   return enum_for(:delete_if) unless block_given?
   @hash = @hash
   @hash.each { |key, value| delete key if yield key, value }
   self
 end
 # Yields each key-value pair to the block, or returns an enumerator.
 # Acts like Hash#each.
 def each &block  # :yields: key, value
   return enum_for(:each) unless block
   @hash.each &block
 end
 alias each_pair each
 # Yields each key to the block, or returns an enumerator.
 # Acts like Hash#each_key.
 def each_key &block  # :yields: key
   return enum_for(:each_key) unless block
   @hash.each_key &block
 end
 # Yields each value to the block, or returns an enumerator.
 # Acts like Hash#each_value.
 def each_value &block  # :yields: value
   return enum_for(:each_value) unless block
   @hash.each_value &block
 end
 # Returns true if _other_ is a FencedHash and has the same key-value
 # pairs as _self_. Acts like Hash#eql?.
 #--
 # Consistent with FencedHash#hash because it delegates to @hash.hash.
 #++
 def eql?(other)
   FencedHash === other and
     @hash.eql?(other.instance_eval { @hash })
 end
 # Returns true if _other_ is a FencedHash and if the key-value pairs
 # of _self_ equal those of _other_. Acts like Hash#==.
 def ==(other)
   FencedHash === other and
     @hash == (other.instance_eval { @hash })
 end
 # .....
 def keep_if
   return enum_for(:keep_if) unless block_given?
   @hash = @hash
   @hash.each { |key, value| delete key unless yield key, value }
   self
 end
 # Stores a _value_ for a _key_. This only works if _key_ is in the
 # fence; FencedHash prevents the insertion of new keys. If _key_ is
 # not in the fence, then this method raises KeyError.
 def store(key, value)
   @hash = @hash
   if @hash.has_key? key
     @hash.store(key, value)
   else
     c = if defined? KeyError then KeyError else IndexError end
     raise c, "fence prevents new key: #{key}"
   end
 end
 alias []= store
 # Converts _self_ to a regular Hash. Returns a new Hash that has the
 # same key-value pairs as _self_.
 def to_hash
   @hash.dup
 end
 # Converts _self_ to a String.
 def to_s
   "#<#{self.class}: #{@hash.inspect}>"
 end
 alias inspect to_s

end</lang>

<lang ruby># fh-test.rb require 'fencedhash' require 'test/unit'

class TestFencedHash < Test::Unit::TestCase

 if RUBY_VERSION >= "1.9"
   KeyEx = KeyError
   FrozenEx = RuntimeError
 else
   KeyEx = IndexError
   FrozenEx = TypeError
 end
 def setup
   @fh = FencedHash[:q => 11, :w => 22, :e => 33,
                    :r => 44, :t => 55, :y => 66]
 end
 def test_bracket_operator
   assert_equal 11, @fh[:q]
   assert_equal 22, @fh[:w]
   assert_equal 33, @fh[:e]
   assert_equal 44, @fh[:r]
   assert_equal 55, @fh[:t]
   assert_equal 66, @fh[:y]
   assert_nil @fh[:u]
 end
 def test_delete
   assert_equal 44, (@fh.delete :r)
   assert_nil @fh.fetch(:r)
   assert_nil @fh.delete(:r)
   assert_nil @fh.delete(:u)
   @fh[:r] = "replacement"
   assert_equal "replacement", (@fh.delete :r)
 end
 def test_delete_if
   a = @fh.delete_if { |key, value| key == :t || value == 66 }
   assert_same @fh, a
   assert_equal 2, @fh.values.grep(nil).length
   @fh[:y] = "why?"
   @fh[:t] = "tea!"
   assert_equal 0, @fh.values.grep(nil).length
 end
 def test_default
   fruit = FencedHash.new(0, [:apple, :banana, :cranberry])
   assert_equal [0, 0, 0], fruit.values
   fruit[:apple] += 1
   fruit[:banana] += 5
   fruit[:cranberry] *= 5
   assert_equal 1, fruit[:apple]
   assert_equal 5, fruit[:banana]
   assert_equal 0, fruit[:cranberry]
   assert_equal 0, fruit.default
 end
 def test_default_assign
   assert_nil @fh.default
   @fh.delete :w
   @fh.default = -1
   assert_equal -1, @fh.default
   @fh.delete :e
   assert_nil @fh[:w]
   assert_equal -1, @fh[:e]
 end
 def test_default_proc
   count = 0
   fruit = FencedHash.new([:apple, :banana, :cranberry]) do |h, k|
     if h.key? k then h[k] = [] else count += 1 end
   end
   fruit[:apple].push :red
   fruit[:banana].concat [:green, :yellow]
   fruit[:cranberry].push :red
   assert_equal 1, fruit[:orange]
   assert_equal [:red], fruit[:apple]
   assert_equal [:green, :yellow], fruit[:banana]
   assert_equal [:red], fruit.delete(:cranberry)
   assert_equal 2, fruit[:orange]
   assert_equal [], fruit[:cranberry]
   assert_nil fruit.delete(:orange)
   assert_equal 3, fruit[:orange]
   assert_equal [], fruit.default_proc[FencedHash[1 => 2], 1]
 end
 def test_each
   count = 0
   @fh.each do |key, value|
     assert_kind_of Symbol, key
     assert_kind_of Integer, value
     assert_equal true, (@fh.has_key? key)
     assert_equal true, (@fh.has_value? value)
     count += 1
   end
   assert_equal 6, count
 end
 def test_eql?
   other = FencedHash[:r, 44, :t, 55, :y, 66,
                      :q, 11, :w, 22, :e, 33]
   float = FencedHash[:y, 66.0, :t, 55.0, :r, 44.0,
                      :e, 33.0, :w, 22.0, :q, 11.0]
   tt = [true, true]
   ff = [false, false]
   if RUBY_VERSION >= "1.9"
     assert_equal tt, [(@fh.eql? other), (other.eql? @fh)]
     assert_equal ff, [(@fh.eql? float), (float.eql? @fh)]
     assert_equal ff, [(other.eql? float), (float.eql? other)]
   end
   assert_equal tt, [@fh == other, other == @fh]
   assert_equal tt, [@fh == float, float == @fh]
   assert_equal tt, [other == float, float == other]
   h = @fh.to_hash
   if RUBY_VERSION >= "1.9"
     assert_equal ff, [(@fh.eql? h), (h.eql? @fh)]
   end
   assert_equal ff, [@fh == h, h == @fh]
 end
 def test_fetch
   assert_equal 11, @fh.fetch(:q)
   assert_equal 22, @fh.fetch(:w)
   assert_equal 33, @fh.fetch(:e)
   assert_equal 44, @fh.fetch(:r)
   assert_equal 55, @fh.fetch(:t)
   assert_equal 66, @fh.fetch(:y)
   assert_raises(KeyEx) { @fh.fetch :u }
 end
 def test_freeze
   assert_equal false, @fh.frozen?
   @fh.freeze
   2.times do
     assert_equal true, @fh.frozen?
     assert_raises(FrozenEx) { @fh.clear }
     assert_raises(FrozenEx) { @fh.delete :q }
     assert_raises(FrozenEx) { @fh.delete_if { true } }
     assert_raises(FrozenEx) { @fh.keep_if { false } }
     assert_raises(FrozenEx) { @fh.store :w, "different" }
     assert_raises(FrozenEx) { @fh[:w] = "different" }
     # Repeat the tests with a clone. The clone must be frozen.
     @fh = @fh.clone
   end
   # A duplicate is not frozen.
   @fh = @fh.dup
   assert_equal false, @fh.frozen?
   @fh[:w] = "different"
   assert_equal "different", @fh[:w]
 end
 def test_has_key
   2.times do |t|
     assert_equal true, (@fh.has_key? :y)
     assert_equal true, (@fh.include? :y)
     assert_equal true, (@fh.key? :y)
     assert_equal true, (@fh.member? :y)
     assert_equal false, (@fh.has_key? :u)
     assert_equal false, (@fh.include? :u)
     assert_equal false, (@fh.key? :u)
     assert_equal false, (@fh.member? :u)
     # Repeat the tests.
     # The fence must prevent any changes to the keys.
     @fh.delete :y
     (@fh[:u] = "value") rescue "ok"
   end
 end
 def test_has_value
   assert_equal true, (@fh.has_value? 22)
   assert_equal true, (@fh.value? 22)
   assert_equal false, (@fh.has_value? 4444)
   assert_equal false, (@fh.value? 4444)
 end
 def test_inject
   # To get an :inject method, FencedHash should mix in Enumerable.
   assert_kind_of Enumerable, @fh
   assert_equal 231, @fh.inject(0) { |sum, kv| sum + kv[1] }
 end
 def test_keep_if
   a = @fh.keep_if { |key, value| key == :t || value == 66 }
   assert_same @fh, a
   assert_equal 4, @fh.values.grep(nil).length
   @fh.delete :y
   @fh.delete :t
   assert_equal 6, @fh.values.grep(nil).length
 end
 def test_keys
   assert_equal([:e, :q, :r, :t, :w, :y],
                @fh.keys.sort_by { |o| o.to_s })
 end
 def test_length
   assert_equal 6, @fh.length
   assert_equal 6, @fh.size
 end
 def test_store
   assert_raises(KeyEx) { @fh[:a] = 111 }
   assert_equal 222, (@fh[:e] = 222)
   assert_equal 222, (@fh.fetch :e)
   assert_equal 333, @fh.store(:e, 333)
   assert_equal 333, @fh[:e]
 end
 def test_values
   assert_equal [11, 22, 33, 44, 55, 66], @fh.values.sort!
 end
 if RUBY_VERSION >= "1.8.7"
   def test_delete_if_enum
     a = @fh.delete_if.with_index { |kv, i| i >= 2 }
     assert_same @fh, a
     assert_equal 4, @fh.values.grep(nil).length
   end
   def test_keep_if_enum
     a = @fh.keep_if.with_index { |kv, i| i >= 2 }
     assert_same @fh, a
     assert_equal 2, @fh.values.grep(nil).length
   end
 end
 if RUBY_VERSION >= "1.9"
   def test_class_bracket_operator
     from_pairs = FencedHash[10, "ten", 20, "twenty", 30, "thirty"]
     from_alist = FencedHash[ [ [10, "ten"], [20, "twenty"], [30, "thirty"] ] ]
     from_hash = FencedHash[10 => "ten", 20 => "twenty", 30 => "thirty"]
     from_fhash = FencedHash[from_pairs]
     [from_pairs, from_alist, from_hash, from_fhash, from_pairs
     ].each_cons(2) do |a, b|
       assert_equal a, b
       assert_not_same a, b
     end
   end
   def test_default_proc_assign
     assert_nil @fh.default_proc
     p = @fh.default_proc = proc { |h, k| h[k] = :deleted }
     assert_same p, @fh.default_proc
     assert_equal 11, @fh.delete(:q)
     assert_equal :deleted, @fh[:q]
     assert_raises(KeyEx) { @fh[:u] }
     @fh.default = :value
     assert_nil @fh.default_proc
     @fh.default_proc = p
     assert_nil @fh.default
   end
   def test_each_rewind
     class << @fh
       attr_reader :test_rewind
       def rewind
         @test_rewind = "correct"
       end
     end
     assert_nil @fh.test_rewind
     # @fh.each.rewind must call @fh.rewind. If @fh forwards :each
     # to another object then this test fails.
     @fh.each.rewind
     assert_equal "correct", @fh.test_rewind
   end
   def test_insertion_order
     assert_equal [:q, :w, :e, :r, :t, :y], @fh.keys
     assert_equal [11, 22, 33, 44, 55, 66], @fh.values
   end
   def test_key
     assert_equal :q, @fh.key(11)
     assert_equal :w, @fh.key(22)
     assert_equal :e, @fh.key(33)
     assert_equal :r, @fh.key(44)
     assert_equal :t, @fh.key(55)
     assert_equal :y, @fh.key(66)
     assert_nil @fh.key(77)
   end
 end

end</lang>