Repository: OpenMined/PySyft

Issue:
Everything should simplify as a tuple in serde._simplify
# Describe the bug
Currently, when you run `_simplify` on a dictionary or set, you are returned information contained within square brackets (`[]`).  It would be a lot easier if we could always work with parens (`()`).

## To Reproduce & Expected behavior

**Dictionary**
_Input:_ `sy.serde._simplify({1: 'hello', 'key2': 999})`
_Output:_ `(0, [(1, (5, (b'hello',))), ((5, (b'key2',)), 999)])`
_Expected:_ `(0, ((1, (5, (b'hello',))), ((5, (b'key2',)), 999)))`

**Set**
_Input:_ `sy.serde._simplify({'apple', 'cherry', 'banana'})`
_Output:_ `(3, [(5, (b'banana',)), (5, (b'apple',)), (5, (b'cherry',))])`
_Expected:_ `(3, ((5, (b'banana',)), (5, (b'apple',)), (5, (b'cherry',))))`

## Screenshots

**Dictionary**
<img width="504" alt="Screen Shot 2019-08-01 at 1 06 06 PM" src="https://user-images.githubusercontent.com/1297930/62292376-2f991d80-b45e-11e9-9dda-74ae44c75914.png">

**Set**
<img width="590" alt="Screen Shot 2019-08-01 at 1 20 23 PM" src="https://user-images.githubusercontent.com/1297930/62292895-4ab85d00-b45f-11e9-9804-8623d05b1f51.png">

## Desktop
 - OS: MacOS
 - Version 10.14.6

## Additional context
I need this working for syft.js's Serde parser to work as expected without having to perform unique string replacement all over the place.  The simpler Serde is in terms of structure, the easier it is to write ports of it to Javascript (syft.js), Kotlin (@mccorby), and other languages.



Discussion:

Resolving patch:
diff --git a/syft/serde/native_serde.py b/syft/serde/native_serde.py
--- a/syft/serde/native_serde.py
+++ b/syft/serde/native_serde.py
@@ -16,51 +16,46 @@
 # Simplify/Detail Collections (list, set, tuple, etc.)
 
 
-def _simplify_collection(my_collection: Collection) -> Collection:
+def _simplify_collection(my_collection: Collection) -> Tuple:
     """
     This function is designed to search a collection for any objects
     which may need to be simplified (i.e., torch tensors). It iterates
     through each object in the collection and calls _simplify on it. Finally,
-    it returns the output collection as the same type as the input collection
-    so that the consuming serialization step knows the correct type info. The
-    reverse function to this function is _detail_collection, which undoes
-    the functionality of this function.
+    it returns the output as the tuple of simplified items of the input collection.
+    This function is used to simplify list, set, and tuple. The reverse function,
+    which undoes the functionality of this function is different for each of these types:
+    _detail_collection_list, _detail_collection_set, _detail_collection_tuple.
 
     Args:
         my_collection (Collection): a collection of python objects
 
     Returns:
-        Collection: a collection of the same type as the input of simplified
-            objects.
+        Tuple: a tuple with simplified objects.
 
     """
 
-    # Step 0: get collection type for later use and itialize empty list
-    my_type = type(my_collection)
+    # Step 0: initialize empty list
     pieces = list()
 
     # Step 1: serialize each part of the collection
     for part in my_collection:
         pieces.append(serde._simplify(part))
 
-    # Step 2: convert back to original type and return serialization
-    if my_type == set:
-        return pieces
-
+    # Step 2: return serialization as tuple of simplified items
     return tuple(pieces)
 
 
-def _detail_collection_list(worker: AbstractWorker, my_collection: Collection) -> Collection:
+def _detail_collection_list(worker: AbstractWorker, my_collection: Tuple) -> Collection:
     """
     This function is designed to operate in the opposite direction of
-    _simplify_collection. It takes a collection of simple python objects
+    _simplify_collection. It takes a tuple of simple python objects
     and iterates through it to determine whether objects in the collection
     need to be converted into more advanced types. In particular, it
     converts binary objects into torch Tensors where appropriate.
 
     Args:
         worker: the worker doing the deserialization
-        my_collection (Collection): a collection of simple python objects (including binary).
+        my_collection (Tuple): a tuple of simple python objects (including binary).
 
     Returns:
         Collection: a collection of the same type as the input where the objects
@@ -81,17 +76,17 @@ def _detail_collection_list(worker: AbstractWorker, my_collection: Collection) -
     return pieces
 
 
-def _detail_collection_set(worker: AbstractWorker, my_collection: Collection) -> Collection:
+def _detail_collection_set(worker: AbstractWorker, my_collection: Tuple) -> Collection:
     """
     This function is designed to operate in the opposite direction of
