Comprehensions Python vers Catnip

Guide de traduction des list/dict/set comprehensions Python vers Catnip idiomatique.

Voir aussi : BROADCAST_GUIDE pour les patterns broadcast, FOLD_GUIDE pour l'agrégation.

Rappel

Une comprehension combine trois opérations : itération, transformation, filtrage.

[x * 2 for x in data]               # map
[x for x in data if x > 5]          # filter
[x * 2 for x in data if x > 5]      # filter + map

Catnip n'a pas de comprehensions. Le broadcasting et la ND-recursion couvrent ces cas avec moins de surface syntaxique et des propriétés supplémentaires (parallélisme, descente dans les structures imbriquées).


Map

# Python
[x ** 2 for x in numbers]
[f(x) for x in data]
# Catnip
numbers.[** 2]
data.[~> f]

Le broadcast descend automatiquement dans les structures imbriquées. Le même code fonctionne sur un scalaire, une liste, un tuple ou une structure imbriquée.


Filter

# Python
[x for x in data if x > 5]
# Catnip
data.[if > 5]

Filter + map

# Python
[x * 2 for x in data if x > 5]
# Catnip : chaînage
data.[if > 5].[* 2]

Le chaînage est composable : chaque maillon est un filtre ou une transformation indépendante.

data.[if > 3].[* 2].[if < 20]

Map avec lambda

# Python
[x.upper() for x in names if len(x) > 3]
# Catnip
names.[if (n) => { len(n) > 3 }].[~> (n) => { upper(n) }]

Quand l'opération n'est pas un opérateur binaire, ~> applique une fonction à chaque élément.


Masque booléen

# Python
[x for x, m in zip(data, mask) if m]
# Catnip
data.[mask]

Transformation récursive (parallèle)

# Python - séquentiel obligatoire
[fib(n) for n in range(20)]
# Catnip : parallélisable sans modification
pragma("nd_mode", ND.thread)
pragma("nd_memoize", True)

range(20).[~~ (n, recur) => {
    if n <= 1 { n }
    else { recur(n - 1) + recur(n - 2) }
}]

La ND-recursion (~~) passe de séquentiel à parallèle en changeant un pragma. Aucune comprehension ne permet ça.


Produit cartésien

# Python
[(i, j) for i in range(3) for j in range(3)]
# Catnip : boucles explicites
result = list()
for i in range(3) {
    for j in range(3) {
        result = result + list(tuple(i, j))
    }
}

C'est le cas où Catnip est plus verbeux. Ce pattern est rare dans du code idiomatique.


Flatmap

# Python
[y for x in nested for y in x]
# Catnip : fold + concatenation
fold(nested, list(), (acc, x) => { acc + x })

Dict comprehension

dict() accepte un itérable de paires (clé, valeur). Le broadcast produit la liste de paires, dict() la convertit.

# Python
{k: v for k, v in pairs}
# Catnip
dict(pairs)

Dict depuis une transformation

# Python
{x: x ** 2 for x in range(5)}
# Catnip
dict(range(5).[(x) => { tuple(x, x ** 2) }])

Dict depuis deux listes

# Python
dict(zip(keys, values))
# Catnip
dict(zip(keys, values))

Dict avec filtre

# Python
{x: x ** 2 for x in range(10) if x % 2 == 0}
# Catnip : filtrer d'abord, puis transformer
dict(range(10).[if (x) => { x % 2 == 0 }].[(x) => { tuple(x, x ** 2) }])

fold pour les cas complexes

Quand la construction du dict nécessite un accumulateur (compteurs, groupement, fusion), fold reste disponible.

fold(words, dict(), (acc, w) => {
    acc[w] = (acc[w] or 0) + 1; acc
})

Récapitulatif

Python Catnip
[x * 2 for x in data] data.[* 2]
[f(x) for x in data] data.[~> f]
[x for x in data if x > 5] data.[if > 5]
[x * 2 for x in data if x > 5] data.[if > 5].[* 2]
[x for x, m in zip(...) if m] data.[mask]
[fib(n) for n in range(20)] range(20).[~~ fib]
{x: x**2 for x in data} dict(data.[(x) => { tuple(x, x**2) }])
produit cartésien boucles for imbriquées
flatmap fold(nested, list(), (a, x) => { a + x })

Les set comprehensions ({x for ...}) utilisent le même pattern avec set() à la place de dict().