From: Jean Privat Date: Tue, 3 Mar 2015 15:04:56 +0000 (+0700) Subject: Merge: Introduce Cloneable interface X-Git-Tag: v0.7.3~44 X-Git-Url: http://nitlanguage.org?hp=a6d33c1aefc24736f9726a9cac26329e06e9f840 Merge: Introduce Cloneable interface As exposed by #917 a standard API to clone objects could be useful. This PR propose a Cloneable interface that class can implement. The specif semantic of what a clone is is domain-dependent, the only suggestion I define is to be POLA and to consider the post condition `self == clone`. It seems sane since the precise semantic of `==` is also domain-dependent. The post-condition is not mandatory since not all classes redefines `==` and maybe some classes could be cloneable without being `==`-able. The reason is that `clone` is instantaneous but the semantic `==` should remain valid all the time. For instance, It could make sense to clone some Iterators but I am not sure what is a sane definition of `==` on them. As a POC, clone is implemented is the three Array-related classes, Array, ArraySet and ArrayMap. Pull-Request: #1176 Reviewed-by: Lucas Bajolet Reviewed-by: Alexandre Terrasa Reviewed-by: Alexis Laferrière --- diff --git a/lib/standard/collection/abstract_collection.nit b/lib/standard/collection/abstract_collection.nit index 858d569..20d1584 100644 --- a/lib/standard/collection/abstract_collection.nit +++ b/lib/standard/collection/abstract_collection.nit @@ -466,6 +466,30 @@ interface MapRead[K, V] # Note: the value is returned *as is*, implementations may want to store the value in the map before returning it # @toimplement protected fun provide_default_value(key: K): V do abort + + # Does `self` and `other` have the same keys associated with the same values? + # + # ~~~ + # var a = new HashMap[String, Int] + # var b = new ArrayMap[Object, Numeric] + # assert a == b + # a["one"] = 1 + # assert a != b + # b["one"] = 1 + # assert a == b + # b["one"] = 2 + # assert a != b + # ~~~ + redef fun ==(other) + do + if not other isa MapRead[nullable Object, nullable Object] then return false + if other.length != self.length then return false + for k, v in self do + if not other.has_key(k) then return false + if other[k] != v then return false + end + return true + end end # Maps are associative collections: `key` -> `item`. diff --git a/lib/standard/collection/array.nit b/lib/standard/collection/array.nit index ae351cf..1fb8581 100644 --- a/lib/standard/collection/array.nit +++ b/lib/standard/collection/array.nit @@ -252,6 +252,7 @@ end # assert a == b class Array[E] super AbstractArray[E] + super Cloneable redef fun [](index) do @@ -393,6 +394,29 @@ class Array[E] return true end + # Shallow clone of `self` + # + # ~~~ + # var a = [1,2,3] + # var b = a.clone + # assert a == b + # a.add 4 + # assert a != b + # b.add 4 + # assert a == b + # ~~~ + # + # Note that the clone is shallow and elements are shared between `self` and the result. + # + # ~~~ + # var aa = [a] + # var bb = aa.clone + # assert aa == bb + # aa.first.add 5 + # assert aa == bb + # ~~~ + redef fun clone do return to_a + # Concatenation of arrays. # # Returns a new array built by concatenating `self` and `other` together. @@ -473,6 +497,7 @@ end # A set implemented with an Array. class ArraySet[E] super Set[E] + super Cloneable # The stored elements. private var array: Array[E] is noinit @@ -519,6 +544,37 @@ class ArraySet[E] init with_capacity(i: Int) do _array = new Array[E].with_capacity(i) redef fun new_set do return new ArraySet[E] + + # Shallow clone of `self` + # + # ~~~ + # var a = new ArraySet[Int] + # a.add 1 + # a.add 2 + # var b = a.clone + # assert a == b + # a.add 3 + # assert a != b + # b.add 3 + # assert a == b + # ~~~ + # + # Note that the clone is shallow and keys and values are shared between `self` and the result. + # + # ~~~ + # var aa = new ArraySet[Array[Int]] + # aa.add([1,2]) + # var bb = aa.clone + # assert aa == bb + # aa.first.add 5 + # assert aa == bb + # ~~~ + redef fun clone + do + var res = new ArraySet[E] + res.add_all self + return res + end end # Iterators on sets implemented with arrays. @@ -538,6 +594,7 @@ end # Associative arrays implemented with an array of (key, value) pairs. class ArrayMap[K, E] super CoupleMap[K, E] + super Cloneable # O(n) redef fun [](key) @@ -616,6 +673,35 @@ class ArrayMap[K, E] end return -1 end + + # Shallow clone of `self` + # + # ~~~ + # var a = new ArrayMap[String,Int] + # a["one"] = 1 + # a["two"] = 2 + # var b = a.clone + # assert a == b + # a["zero"] = 0 + # assert a != b + # ~~~ + # + # Note that the clone is shallow and keys and values are shared between `self` and the result. + # + # ~~~ + # var aa = new ArrayMap[String, Array[Int]] + # aa["two"] = [1,2] + # var bb = aa.clone + # assert aa == bb + # aa["two"].add 5 + # assert aa == bb + # ~~~ + redef fun clone + do + var res = new ArrayMap[K,E] + res.recover_with self + return res + end end private class ArrayMapKeys[K, E] diff --git a/lib/standard/kernel.nit b/lib/standard/kernel.nit index 5cc0547..21b0fc1 100644 --- a/lib/standard/kernel.nit +++ b/lib/standard/kernel.nit @@ -226,6 +226,25 @@ interface Discrete end end +# Something that can be cloned +# +# This interface introduces the `clone` method used to duplicate an instance +# Its specific semantic is let to the subclasses. +interface Cloneable + # Duplicate `self` + # + # The specific semantic of this method is let to the subclasses; + # Especially, if (and how) attributes are cloned (depth vs. shallow). + # + # As a rule of thumb, the principle of least astonishment should + # be used to guide the semantic. + # + # Note that as the returned clone depends on the semantic, + # the `==` method, if redefined, should ensure the equality + # between an object and its clone. + fun clone: SELF is abstract +end + # A numeric value supporting mathematical operations interface Numeric super Comparable diff --git a/tests/sav/nitg-e/fixme/base_gen_reassign_alt4.res b/tests/sav/nitg-e/fixme/base_gen_reassign_alt4.res index 0856809..2ddb582 100644 --- a/tests/sav/nitg-e/fixme/base_gen_reassign_alt4.res +++ b/tests/sav/nitg-e/fixme/base_gen_reassign_alt4.res @@ -1,4 +1,4 @@ -Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:413) +Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:432) 11 21 31 diff --git a/tests/sav/nitg-e/fixme/base_gen_reassign_alt5.res b/tests/sav/nitg-e/fixme/base_gen_reassign_alt5.res index 0856809..2ddb582 100644 --- a/tests/sav/nitg-e/fixme/base_gen_reassign_alt5.res +++ b/tests/sav/nitg-e/fixme/base_gen_reassign_alt5.res @@ -1,4 +1,4 @@ -Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:413) +Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:432) 11 21 31 diff --git a/tests/sav/nitg-e/fixme/base_gen_reassign_alt6.res b/tests/sav/nitg-e/fixme/base_gen_reassign_alt6.res index 0856809..2ddb582 100644 --- a/tests/sav/nitg-e/fixme/base_gen_reassign_alt6.res +++ b/tests/sav/nitg-e/fixme/base_gen_reassign_alt6.res @@ -1,4 +1,4 @@ -Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:413) +Runtime error: Cast failed. Expected `OTHER`, got `Float` (../lib/standard/kernel.nit:432) 11 21 31 diff --git a/tests/sav/nituml_args3.res b/tests/sav/nituml_args3.res index e248df6..24adbed 100644 --- a/tests/sav/nituml_args3.res +++ b/tests/sav/nituml_args3.res @@ -30,6 +30,11 @@ Discrete [ ] Comparable -> Discrete [dir=back arrowtail=open style=dashed]; +Cloneable [ + label = "{interface\nCloneable||+ clone(): SELF\l}" +] +Object -> Cloneable [dir=back arrowtail=open style=dashed]; + Numeric [ label = "{interface\nNumeric||+ +(i: OTHER): OTHER\l+ -(i: OTHER): OTHER\l+ unary -(): OTHER\l+ *(i: OTHER): OTHER\l+ /(i: OTHER): OTHER\l+ to_i(): Int\l+ to_f(): Float\l+ is_zero(): Bool\l+ zero(): OTHER\l+ value_of(val: Numeric): OTHER\l}" ] diff --git a/tests/sav/nituml_args4.res b/tests/sav/nituml_args4.res index 9a7b30f..81077d2 100644 --- a/tests/sav/nituml_args4.res +++ b/tests/sav/nituml_args4.res @@ -30,6 +30,11 @@ Discrete [ ] Comparable -> Discrete [dir=back arrowtail=open style=dashed]; +Cloneable [ + label = "{interface\nCloneable||+ clone(): SELF\l}" +] +Object -> Cloneable [dir=back arrowtail=open style=dashed]; + Numeric [ label = "{interface\nNumeric||+ +(i: OTHER): OTHER\l+ -(i: OTHER): OTHER\l+ unary -(): OTHER\l+ *(i: OTHER): OTHER\l+ /(i: OTHER): OTHER\l+ to_i(): Int\l+ to_f(): Float\l+ is_zero(): Bool\l+ zero(): OTHER\l+ value_of(val: Numeric): OTHER\l}" ] diff --git a/tests/sav/test_new_native_alt1.res b/tests/sav/test_new_native_alt1.res index d21eb0d..cf299e7 100644 --- a/tests/sav/test_new_native_alt1.res +++ b/tests/sav/test_new_native_alt1.res @@ -1,4 +1,4 @@ -Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:808) +Runtime error: Cast failed. Expected `E`, got `Bool` (../lib/standard/collection/array.nit:894) NativeString N Nit