Commits

Taku Miyakawa committed 5d09332

Refines functions of maps (closing issue #482)

Comments (0)

Files changed (2)

src/main/kink/MAP.kn

 use('JAVA.null')
 use('META.meta')
 
+### .map([Key Value ...]) => /map
+###
+### SUMMARY
+###     Returns a new concurrent hash map,
+###     associating each key from (N * 2)th argument
+###     with each value from (N * 2 + 1)th argument, where N is a zero-based number.
+###
+### PARAM Key
+###     A key of an entry.
+###
+### PARAM Value
+###     A value of an entry.
+###
+### USAGE
+###     :Map = map('name' 'John Doe' 'age' 42)
+###     print_line(Map.get('name'))  # => John Doe
+###     print_line(Map.get('age'))   # => 42
 :map = { (* :Args)
     :Java_map = ConcurrentHashMap.new
     Args.unconcat(2).loop { ([:Key :Value])
     Map_proto.child._init(Java_map)
 }
 
+# Throws a key-not-found exception.
 :_key_not_found = { (:Key)
     throw(expand('Key not found: #{Key.show}'))
 }
 
+### /map
+###
+### SUMMARY
+###     Concurrent map.
 :Map_proto = value(
+
+    # Initializes a map.
     '_init' { > :Self (:Java_map)
         Self.set_vars(
             'Java_map' Java_map
             'Showing?_tl' THREAD_LOCAL_STORAGE.new
         )
     }
+
+    ### .any? => /bool
+    ###
+    ### SUMMARY
+    ###     Returns true if the map has at least one entry.
+    ###
+    ### RETURNS
+    ###     true if the map has at least one entry;
+    ###     false if the map has no entry.
     'any?' { > :Map ()
         ! Map.Java_map.isEmpty
     }
+
+    ### .empty? => /bool
+    ###
+    ### SUMMARY
+    ###     Returns true if the map has no entry.
+    ###
+    ### RETURNS
+    ###     true if the map has no entry;
+    ###     false if the map has at least one entry.
     'empty?' { > :Map ()
         Map.Java_map.isEmpty
     }
-    'count' { > :Map () Map.Java_map.size }
-    'get' { > :Map (:Key :_no_element = {_key_not_found(Key)}  :_element_found = {\0} )
+
+    ### .count => /int_num
+    ###
+    ### SUMMARY
+    ###     Returns the number of the entries of the map.
+    'count' { > :Map ()
+        Map.Java_map.size
+    }
+
+    ### .get(Key [Default_value_fun/fun])
+    ###
+    ### SUMMARY
+    ###     Returns the value associated with the key.
+    ###     If the map has no entry associated with the key,
+    ###     and the default value function is given,
+    ###     it tail-calls the function and returns the result.
+    ###     If the map has no entry associated with the key,
+    ###     and the default value function is not given,
+    ###     an it throws an exception.
+    ###
+    ### PARAM Key
+    ###     A key.
+    ###
+    ### PARAM Default_value_fun/fun(-> /value)
+    ###     A function to provide the default value.
+    ###
+    ### RETURNS
+    ###     The value associated with the key,
+    ###     or the default value provided by the default value function.
+    ###
+    ### THROWS
+    ###     An exception if no entry is found and the default value function is not given.
+    'get' { > :Map (:Key :_default_value = {_key_not_found(Key)})
         Map.Java_map.get(meta(Key)).switch(
-            (null) { _no_element }
-            :Value { _element_found( Value.unmeta ) }
+            (null) { _default_value }
+            :Value_meta { Value_meta.unmeta }
         )
     }
+
+    ### .get_if_any(Key) => /list
+    ###
+    ### SUMMARY
+    ###     Returns a singleton list containing the value associated with the key;
+    ###     or an empty list if no value is associated with the key.
+    ###
+    ### PARAM Key
+    ###     A key.
+    ###
+    ### RETURNS
+    ###     A singleton list containing the value associated with the key;
+    ###     or an empty list if no value is associated with the key.
+    'get_if_any' { > :Map (:Key)
+        Map.Java_map.get(meta(Key)).switch(
+            (null) { [] }
+            :Value_meta { [Value_meta.unmeta] }
+        )
+    }
+
+    ### .has_key?(Key)
+    ###
+    ### SUMMARY
+    ###     Returns true if the map has an entry associated with the key.
+    ###
+    ### PARAM Key
+    ###     A key.
+    ###
+    ### RETURNS
+    ###     true if the map has an entry associated with the key;
+    ###     false otherwise.
     'has_key?' { > :Map (:Key)
         Map.Java_map.containsKey(meta(Key))
     }
+
+    ### .has?(Value)
+    ###
+    ### SUMMARY
+    ###     Returns true if the map has the value.
+    ###     Note that it is an O(N) operation, where N is the size of the map.
+    ###
+    ### PARAM Value
+    ###     A value.
+    ###
+    ### RETURNS
+    ###     true if the map has the value;
+    ###     false otherwise.
     'has?' { > :Map (:Value)
         Map.Java_map.values.contains(meta(Value))
     }
+
+    ### .set(Key Value) => receiver/map
+    ###
+    ### SUMMARY
+    ###     Associates the value with the key.
+    ###
+    ### PARAM Key
+    ###     A key.
+    ###
+    ### PARAM Value
+    ###     A value.
+    ###
+    ### RETURNS
+    ###     The map itself.
     'set' { > :Map (:Key :Value)
         Map.Java_map.put(meta(Key) meta(Value))
         Map
     }
-    'set_if_absent' { > :Map (:Key :Value :_not_set = {Map} :_set = {Map})
+
+    ### .set_if_absent(Key Value) => /list
+    ###
+    ### SUMMARY
+    ###     Associates the value with the key if no value is associated with the key.
+    ###
+    ### PARAM Key
+    ###     The key.
+    ###
+    ### PARAM Value
+    ###     The value.
+    ###
+    ### RETURNS
+    ###     [] if the value is associated with the key;
+    ###     [Existing_value] if Existing_value is already associated with the key.
+    'set_if_absent' { > :Map(:Key :Value)
         Map.Java_map.putIfAbsent(meta(Key) meta(Value)).switch(
-            (null) { _set }
-            :Old { _not_set(Old.unmeta) }
+            (null) { [] }
+            :Existing_value_meta { [Existing_value_meta.unmeta] }
         )
     }
-    'set_if_present' { > :Map (:Key :Value :_not_set = {Map} :_set = {Map})
-        Map.Java_map.replace(meta(Key) meta(Value)).switch(
-            (null) { _not_set }
-            :Old { _set(Old.unmeta) }
+
+    ### .set_if_any(Key New_value) => /list
+    ###
+    ### SUMMARY
+    ###     Associates the new value to the key if some value is already associated with the key.
+    ###
+    ### PARAM Key
+    ###     The key.
+    ###
+    ### PARAM New_value
+    ###     The new value.
+    ###
+    ### RETURNS
+    ###     [] if the value is not associated with the key;
+    ###     [Old_value] if the new value is not associated with the key,
+    ###     where Old_value is a value which has been associated with the key before the operation.
+    'set_if_any' { > :Map (:Key :New_value)
+        Map.Java_map.replace(meta(Key) meta(New_value)).switch(
+            (null) { [] }
+            :Old_value_meta { [Old_value_meta.unmeta] }
         )
     }
-    'set_if_equal' { > :Map (:Key :Old :New :_not_set = {Map} :_set = {Map})
-        Map.Java_map.replace(meta(Key) meta(Old) meta(New)).then($_set $_not_set)
+
+    ### .set_if_equal(Key Old_value New_value) => /bool
+    ###
+    ### SUMMARY
+    ###     Associates the new value to the key, if the old value is associated with the key.
+    ###
+    ### PARAM Key
+    ###     A key.
+    ###
+    ### PARAM Old_value
+    ###     An old value.
+    ###
+    ### PARAM New_value
+    ###     A new value.
+    ###
+    ### RETURNS
+    ###     true if the new value is associated with the key;
+    ###     false otherwise.
+    'set_if_equal' { > :Map (:Key :Old_value :New_value)
+        Map.Java_map.replace(meta(Key) meta(Old_value) meta(New_value))
     }
-    'pop' { > :Map (:Key :_not_popped = {_key_not_found(Key)}  :_popped = {\0})
+
+    ### .pop(Key [Default_value_fun/fun(-> /value)])
+    ###
+    ### SUMMARY
+    ###     Removes and returns a value associated with the key.
+    ###     If the map has no entry associated with the key,
+    ###     and the default value function is given,
+    ###     it calls the function and retruns the result.
+    ###     If the map has no entry associated with the key,
+    ###     and the default value function is not given,
+    ###     it throws an exception.
+    ###
+    ### PARAM Key
+    ###     A key.
+    ###
+    ### PARAM Default_value_fun/fun(-> /value)
+    ###     A function to provide the default value.
+    ###
+    ### RETURNS
+    ###     The value associated with the key,
+    ###     or the default value provided by the default value function.
+    ###
+    ### THROWS
+    ###     An exception if no entry is found and the default value function is not given.
+    'pop' { > :Map (:Key :_default_value = {_key_not_found(Key)})
         Map.Java_map.remove(meta(Key)).switch(
-            (null) { _not_popped }
-            :Value { _popped(Value.unmeta) }
+            (null) { _default_value }
+            :Value { Value.unmeta }
         )
     }
-    'append' { > :Map (:Another :_select = {\2})
-        Another.pairs.loop { ([:Key :Value])
-            :Put = Map.Java_map.containsKey(meta(Key)).then {
-                _select(Key Map.Java_map.get(meta(Key)).unmeta Value)
-            } {
-                Value
-            }
-            Map.Java_map.put(meta(Key) meta(Put))
-        }
-        Map
+
+    ### .pop_if_any(Key) => /list
+    ###
+    ### SUMMARY
+    ###     Removes the entry associated with the key,
+    ###     and returns a singleton list containing the value of the entry.
+    ###     If no entry is associated with the key, returns an empty list.
+    ###
+    ### PARAM Key
+    ###     A key.
+    ###
+    ### RETURNS
+    ###     A singleton list containing the value associated with the key;
+    ###     or an empty list if no value is associated with the key.
+    'pop_if_any' { > :Map (:Key)
+        Map.Java_map.remove(meta(Key)).switch(
+            (null) { [] }
+            :Value_meta { [Value_meta.unmeta] }
+        )
     }
-    'op_add' { > :Map (:Another)
-        Map.clone.tee {
-            \0.append(Another)
-        }
-    }
+
+    ### .clear => receiver/map
+    ###
+    ### SUMMARY
+    ###     Removes all the entries from the map
+    ###
+    ### RETURNS
+    ###     The map itself.
     'clear' { > :Map ()
         Map.Java_map.clear
         Map
     }
+
+    ### .clone => /map
+    ###
+    ### SUMMARY
+    ###     Returns a new map which is equal to the map.
     'clone' { > :Map ()
-        :Newmap = map
-        Map.pairs.loop { ([:Key :Value])
-            Newmap.set(Key Value)
+        :Clone_map = map
+        Map.pair_seq.loop { ([:Key :Value])
+            Clone_map.set(Key Value)
         }
-        Newmap
+        Clone_map
     }
+
+    ### .key_seq => /seq
+    ###
+    ### SUMMARY
+    ###     Returns a sequence of the keys.
     'key_seq' { > :Map ()
         Map.Java_map.keySet.seq.map { \0.unmeta }
     }
+
+    ### .keys => /seq
+    ###
+    ### SUMMARY
+    ###     Returns a list of the keys.
     'keys' { > :Map ()
         Map.key_seq.list
     }
+
+    ### .seq => /seq
+    ###
+    ### SUMMARY
+    ###     Returns a sequence of the values.
     'seq' { > :Map ()
         Map.Java_map.values.seq.map { \0.unmeta }
     }
+
+    ### .list => /list
+    ###
+    ### SUMMARY
+    ###     Returns a list of the values.
     'list' { > :Map ()
         Map.seq.list
     }
+
+    ### .pair_seq => /seq
+    ###
+    ### SUMMARY
+    ###     Returns a sequence of the pairs of the entries.
     'pair_seq' { > :Map ()
         Map.Java_map.entrySet.seq.map { [\0.getKey.unmeta \0.getValue.unmeta] }
     }
+
+    ### .pairs => /list
+    ###
+    ### SUMMARY
+    ###     Returns a list of the pairs of the entries.
     'pairs' { > :Map ()
         Map.Java_map.entrySet.seq.map { [\0.getKey.unmeta \0.getValue.unmeta] } .list
     }
-    'hash' { > :Map ()
-        Map.pairs.fold(0) { \0 + \1.hash }
+
+    ### .map?? => true/bool
+    ###
+    ### SUMMARY
+    ###     Returns true to indicate that it is a map.
+    'map??' { ()
+        true
     }
-    'op_eq' { > :Map (:Another)
-        Another.map??
-        && Map.count == Another.count
-        && Another.pairs.all? { ([:Key :Value])
-            Map.has_key?(Key) && Map.get(Key) == Value
-        }
-    }
+
+    ### .show => /str
+    ###
+    ### SUMMARY
+    ###     Returns a string representation of the map.
     'show' { > :Map ()
         Map.Showing?_tl.get { false } .then {
             'map(...)'
             }
         }
     }
-    'map??' { true }
+
+    ### .hash => /int_num
+    ###
+    ### SUMMARY
+    ###     Returns a hash code of the map.
+    'hash' { > :Map ()
+        Map.pairs.fold(0) { \0 + \1.hash }
+    }
+
+    ### .op_eq(Another) => /bool
+    ###
+    ### SUMMARY
+    ###     Returns true if the argument is a map, and the two map are equal;
+    ###     in the other words, the two maps have equal sets of keys,
+    ###     and the values associated with the keys are equal.
+    ###
+    ### PARAM Another
+    ###     Value to compare to the map.
+    'op_eq' { > :Map (:Another)
+        Another.map??
+        && Map.count == Another.count
+        && Another.pairs.all? { ([:Key :Value])
+            Map.has_key?(Key) && Map.get(Key) == Value
+        }
+    }
+
+    ### .append(Another_map/map [Select_fun/fun(Common_key Value1 Value2 -> /value)]) => receiver/map
+    ###
+    ### SUMMARY
+    ###     Appends the entries of the argument map to the receiver map.
+    ###     If the both maps have a common key,
+    ###     Select_fun is called and the result of the function is set to the receiver map.
+    ###
+    ### PARAM Another_map
+    ###     A map.
+    ###
+    ### PARAM Select_fun
+    ###     A function to select a value for common keys.
+    ###     The first argument is the common key,
+    ###     the second argument is the value from the receiver map,
+    ###     and the third argument is the value from the argument map.
+    ###
+    ### RETURNS
+    ###     The receiver map itself.
+    'append' { > :Map (:Another :_select = {\2})
+        Another.pair_seq.loop { ([:Key :Value])
+            :Put = Map.Java_map.containsKey(meta(Key)).then {
+                _select(Key Map.Java_map.get(meta(Key)).unmeta Value)
+            } {
+                Value
+            }
+            Map.Java_map.put(meta(Key) meta(Put))
+        }
+        Map
+    }
+
+    ### .op_add(Another_map/map) => /map
+    ###
+    ### SUMMARY
+    ###     Returns a new map which merges the receiver map and the argument map.
+    ###     If the both maps have a common key,
+    ###     the value from the argument map is preferred.
+    ###
+    ### PARAM Another_map
+    ###     A map.
+    ###
+    ### RETURNS
+    ###     A new map.
+    'op_add' { > :Map (:Another)
+        Map.clone.tee {
+            \0.append(Another)
+        }
+    }
 )
 ._init(ConcurrentHashMap.new)

src/test/kink/MAP_test.kn

 #!/usr/bin/env kink
-# vim: et sw=4 sts=4
+# vim: et sw=4 sts=4 fdm=marker
 
 # Copyright (c) 2013 Miyakawa Taku
 # 
 # THE SOFTWARE.
 
 use('MAP.map')
-use('TEST.test')
 use('CHECK.check_thrown')
 use('COMMON_PATTERN.not')
 use('COMMON_PATTERN.eq')
 use('COMMON_PATTERN.ne')
 use('COMMON_PATTERN.identical_to')
+use('COMPARE')
 use('JAVA.null')
 
-test {
-    :Map = map(
-        'foo' 'foofoo'
-        'bar' 'barbar'
-        'baz' 'bazbaz'
-    )
-    Map.count.check( 3 )
-    Map.any?.check( true )
+use('TEST.test')
+use('TEST.test_group')
+use('TEST.set_up')
+
+test_group('Empty map') {
+    set_up(:Map) {
+        map
+    }
+
+    test('says it has 0 element') {
+        Map.count.check(0)
+    }
+
+    test('says it does not have any element') {
+        Map.any?.check(false)
+    }
+
+    test('says it is empty') {
+        Map.empty?.check(true)
+    }
 }
 
-test {
-    :Map = map()
-    Map.count.check( 0 )
-    Map.any?.check(false)
-    Map.empty?.check(true)
+test_group('map("hoge" Hoge "fuga" Fuga)') { # {{{1
+
+    set_up(:Hoge) { value('show' { 'Hoge' }) }
+    set_up(:Fuga) { value('show' { 'Fuga' }) }
+    set_up(:Fuga2) { value('show' { 'Fuga2' }) }
+    set_up(:Piyo) { value('show' { 'Piyo' }) }
+    set_up(:Hemo) { value('show' { 'Hemo' }) }
+
+    set_up(:Map) {
+        map('hoge' Hoge 'fuga' Fuga)
+    }
+
+    test_group('.count') {
+        test('says it has 2 elements') {
+            Map.count.check(2)
+        }
+    }
+
+    test_group('.any?') {
+        test('says it has any element') {
+            Map.any?.check(true)
+        }
+    }
+
+    test_group('.empty?') {
+        test('says it is not empty') {
+            Map.empty?.check(false)
+        }
+    }
+
+    test_group('.get') {
+        test('can get elements associated with keys from') {
+            Map.get('hoge').check(Hoge)
+            Map.get('fuga').check(Fuga)
+        }
+
+        test('throws an exception if the map does not have a key specified') {
+            :Exception = check_thrown {
+                Map.get('piyo')
+            }
+            Exception.message.check { \0.any?('piyo') }
+        }
+
+        test('can get a default value if the map does not have the specified key') {
+            :Result = Map.get('piyo') { Piyo }
+            Result.check(Piyo)
+        }
+
+        test('can get an element associated with the specified key ignoring the default provider') {
+            :Result = Map.get('hoge') {
+                throw('Should not be called')
+            }
+            Result.check(Hoge)
+        }
+    }
+
+    test_group('.get_if_any') {
+        test('can get an element associated with a key') {
+            Map.get_if_any('hoge').check([Hoge])
+            Map.get_if_any('fuga').check([Fuga])
+        }
+
+        test('returns an empty list if no entry is value with the key') {
+            Map.get_if_any('piyo').check([])
+        }
+    }
+
+    test_group('.has_key?') {
+        test('can decide it has a specified key or not') {
+            Map.has_key?('hoge').check(true)
+            Map.has_key?('fuga').check(true)
+            Map.has_key?('piyo').check(false)
+        }
+    }
+
+    test_group('.has?') {
+        test('can decide it has a specified value or not') {
+            Map.has?(Hoge).check(true)
+            Map.has?(Fuga).check(true)
+            Map.has?(Piyo).check(false)
+        }
+    }
+
+    test_group('.set') {
+        test('can set a value with a new key') {
+            :Result = Map.set('piyo' Piyo)
+            Result.check(identical_to(Map))
+            Map.count.check(3)
+            Map.get('piyo').check(Piyo)
+        }
+
+        test('can set a value with an existing key') {
+            :Result = Map.set('fuga' Fuga2)
+            Result.check(identical_to(Map))
+            Map.count.check(2)
+            Map.get('fuga').check(Fuga2)
+        }
+    }
+
+    test_group('.set_if_absent') {
+        test('can set a value with a new key') {
+            :Result = Map.set_if_absent('piyo' Piyo)
+            Result.check([])
+            Map.get('piyo').check(Piyo)
+        }
+
+        test('does not set a value with an existing key') {
+            :Result = Map.set_if_absent('fuga' Fuga2)
+            Result.check([Fuga])
+            Map.get('fuga').check(Fuga)
+        }
+    }
+
+    test_group('.set_if_any') {
+        test('can set a value with an existing key') {
+            :Result = Map.set_if_any('fuga' Fuga2)
+            Result.check([Fuga])
+            Map.get('fuga').check(Fuga2)
+        }
+
+        test('does not set a value with a new key') {
+            :Result = Map.set_if_any('piyo' Piyo)
+            Result.check([])
+            Map.has_key?('piyo').check(false)
+        }
+    }
+
+    test_group('.set_if_equal') {
+        test('sets a new value if the old value is already associated with the key') {
+            :Result = Map.set_if_equal('fuga' Fuga Fuga2)
+            Result.check(true)
+            Map.get('fuga').check(Fuga2)
+        }
+
+        test('does not set a new value if the existing value is not equal to the old value') {
+            :Result = Map.set_if_equal('fuga' Hoge Fuga2)
+            Result.check(false)
+            Map.get('fuga').check(Fuga)
+        }
+
+        test('does not set a new value if no value is associated with the key') {
+            :Result = Map.set_if_equal('piyo' Hoge Piyo)
+            Result.check(false)
+            Map.has_key?('piyo').check(false)
+        }
+    }
+
+    test_group('.pop') {
+        test('pops the value associated with a key') {
+            Map.pop('hoge').check(Hoge)
+            Map.has_key?('hoge').check(false)
+        }
+
+        test('returns the default value if no entry is found') {
+            Map.pop('piyo') { Piyo } .check(Piyo)
+        }
+
+        test('throws an exception if no entry is found and no function is given') {
+            :Exception = check_thrown { Map.pop('piyo') }
+            Exception.message.check { \0.any?('piyo') }
+        }
+    }
+
+    test_group('.pop_if_any') {
+        test('pops the value associated with a key') {
+            Map.pop_if_any('hoge').check([Hoge])
+            Map.has_key?('hoge').check(false)
+        }
+
+        test('returns an empty list if no value is found') {
+            Map.pop_if_any('piyo').check([])
+        }
+    }
+
+    test_group('.clear') {
+        test('removes all the entries') {
+            :Result = Map.clear
+            Result.check(identical_to(Map))
+            Map.empty?.check(true)
+        }
+    }
+
+    test_group('.clone') {
+        test('makes a new map equal to itself') {
+            :Clone_map = Map.clone
+            Clone_map.check(eq(Map))
+            Clone_map.check(not(identical_to(Map)))
+        }
+    }
+
+    test_group('.key_seq') {
+        test('returns a sequence of keys') {
+            :Key_seq = Map.key_seq
+            Key_seq.list.sort.check(['fuga' 'hoge'])
+        }
+    }
+
+    test_group('.keys') {
+        test('returns a list of keys') {
+            Map.keys.sort.check(['fuga' 'hoge'])
+        }
+    }
+
+    test_group('.seq') {
+        test('returns a sequence of values') {
+            :Value_seq = Map.seq
+            Value_seq.list.sort(COMPARE.by { \0.show }).check([Fuga Hoge])
+        }
+    }
+
+    test_group('.list') {
+        test('returns a list of values') {
+            :Values = Map.list
+            Values.sort(COMPARE.by { \0.show }).check([Fuga Hoge])
+        }
+    }
+
+    test_group('.pair_seq') {
+        test('returns a sequence of pairs') {
+            Map.pair_seq.list.sort(COMPARE.by { \0.first }).check([['fuga' Fuga] ['hoge' Hoge]])
+        }
+    }
+
+    test_group('.pairs') {
+        test('returns a list of pairs') {
+            Map.pairs.sort(COMPARE.by { \0.first }).check([['fuga' Fuga] ['hoge' Hoge]])
+        }
+    }
+
+    test_group('.show') {
+        test('returns a string representation of the map') {
+            use('COMMON_PATTERN.or')
+            Map.show.check(or(
+                "map('hoge' Hoge  'fuga' Fuga)"
+                "map('fuga' Fuga  'hoge' Hoge)"
+            ))
+        }
+    }
+
+} # }}}1
+
+test_group('Equality groups') {
+    set_up(:Key1_value1_group) {
+        [map('key1' 'value1') map('key1' 'value1')]
+    }
+
+    set_up(:Key1_value2_group) {
+        [map('key1' 'value2') map('key1' 'value2')]
+    }
+
+    set_up(:Key2_value1_group) {
+        [map('key2' 'value1') map('key2' 'value1')]
+    }
+
+    set_up(:Empty_map_group) {
+        :Map_proto = map.parent
+        :Empty_map = map
+        [Map_proto Empty_map]
+    }
+
+    test('equality constraint') {
+        use('CHECK.check_equality')
+        use('JAVA.null')
+        check_equality(
+            Key1_value1_group
+            Key1_value2_group
+            Key2_value1_group
+            Empty_map_group
+            [null]
+            [42]
+        )
+    }
 }
 
-test {
-    :Map = map('foo' 'FOO' 'bar' 'BAR')
-    Map.count.check(2)
-    Map.any?.check(true)
-    Map.empty?.check(false)
+test_group('Recursive map') {
+    set_up(:Recursive_map) {
+        :Map = map
+        Map.set('itself' Map)
+    }
+
+    test('.show returns the string representation') {
+        Recursive_map.show.check("map('itself' map(...))")
+    }
 }
 
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.get( 'foo' ).check( 0xf00 )
-    Map.get( 'bar' ).check( 0xbaa )
+test_group('2 maps') {
+    set_up(:Hoge) { value('show' { 'Hoge' }) }
+    set_up(:Fuga) { value('show' { 'Fuga' }) }
+    set_up(:Fuga2) { value('show' { 'Fuga2' }) }
+    set_up(:Piyo) { value('show' { 'Piyo' }) }
+
+    set_up(:Hoge_fuga_map) {
+        map(
+            'hoge' Hoge
+            'fuga' Fuga
+        )
+    }
+
+    set_up(:Fuga2_piyo_map) {
+        map(
+            'fuga' Fuga2
+            'piyo' Piyo
+        )
+    }
+
+    test_group('.append') {
+        test('appends entries of the given map') {
+            :Result = Hoge_fuga_map.append(Fuga2_piyo_map)
+            Result.check(identical_to(Hoge_fuga_map))
+
+            # Existing entry remains
+            Hoge_fuga_map.get('hoge').check(Hoge)
+
+            # Overwritten by the entry of the argument
+            Hoge_fuga_map.get('fuga').check(Fuga2)
+
+            # New entry from the argument
+            Hoge_fuga_map.get('piyo').check(Piyo)
+        }
+
+        test('appends entries of the given map selecting by the function') {
+            :Result = Hoge_fuga_map.append(Fuga2_piyo_map) { (:Key :First :Second)
+                expand('#Key #First #Second')
+            }
+            Result.check(identical_to(Hoge_fuga_map))
+
+            # Existing entry remains
+            Hoge_fuga_map.get('hoge').check(Hoge)
+
+            # Value from the selecting function
+            Hoge_fuga_map.get('fuga').check('fuga Fuga Fuga2')
+
+            # New entry from the argument
+            Hoge_fuga_map.get('piyo').check(Piyo)
+        }
+    }
+
+    test_group('.op_add') {
+        test('returns a new map which merges two maps') {
+            :New_map = Hoge_fuga_map + Fuga2_piyo_map
+            New_map.check(map('hoge' Hoge 'fuga' Fuga2 'piyo' Piyo))
+        }
+    }
 }
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.has_key?('foo').check( true )
-    Map.has_key?('xxx').check( false )
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.has?(0xbaa).check(true)
-    Map.has?(0xddd).check(false)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set('baz' 0xba2).check(identical_to(Map))
-    Map.get('baz').check(0xba2)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    check_thrown { Map.get('baz') }
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.get('baz') { 0xba2 }.check(0xba2)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.get('foo') { } { \0 / 2 }.check(0xf00 / 2)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_absent('baz' 0xba2).check(identical_to(Map))
-    Map.get('baz').check(0xba2)
-    Map.set_if_absent('baz' null).check(identical_to(Map))
-    Map.get('baz').check(0xba2)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_absent('foo' null) { \0 / 2 } .check(0xf00 / 2)
-    Map.get('foo').check(0xf00)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_absent('baz' 0xba2) { null } { 'set!' } .check( 'set!' )
-    Map.get('baz').check(0xba2)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_present('foo' 0xf000).check(identical_to(Map))
-    Map.get('foo').check(0xf000)
-    Map.set_if_present('baz' 0xba2).check(identical_to(Map))
-    Map.has_key?('baz').check(false)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_present('baz' 0xba2) { 'not set!' } .check( 'not set!' )
-    Map.has_key?('baz').check(false)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_present('foo' 0xf000) { null } { \0 / 2 } .check( 0xf00 / 2 )
-    Map.get('foo').check(0xf000)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_equal('bar' 0xbaa 0x999).check(identical_to(Map))
-    Map.get('bar').check(0x999)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_equal('bar' 0x999 0x888).check(identical_to(Map))
-    Map.get('bar').check(0xbaa)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_equal('bar' 0xbaa 0x999) { throw('fail') } { 'set!' } .check( 'set!' )
-    Map.get('bar').check(0x999)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.set_if_equal('bar' 0x999 0x888) { 'not set!' } { throw( 'fail' ) } .check( 'not set!' )
-    Map.get('bar').check(0xbaa)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.pop('foo').check(0xf00)
-    Map.has_key?('foo').check(false)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    check_thrown { Map.pop('baz') }
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.pop('baz') { 'not popped' } .check( 'not popped' )
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.pop('foo') {} { \0 / 2 }.check(0xf00 / 2)
-    Map.has_key?('foo').check(false)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    :Another = map('bar' 0xbaaa  'baz' 0xba2)
-    Map.append(Another).check(identical_to(Map))
-    Map.get('foo').check(0xf00)
-    Map.get('bar').check(0xbaaa)
-    Map.get('baz').check(0xba2)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    :Another = map('bar' 0xbaaa  'baz' 0xba2)
-    Map.append(Another) { \0 + (\1 - \2).show } .check(identical_to(Map))
-    Map.get('foo').check(0xf00)
-    Map.get('bar').check('bar' + (0xbaa - 0xbaaa).show)
-    Map.get('baz').check(0xba2)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    :Another = map('bar' 0xbaaa  'baz' 0xba2)
-    (Map + Another).check(map('foo' 0xf00  'bar' 0xbaaa  'baz' 0xba2))
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.clear.check(identical_to(Map))
-    Map.any?.check(false)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    :Clone = Map.clone
-    Clone.check(not(identical_to(Map)))
-    Clone.get('foo').check(0xf00)
-    Clone.get('bar').check(0xbaa)
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.key_seq.list.sort.check(['bar' 'foo'])
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.keys.sort.check(['bar' 'foo'])
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.seq.list.sort.check([0xbaa 0xf00])
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.list.sort.check([0xbaa 0xf00])
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.pair_seq.list.sort.check([['bar' 0xbaa] ['foo' 0xf00]])
-}
-
-test {
-    :Map = map('foo' 0xf00  'bar' 0xbaa)
-    Map.pairs.sort.check([['bar' 0xbaa] ['foo' 0xf00]])
-}
-
-test {
-    use('JAVA')
-    use('CHECK.check_equality')
-    check_equality(
-        [map('foo' 0xf00  'bar' 0xbaa) map('foo' 0xf00  'bar' 0xbaa)]
-        [map('fooo' 0xf00  'bar' 0xbaa) map('fooo' 0xf00  'bar' 0xbaa)]
-        [map('foo' 0xf000  'bar' 0xbaa) map('foo' 0xf000  'bar' 0xbaa)]
-        [map().parent map()]
-        [JAVA.null]
-        [42]
-    )
-}
-
-# Tests MAP.show
-test {
-    use('COMMON_PATTERN.or')
-    :Map = map('foo' 'FOO'  'bar' 'BAR')
-    Map.show.check(
-        or("map('foo' 'FOO'  'bar' 'BAR')"  "map('bar' 'BAR'  'foo' 'FOO')")
-    )
-}
-
-test {
-    :Map = map()
-    Map.set('map' Map)
-    Map.show.check("map('map' map(...))")
-}
-
-# Tests the prototype of maps
-test {
-    :Proto = map('foo' 'FOO' 'bar' 'BAR').parent
-    Proto.check(eq(map()))
-}
-
-# Tests MAP.map??
-test {
-    map().map??.check(true)
-}