Create an object/Native demonstration

From Rosetta Code
Revision as of 05:35, 15 February 2011 by rosettacode>Kernigh (→‎{{header|JavaScript}}: Add Ruby {{in progress}}.)
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.

Task:

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

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

Objective:

The objective is not just create such object, but to demonstarion the language's native way of object creation. For some language, the task should show how the so-called Magic Methods work.

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)

Ruby

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

TODO: Inherit from Hash (class FencedHash < Hash). Add missing methods. Add test case.

<lang ruby>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 revert the value to
  5. the default value.

class FencedHash

 extend Enumerable, Forwardable
 #--
 # @hash: our Hash inside the fence
 # @keys: set of all keys in the fence (really another Hash)
 # @default_proc: passes self, not @hash
 #
 # When x is in @keys but not in @hash, then @hash[x] gives the
 # correct default value. Allows FencedHash#default_proc= to work.
 #
 # @keys is not an Array because Hash#include? is faster than
 # Array#include?. @keys is not a Set (require 'set') because there
 # was no Set#compare_by_identity method.
 #++
 attr_reader :default_proc
 def_delegators :@hash, :[], :compare_by_identity?
 def_delegators :@keys, :has_key?, :include?, :key?, :keys, :member?
 # Acts like Hash::[] but creates a FencedHash.
 def self.[](*args)
   fh = FencedHash.allocate
   fh.instance_eval do
     @hash = Hash[*args]
     @keys = {}
     @hash.each_key { |key| @keys[key] = true }
   end
 end
 # call-seq:
 #   FencedHash.new(obj, *keys)                   -> fh
 #   FencedHash.new(*keys) { |fh, key| block }    -> fh
 #
 # Creates a FencedHash...
 def initialize(*args, &block)
   if block_given?
     @default_proc = block
     @hash = Hash.new { |hash, key| yield self, key }
   else
     # FencedHash.new() acts like FencedHash.new(nil) because
     # if args.empty?, then args.shift returns nil.
     @hash = Hash.new(args.shift)
   end
   @keys = {}
   @hash.each_key { |key| @keys[key] = true }
 end
 # Acts like Hash#clear.
 def clear
   @hash.clear
   self
 end
 # Acts like Hash#compare_by_identity.
 def compare_by_identity
   @hash.compare_by_identity
   @keys.compare_by_identity
   self
 end

end</lang>