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 avecset()à la place dedict().