Monday 24 April 2023

Access to Non Existing Index or Key

JavaScript, Kotlin and Python have some important differences when accessing indexes or keys that do not exist in a List/Array or Dictionary/Map. Notice that in Python we use the term attribute for fields in an object, and property for the classic getter-setter thing (that is implemented via descriptors). In JavaScript we use the term property for fields, and those properties can be a getter-setter descriptor or just a data descriptor. In Kotlin we use the term property, that always involves a getter and maybe a setter (that can be just the default ones that have no extra logic but accessing the backing field).

JavaScript is very permissive (I'm not surprised...). Accessing a non existing index of an array, a non existing key of a dictionary (a Map) or a non existing property of an object will not throw an exception, but return undefined.


 
> let ar = [];
> ar[2];
undefined
> 
> let dic = new Map();
> dic.get("name");
undefined
> 
> let ob = {};
> ob.name;
undefined

Python is not that permissive and any of the 3 accesses (missing index, missing key or missing attribute) will cause a different exception


ar = []
ar[2]
IndexError: list index out of range 

d = {}
d["name"]
KeyError: 'name'

ob = object()
ob.name
AttributeError: 'object' object has no attribute 'name'


For safe access to a dictionary key we can use the .get(key, default) method, and for safe access to an object attribute we can use getattr(ob, attr_name, default). Oddly enough, lists do not have a .get(index, default) method. Checking this question the reasonings for not having such method seem totally absurd to me. It's one of those occasions where the "being pythonic" and "zen of python" crap really infurates me. Python is an amazing language, but it could be even better if those that make the excellent work of developing and maintaining it were a bit more open to change.

In Kotlin, as it's a static language, accessing a non existent property of an object makes no sense, as the compiler prevents that. As for accessing a not existing index or key, the behaviour is a bit odd. A non existing index in an Array or List will throw an exception, but a non existing key in a map will just return null.


    val ls = listOf("a", "b")
    try {
        println(ls[4])
    }
    catch (e: Throwable) {
        println("${e::class.simpleName} - ${e.message}")
        // ArrayIndexOutOfBoundsException
    }

    val dic = mapOf(
        "France" to "Paris",
        "Austria" to "Vienna",
    )
    try {
        println(dic["Italy"]) // No exception, returns Null
    }
    catch (e: Throwable) {
        println("${e::class.simpleName} - ${e.message}")
    }

We can use getOrNull with an Array or List to get the same behaviour that we have with Maps. We also have getOrElse to return a default value rather than null. With Maps we can use both getOrElse or getOrDefault. Notice that the list and map retrieval behaviours are explained here and here

Friday 21 April 2023

List Comprehensions vs Chaining Style

For sure list-comprehensions are a beautiful Python feature, but I've realised that I've been overusing them lately, applying a list comprehension to another list-comprehension, which makes the code not particularly clear. This has led me to compare it with the common approach followed in other languages: chaining methods on collections (JavaScript array extras or lodash, linq, pydash in python itself, Kotlin collections methods...). Let's compare some examples:

Using nested list comprehensions like this in Python seems pretty elegant to me:


cities = [ city.upper()
	for country in countries if country.name.startswith("c")
	for city in country.cities if city.name.startswith("c")
]

We can write an equivalent in "chaining style" in JavaScript like this:


let cities = countries.filter(country => country.startsWith("c"))
	.flatmap(country => country.cities)
	.filter(city => city.startsWith("c")
	.map(city => city.toUpper());


In this case I think I prefer the list-comprehensions option.

Nested list-comprehensions are also very nice if I want to combine values from the main and the nested collection. For example here, where I want a listing of country-city:


country_and_cities = [ [country.name, city.name]
	for country in countries 
	for city in country.cities 
]

That I would write in "chaining style" in JavaScript like this


let countryAndCities = countries.flatMap(country => country.cities.map(city => [country.name, city.name]));

Let's go now with those cases where I think list-comprehensions make code not so pleasant to read.

For example for parsing some lines read from a CSV into a list of objects we would write:


@dataclass
class Person:
	name: str
	age: int
	
countries = [Person(items[0], int(items[2])
	for items in [
		line.split(",") 
		for line in lines if not line.startswith("--")
	]
]

We would write it in "chaining style" in javascript like this:


lines.filter(line => !line.startsWith("--")
.map(line => {
	let [name, age] = line.split(",");
	return new Person(name, int(population));
});


And for dumping a list of objects to a CSV string we have this Python code with list-comprehensions:


users = [Person("Francois", 55), Person("Iyan", 45)]

"\n".join([user_line for user_line in 
    [";".join(user.name, str(user.age))
    for user in users]
])

That I think looks better in "chaining style" with javascript like this (or using pydash in python for example)


users.map(user => [user.name, user.age].join(";"))
	.join("\n");


Friday 14 April 2023

Python Walrus Operator

The walrus operator aka Assignment Expressions was added to Python a few years ago, and it seems pretty interesting to me as I had never seen it before in other languages. As the name says, it allows us to have expressions that assign to a value. The main sample in the PEP-572 documentation gives us some useful use case:


# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match

# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
   process(chunk)

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

I've started to use it in conditionals like this:


if (us := get_user()) and us.startswith("a"):
    print(f"yes, {us} starts with a")
else:
    print("no")

Given that in Kotlin almost everything is an expression I was expecting it to treat assignments as expressions also, but that's not the case. When searching about that I found out to my surprise that Java provides this feature!.

Though I'm still going through the process of interiorizing when/how to use Kotlin's scope functions and similar ones like takeIf, I've managed to come up to rewrite the conditional above like this:


    getUser()?.takeIf {
	    it.startsWith("a")
    }?.let { println("yes, ${it} starts with a") }

We can use walrus in Python when passing parameters to a function (even with named parameters):


def say(txt):
    print(txt)   

say(msg := "hi")
# hi
print(msg)
# 'hi'

say(txt=(msg := "hey"))
# hey
print(msg)
# hey

We can use it also as an indexer


items = [1,2,3,4,5,6]
pos = 0
items[pos:(pos:=pos+2)]
Out[46]: [1, 2]
pos
Out[47]: 2

Which seems pretty useful to write code like this:


def chunks_generator_fn(chunk_size, items):
    cur_pos = 0
    while cur_pos < len(items):
        next_chunk = items[cur_pos:(cur_pos := cour_pos + chunk_size]
        yield next_chunk

And one more use case:


if (index := index + 1) > end:
        index = 0

# looks better to me than:
index += 1
if index > end:
        index = 0        

Sunday 2 April 2023

forEach Method vs for of loop

While I'm using JavaScript here, this post applies to any language providing a forEach() iteration method as an alternative to traditional loops. I think it was in 2005 with that beautiful library called prototype.js that I first came across the possibility of iterating an array using a method (in modern JavaScript it would be the Array.prototype.forEach() method) rather than a "normal" loop construct. At that time it seemed so cool and using it was like a way of bragging. Over the years I've found myself using it more and more rarely. Of course I love all the iterate-transform methods: map, filter, etc in Array extras or lodash, but when all I need is iterating an array to apply some action I tend to use the "for of" loop.

I've been thinking lately if there's any advantage in using forEach() rather than for of. Well, indeed forEach() has some limitations. First, if your action is an async function forEach() won't work (though we saw that it's pretty simple to implement an async counterpart). Then you have to simulate "continue" using "return" (but in those cases I think it's more natural to use a normal loop), and we can't simulate "break". Of course we could write a new forEach() that treats an specific exception (for example BreakError) as a break.

When those limitations do not apply (we don't need await, break or continue), one could wonder if forEach() makes our code more declarative, more functional. I general terms, I don't think so, and I'm not alone [1] and [2]

However, there are specific cases when forEach() can make our code look better. Let's see:

- Combined with the safe navigation operator:


users?.forEach(user -> /* doWhatever */);

//probably looks better than:

if (users){
	for (const user of users) {
	 /* do whatever */
	}
}


- When you have already chained calls to other Array Extras methods



getUsers().filter(user -> user.country == "France")
	.forEach(user -> /* doWhatever */);

// maybe looks better than:

for (const user of getUsers.filter(user -> user.country == "France")){
	/* do whatever */
}