-    _simplify_collection. It takes a collection of simple python objects
+    _simplify_collection. It takes a tuple of simple python objects
     and iterates through it to determine whether objects in the collection
     need to be converted into more advanced types. In particular, it
     converts binary objects into torch Tensors where appropriate.
 
     Args:
         worker: the worker doing the deserialization
-        my_collection (Collection): a collection of simple python objects (including binary).
+        my_collection (Tuple): a tuple of simple python objects (including binary).
 
     Returns:
         Collection: a collection of the same type as the input where the objects
@@ -139,13 +134,12 @@ def _detail_collection_tuple(worker: AbstractWorker, my_tuple: Tuple) -> Tuple:
     return tuple(pieces)
 
 
-def _simplify_dictionary(my_dict: Dict) -> Dict:
+def _simplify_dictionary(my_dict: Dict) -> Tuple:
     """
     This function is designed to search a dict for any objects
     which may need to be simplified (i.e., torch tensors). It iterates
     through each key, value in the dict and calls _simplify on it. Finally,
-    it returns the output dict as the same type as the input dict
-    so that the consuming serialization step knows the correct type info. The
+    it returns the output tuple of tuples containing key/value pairs. The
     reverse function to this function is _detail_dictionary, which undoes
     the functionality of this function.
 
@@ -153,8 +147,8 @@ def _simplify_dictionary(my_dict: Dict) -> Dict:
         my_dict: A dictionary of python objects.
 
     Returns:
-        Dict: A dictionary of the same type as the input of simplified
-            objects.
+        Tuple: Tuple containing tuples of simplified key/value pairs from the
+            input dictionary.
 
     """
     pieces = list()
@@ -162,10 +156,10 @@ def _simplify_dictionary(my_dict: Dict) -> Dict:
     for key, value in my_dict.items():
         pieces.append((serde._simplify(key), serde._simplify(value)))
 
-    return pieces
+    return tuple(pieces)
 
 
-def _detail_dictionary(worker: AbstractWorker, my_dict: Dict) -> Dict:
+def _detail_dictionary(worker: AbstractWorker, my_dict: Tuple) -> Dict:
     """
     This function is designed to operate in the opposite direction of
     _simplify_dictionary. It takes a dictionary of simple python objects
@@ -175,10 +169,10 @@ def _detail_dictionary(worker: AbstractWorker, my_dict: Dict) -> Dict:
 
     Args:
         worker: the worker doing the deserialization
-        my_dict (Dict): a dictionary of simple python objects (including binary).
+        my_dict (Tuple): a simplified dictionary of simple python objects (including binary).
 
     Returns:
-        tuple: a collection of the same type as the input where the objects
+        Dict: a collection of the same type as the input where the objects
             in the collection have been detailed.
     """
     pieces = {}


Test patch:
diff --git a/test/test_serde.py b/test/test_serde.py
--- a/test/test_serde.py
+++ b/test/test_serde.py
@@ -59,7 +59,7 @@ def test_set_simplify():
     input = set(["hello", "world"])
     set_detail_index = serde.detailers.index(native_serde._detail_collection_set)
     str_detail_index = serde.detailers.index(native_serde._detail_str)
-    target = (set_detail_index, [(str_detail_index, (b"hello",)), (str_detail_index, (b"world",))])
+    target = (set_detail_index, ((str_detail_index, (b"hello",)), (str_detail_index, (b"world",))))
     assert serde._simplify(input)[0] == target[0]
     assert set(serde._simplify(input)[1]) == set(target[1])
 
@@ -109,7 +109,7 @@ def test_dict_simplify():
     detail_str_index = serde.detailers.index(native_serde._detail_str)
     target = (
         detail_dict_index,
-        [((detail_str_index, (b"hello",)), (detail_str_index, (b"world",)))],
+        (((detail_str_index, (b"hello",)), (detail_str_index, (b"world",))),),
     )
     assert serde._simplify(input) == target