Create an object/Native demonstration

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) ;
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.

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*/ @safe {
       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*/ @safe {
       current = standard.dup;
   }

}

void main() {

   import std.stdio;
   auto d = ["a": 1, "b": 2].DefaultAA!(string, int);
   d.writeln;                // ["a":1, "b":2]
   d["a"] = 55; d["b"] = 66;
   d.writeln;                // ["a":55, "b":66]
   d.clear;
   d.writeln;                // ["a":1, "b":2]
   d["a"] = 55; d["b"] = 66;
   d["a"].writeln;           // 55
   d.remove("a");
   d.writeln;                // ["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)

jq

jq objects are JSON objects and can be created using JSON syntax, e.g. <lang jq>{"language": "jq"}</lang> Objects can also be created programmatically, e.g. <lang jq>{"one": 1} + {"two": 2}</lang>

jq objects, however, are really just values: they are immutable, and cannot be "deleted" any more than the number 1 can be deleted.

Mathematica / Wolfram Language

<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 2015.10-29

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($key) ?? $!hash.AT-KEY($key) !! Failure.new(q{can't store value for unknown key});
       }
       method DELETE-KEY($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 (Any))
(1 42)
can't store value for unknown key
  in block <unit> at native-demonstration.p6:17

Actually thrown at:
  in block <unit> at native-demonstration.p6:17

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

Works with: Ruby version 1.9

<lang ruby># A FencedHash acts like a Hash, but with a fence around its keys.

  1. One may change its values, but not its keys. Any attempt to insert
  2. a new key raises KeyError. One may delete a key, but this only
  3. restores its original value.
  4. FencedHash reimplements these Hash methods: #[] #[]= #clear #delete
  5. #delete_if #default #default= #each_key #each_pair #each_value
  6. #fetch #has_key? #keep_if #keys #length #values #values_at

class FencedHash

 # call-seq:
 #   FencedHash.new(hash, obj=nil)  -> fh
 #
 # Creates a FencedHash that takes its keys and original values from
 # a source _hash_.  The source _hash_ can be any object that
 # responds to each_pair.  Sets the default value for missing keys to
 # _obj_, so FencedHash#[] returns _obj_ when a key is not in fence.
 def initialize(hash, obj=nil)
   @default = obj
   @hash = {}
   hash.each_pair do |key, value|
     # @hash[key][0] = current value
     # @hash[key][1] = original value
     @hash[key] = [value, value]
   end
 end
 def initialize_clone(orig)
   # Object#clone calls here in Ruby 2.0.  If _orig_ was frozen, then
   # each array of _values_ is frozen, so make frozen clones.
   super
   copy = {}
   @hash.each_pair {|key, values| copy[key] = values.clone }
   @hash = copy
 end
 def initialize_dup(orig)
   # Object#dup calls here in Ruby 2.0.  If _orig_ was frozen, then
   # make duplicates that are not frozen.
   super
   copy = {}
   @hash.each_pair {|key, values| copy[key] = values.dup }
   @hash = copy
 end
 # Retrieves current value for _key_, like Hash#[].  If _key_ is not
 # in fence, returns default object.
 def [](key)
   values = @hash[key]
   if values
     values[0]
   else
     @default
   end
 end
 # call-seq:
 #   fh[key] = value       -> value
 #   fh.store(key, value)  -> value
 #
 # Sets _value_ for a _key_.  Returns _value.  If _key_ is not in
 # fence, raises KeyError.
 def []=(key, value)
   values = @hash[key]
   if values
     values[0] = value
   else
     raise KeyError, "fence prevents adding new key: #{key.inspect}"
   end
 end
 alias store []=
 # Resets all keys to their original values.  Returns self.
 def clear
   @hash.each_value {|values| values[0] = values[1]}
   self
 end
 # Resets _key_ to its original value.  Returns old value before
 # reset.  If _key_ is not in fence, returns +nil+.
 def delete(key)
   values = @hash[key]
   if values
     old = values[0]
     values[0] = values[1]
     old  # return old
   end    # else return nil
 end
 # call-seq:
 #   fh.delete_if {|key, value| block }  -> fh
 #   fh.delete_if                        -> enumerator
 #
 # Yields each _key_ with current _value_ to _block_.  Resets _key_
 # to its original value when block evaluates to true.
 def delete_if
   if block_given?
     @hash.each_pair do |key, values|
       yield(key, values[0]) and values[0] = values[1]
     end
     self
   else
     enum_for(:delete_if) { @hash.size }
   end
 end
 # The default value for keys not in fence.
 attr_accessor :default
 # call-seq:
 #   fh.each_key {|key| block}  -> fh
 #   fh.each_key                -> enumerator
 #
 # Yields each key in fence to the block.
 def each_key(&block)
   if block
     @hash.each_key(&block)
     self
   else
     enum_for(:each_key) { @hash.size }
   end
 end
 # call-seq:
 #   fh.each_pair {|key, value| block}  -> fh
 #   fh.each_pair                       -> enumerator
 #
 # Yields each key-value pair to the block, like Hash#each_pair.
 # This yields each [key, value] as an array of 2 elements.
 def each_pair
   if block_given?
     @hash.each_pair {|key, values| yield [key, values[0]] }
     self
   else
     enum_for(:each_pair) { @hash.size }
   end
 end
 # call-seq
 #   fh.each_value {|value| block} -> fh
 #   fh.each_value                 -> enumerator
 #
 # Yields current value of each key-value pair to the block.
 def each_value
   if block_given?
     @hash.each_value {|values| yield values[0] }
   else
     enum_for(:each_value) { @hash.size }
   end
 end
 # call-seq:
 #   fenhsh.fetch(key [,default])
 #   fenhsh.fetch(key) {|key| block }
 #
 # Fetches value for _key_.  Takes same arguments as Hash#fetch.
 def fetch(*argv)
   argc = argv.length
   unless argc.between?(1, 2)
     raise(ArgumentError,
           "wrong number of arguments (#{argc} for 1..2)")
   end
   if argc == 2 and block_given?
     warn("#{caller[0]}: warning: " +
          "block supersedes default value argument")
   end
   key, default = argv
   values = @hash[key]
   if values
     values[0]
   elsif block_given?
     yield key
   elsif argc == 2
     default
   else
     raise KeyError, "key not found: #{key.inspect}"
   end
 end
 # Freezes this FencedHash.
 def freeze
   @hash.each_value {|values| values.freeze }
   super
 end
 # Returns true if _key_ is in fence.
 def has_key?(key)
   @hash.has_key?(key)
 end
 alias include? has_key?
 alias member? has_key?
 # call-seq:
 #   fh.keep_if {|key, value| block }  -> fh
 #   fh.keep_if                        -> enumerator
 #
 # Yields each _key_ with current _value_ to _block_.  Resets _key_
 # to its original value when block evaluates to false.
 def keep_if
   if block_given?
     @hash.each_pair do |key, values|
       yield(key, values[0]) or values[0] = values[1]
     end
     self
   else
     enum_for(:keep_if) { @hash.size }
   end
 end
 # Returns array of keys in fence.
 def keys
   @hash.keys
 end
 # Returns number of key-value pairs.
 def length
   @hash.length
 end
 alias size length
 # Converts self to a regular Hash.
 def to_h
   result = Hash.new(@default)
   @hash.each_pair {|key, values| result[key] = values[0]}
   result
 end
 # Converts self to a String.
 def to_s
   "#<#{self.class}: #{to_h}>"
 end
 alias inspect to_s
 # Returns array of current values.
 def values
   @hash.each_value.map {|values| values[0]}
 end
 # Returns array of current values for keys, like Hash#values_at.
 def values_at(*keys)
   keys.map {|key| self[key]}
 end

end</lang>

Tcl

This solution uses a dict(ionary), so requires Tcl 8.5 or better. Variable traces are used to detect write or unset access to such a protected variable, restore it to the backup value at protection time, and throw an exception

<lang Tcl>proc protect _var {

   upvar 1 $_var var
   trace add variable var {write unset} [list protect0 $var]

} proc protect0 {backup name1 name2 op} {

   upvar 1 $name1 var
   trace remove variable var {write unset} [list protect 0 $backup]
   set var $backup
   trace add variable var {write unset} [list protect0 $backup]
   return -code error "$name1 is protected"

} proc trying cmd { #-- convenience function for demo

   puts "trying: $cmd"
   if [catch {uplevel 1 $cmd} msg] {puts $msg}

}</lang> Testing:

dict set dic 1 one 
dict set dic 2 two
puts dic:$dic
protect dic
trying "dict set dic 3 three"
puts dic:$dic
trying "dict unset dic 1"
trying "unset dic"
puts dic:$dic

displays on stdout:

dic:1 one 2 two
trying: dict set dic 3 three
can't set "dic": dic is protected
dic:1 one 2 two
trying: dict unset dic 1
can't set "dic": dic is protected
trying: unset dic
dic:1 one 2 two

zkl

zkl has two dictionary objects: SD, a small dictionary that is created immutable and the "regular" dictionary has has a makeReadOnly method. They both behave the same when locked down. <lang zkl">d:=SD("one",1,"two",2); d.keys; //-->L("one","two") d["one"]; //-->1 d.add("three",3); // error thrown d.pop("one") // error thrown</lang>