Saturday 3 June 2023

Destructuring and Custom Unpacking

Years ago I talked about Destructuring Assignment in JavaScript and C#. We could say that this feature in Python is quite similar to its JavaScript counterpart, and in Kotlin it's quite similar to the C# one. While in JavaScript and Python we use destructuring mainly with Arrays or lists/tuples, it can be used with any iterable object. I'm not sure why Kotlin did not follow this same iterable logic. To be "destructurable" Kotlin objects have to provide the component1(), component2()... methods, which is not so different from C# approach and deconstructors.

Reading this discussion about destructuring in Kotlin I've found what seems a good nomenclature to distinguish 2 types of destructuring: Positional and Nominal (featured only by JavaScript).

Positional Destructuring Assignment means that values are assigned to the variables just based on their position. The first variable gets the first value returned by the iterable (or by component1()) and so on. Nominal Destructuring Assignment is the JavaScript feature where we can destructure an object by assigning to a variable the property named as that variable, I mean:


const user = {
  id: 42,
  isVerified: true,
};

const { id, isVerified } = user;

console.log(id); // 42
console.log(isVerified); // true

One different feature that I tend to relate to destructuring are the rest-spread operator in JavaScript ("...") and the pack-unpack operators ("*", "**") in Python. The relation is that we can use rest-pack when destructuring, like this:


> function* cities() {
... yield "Paris";
... yield "Xixon";
... yield "Uvieu";
}

> let [a, ...b] = cities();

> a;
'Paris'
> b;
[ 'Xixon', 'Uvieu' ]



def cities():
    yield "Paris"
    yield "Xixon"
    yield "Uvieu"
    
a, *b = cities()

a
# 'Paris'
b
# ['Xixon', 'Uvieu']

In JavaScript, same as we can use Positional and Nominal destructuring, we can also use rest-spread not only with iterables, but also with any object. In Python, we can use "*" with any iterable object and "**" with dictionaries. Well, this second point is interesting, cause I've learned here that "**" works with any object that implements the collections.abc.Mapping abstract class. We have to explicitly extend collections.abc.Mapping, just adding those methods to our class and expecting Duck Typing or runtime Structural Typing to work won't make it.


from collections.abc import Mapping
# Structural typing won't work for this:
#class Person:

#I have to explicitly declare that it implements Mapping
class Person(Mapping):    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.stress_level = 1

    def __getitem__(self, key):
        return getattr(self, key)

    def __iter__(self):
        yield 'name'
        yield 'age'

    def __len__(self):
        return 2

class Politician:
    def __init__(self, name, age, party):
        self.name = name
        self.age = age
        self.party = party

p1 = Person("Xuan", 47)
politician = Politician(**p1, party="Radical Center")

print(politician.__dict__)

--------------

No comments:

Post a Comment