Concepts fondamentaux¶
Ce guide explore les concepts fondamentaux de fptk, en privilégiant l'aspect pratique par des exemples concrets plutôt qu'une plongée théorique approfondie.
Les fonctions comme briques de construction¶
fptk considère les fonctions comme des briques de construction réutilisables, modulables de diverses manières.
pipe() : Flux de données linéaire¶
pipe() orchestre le passage de données à travers une séquence de fonctions :
from fptk.core.func import pipe
def process_user_data(raw_data):
return pipe(
raw_data,
parse_json, # Étape 1 : analyse
validate_user, # Étape 2 : validation
save_to_db, # Étape 3 : sauvegarde
send_welcome # Étape 4 : notification
)
Avantages :
- Lecture intuitive (de haut en bas)
- Ajout/suppression d'étapes simplifiée
- Test unitaire aisé de chaque étape
compose() : Construction de fonctions¶
compose() assemble des fonctions pour en créer de nouvelles :
from fptk.core.func import compose
# Crée une nouvelle fonction à partir de fonctions existantes
process_and_save = compose(save_to_db, validate_user, parse_json)
# Utilisation
result = process_and_save(raw_data)
Note : compose applique les fonctions de droite à gauche, soit compose(f, g)(x) équivalent à f(g(x)).
curry() : Appels de fonctions flexibles¶
curry() permet d'appeler des fonctions de manière partielle :
from fptk.core.func import curry
def send_email(to, subject, body):
# Logique d'envoi d'e-mail
pass
# Crée des fonctions spécialisées
send_support_email = curry(send_email)("support@company.com")
notify_user = send_support_email("Bienvenue !")
# Utilisation
notify_user("Bienvenue sur notre plateforme !")
Gérer les données manquantes avec Option¶
La valeur None en Python est souvent source d'erreurs. L'Option de fptk rend l'absence d'une valeur explicite.
Utilisation basique d'Option¶
from fptk.adt.option import Some, NOTHING, from_nullable
# Convertit les valeurs potentiellement None
name = from_nullable(user.get('name')) # Some("Alice") ou NOTHING
# Gère l'absence en toute sécurité
display_name = name.map(lambda n: n.upper()).unwrap_or("Anonyme")
Chaîner les opérations Optionnelles¶
def get_full_name(user):
return (
from_nullable(user.get('first_name'))
.zip(from_nullable(user.get('last_name')))
.map(lambda names: f"{names[0]} {names[1]}")
.or_else(lambda: from_nullable(user.get('display_name')))
.unwrap_or('Anonyme')
)
get_full_name({'first_name': 'John', 'last_name': 'Doe'}) # "John Doe"
get_full_name({'display_name': 'Johnny'}) # "Johnny"
get_full_name({}) # "Anonyme"
Opérations clés :
| Méthode | Description |
|---|---|
map(f) |
Transforme la valeur si elle est présente. |
bind(f) |
Enchaîne des opérations qui renvoient une Option. |
zip(other) |
Combine deux Option en un tuple. |
or_else(fallback) |
Propose une Option de repli en cas d'absence. |
unwrap_or(default) |
Récupère la valeur ou une valeur par défaut. |
Gestion des erreurs avec Result¶
Les exceptions sont pertinentes pour les erreurs inattendues, mais Result apporte plus de clarté pour les échecs prévisibles (validation, analyse, etc.).
Utilisation basique de Result¶
from fptk.adt.result import Ok, Err, Result
def divide(a: int, b: int) -> Result[int, str]:
if b == 0:
return Err("Division par zéro")
return Ok(a // b)
result = divide(10, 2) # Ok(5)
error = divide(10, 0) # Err("Division par zéro")
Chaîner les Results¶
def process_payment(amount, card_number):
return (
validate_amount(amount)
.bind(lambda amt: validate_card(card_number))
.bind(lambda card: charge_card(amount, card))
)
# Renvoie soit Ok(success_data) soit Err(error_message)
result = process_payment(100, "4111111111111111")
Opérations clés :
| Méthode | Description |
|---|---|
map(f) |
Transforme la valeur en cas de succès. |
bind(f) |
Enchaîne des opérations qui renvoient un Result. |
map_err(f) |
Modifie l'erreur en cas d'échec. |
unwrap_or(default) |
Récupère la valeur ou une valeur par défaut. |
Travailler avec les collections¶
fptk propose des utilitaires d'itérateurs paresseux (lazy iterators) pour un traitement efficace des collections.
Traitement paresseux¶
from fptk.core.func import pipe
from fptk.iter.lazy import map_iter, filter_iter
# Traite les grands jeux de données sans tout charger en mémoire
def process_logs(logs):
return pipe(
logs,
lambda ls: filter_iter(lambda log: log['level'] == 'ERROR', ls),
lambda ls: map_iter(lambda log: log['message'], ls),
list
)
Groupement et découpage¶
from fptk.iter.lazy import group_by_key, chunk
# Regroupe les données par catégorie (l'entrée doit être triée par clé)
grouped = dict(group_by_key(users, lambda u: u['department']))
# Traite les données par lots
for user_batch in chunk(users, 10):
process_batch(user_batch)
Opérations asynchrones¶
Gérez les opérations concurrentes avec une gestion des erreurs structurée.
Rassembler les Results¶
from fptk.async_tools import gather_results
async def fetch_user_data(user_ids):
tasks = [fetch_user_api(uid) for uid in user_ids]
# Retourne Ok([user_data]) ou Err(première_erreur)
return await gather_results(tasks)
Pipelines asynchrones¶
from fptk.core.func import async_pipe
async def process_request(request):
return await async_pipe(
request,
parse_async,
validate_async,
save_async,
notify_async
)
Validation¶
Accumulez toutes les erreurs de validation au lieu de vous arrêter à la première.
from fptk.adt.result import Ok, Err
from fptk.validate import validate_all
def validate_user(user):
return validate_all([
lambda u: Ok(u) if u.get('email') else Err("Email requis"),
lambda u: Ok(u) if '@' in u.get('email', '') else Err("Email invalide"),
lambda u: Ok(u) if len(u.get('password', '')) >= 8 else Err("Mot de passe trop court")
], user)
validate_user({'email': 'invalide', 'password': 'court'})
# Err(NonEmptyList("Email invalide", "Mot de passe trop court"))
Assembler le tout¶
Voici un exemple complet combinant plusieurs concepts :
from fptk.core.func import pipe
from fptk.adt.result import Ok, Err
from fptk.validate import validate_all
def process_registration(data):
return pipe(
data,
validate_registration,
lambda valid: valid.bind(save_user),
lambda saved: saved.bind(send_welcome_email),
lambda result: result.map(lambda user: {
'user_id': user['id'],
'message': 'Enregistrement réussi'
})
)
def validate_registration(data):
return validate_all([
lambda d: Ok(d) if d.get('email') else Err("Email requis"),
lambda d: Ok(d) if d.get('password') else Err("Mot de passe requis"),
], data)
# Utilisation
result = process_registration({
'email': 'utilisateur@example.com',
'password': 'secure123'
})
# Ok({'user_id': 123, 'message': 'Enregistrement réussi'})
Cet exemple illustre comment les différents concepts de fptk s'articulent pour créer un code robuste et lisible.