Tutoriel - Fonctions métier pures
Réunir tous les constats d'une validation
Ce programme conserve tous les constats bloquants, dans leur ordre d'origine :
module examples.review_list
type Finding
label is Text
blocking is Boolean
function Report
takes findings as List of Finding
takes additional as List of Finding
gives Text
do
bind all as List of Finding = concat(findings, additional)
bind labels as List of Text = [item.label for item in all if item.blocking]
give join(labels, "; ")
touba compile review-list.tb --out review-list.air.json
touba run review-list.air.json --entry function:examples.review_list/Report --inputs-json '{"findings":[{"label":"Missing owner","blocking":true},{"label":"Style","blocking":false}],"additional":[{"label":"Expired policy","blocking":true}]}' --output json
Résultat : Missing owner; Expired policy. Une liste sans constat bloquant
produit un texte vide. Le filtre précède la transformation et les listes
d'entrée restent inchangées. Les expressions restent pures : les services
externes s'appellent depuis des tools.
Pour partager une règle d'affichage, exportez cette fonction depuis un package.
Les conventions monétaires et locales restent des choix métier du package ;
join assemble le texte déjà formaté.
Objectif : montrer une computation utile en Touba sans handler hôte.
Partitionner et rechercher
Ces deux fonctions conservent l'ordre et les listes d'entrée.
module examples.collections
type Groups
negative is List of Int
nonnegative is List of Int
function Partition
takes values as List of Int
gives Groups
do
bind negative as List of Int = [value for value in values if value < 0]
bind nonnegative as List of Int = [value for value in values if value >= 0]
give record Groups { negative as negative, nonnegative as nonnegative }
function FirstPositive
takes values as List of Int
gives Optional of Int
do
for value in values
if value > 0
give some(value)
give none(Int)
Avec [-2, 0, 4, -1], Partition retourne negative: [-2, -1] et
nonnegative: [0, 4] ; FirstPositive retourne some(4). Une entrée vide
donne deux listes vides et none(Int). Chaque parcours utilise la limite
d'itérations hôte, y compris pour les éléments filtrés.
Pour une somme générale, un tri ou un groupement dynamique en V1, utilisez un
tool de package au contrat d'effet kind: none. Le calcul métier reste dans
un handler JavaScript/Python testé et retourne un résultat typé. Les builtins
d'expression comprennent concat, join, text, date, some et none.
Une présence se traite avec if present ; sum et sort ne sont pas natifs.
Syntaxe utilisée
Constante de module :
define Name as Type = literal
Valeur locale immuable :
bind name as Type = expression
Retour de fonction :
give expression
Source
module examples.review_functions
define RiskThreshold as Float = 0.7
define FileThreshold as Int = 12
define ReviewPrefix as Text = "review: "
define FilesPrefix as Text = "files: "
type ChangeSignal
title is Text
high_impact is Boolean
risk is Float
files is Int
type ReviewDecision
approved is Boolean
label is Text
risk is Float
function CountRemainingReviews
takes requested as Int
takes completed as Int
gives Int
do
give requested - completed
function ComputeAdjustedRisk
takes signal as ChangeSignal
takes mitigation as Float
gives Float
do
bind adjusted as Float = signal.risk - mitigation
give adjusted
function BuildReviewLabel
takes signal as ChangeSignal
gives Text
do
bind label as Text = ReviewPrefix ++ signal.title ++ " / " ++ FilesPrefix ++ text(signal.files)
give label
function ShouldEscalateChange
takes signal as ChangeSignal
gives Boolean
do
bind risky as Boolean = signal.risk >= RiskThreshold
bind many_files as Boolean = signal.files > FileThreshold
give signal.high_impact or risky or many_files
function BuildReviewDecision
takes signal as ChangeSignal
gives ReviewDecision
do
bind label as Text = ReviewPrefix ++ signal.title
bind approved as Boolean = not signal.high_impact
give record ReviewDecision { risk as signal.risk, approved as approved, label as label }
strategy RunReview
takes signal as ChangeSignal
gives ReviewDecision
uses CountRemainingReviews
uses ComputeAdjustedRisk
uses BuildReviewLabel
uses ShouldEscalateChange
uses BuildReviewDecision
do
CountRemainingReviews
with requested as 5
with completed as 2
into remaining
ComputeAdjustedRisk
with signal as signal
with mitigation as 0.1
into adjusted
BuildReviewLabel
with signal as signal
into label
ShouldEscalateChange
with signal as signal
into escalation
BuildReviewDecision
with signal as signal
into decision
show
with message as label ++ " / remaining: " ++ text(remaining) ++ " / adjusted: " ++ text(adjusted) ++ " / escalate: " ++ text(escalation)
give decision
Ce que l’exemple montre
definepour des seuils non sensibles ;bindpour nommer des calculs intermédiaires ;Intavecrequested - completed;Floatavecsignal.risk - mitigation;TextavecReviewPrefix ++ signal.title;- rendu texte explicite avec
text(signal.files); Booleanavecor,>=,>;- accès champ avec
signal.risk,signal.files,signal.title.
Compiler
touba compile review-functions.tb --out review-functions.air.json
touba run review-functions.air.json --entry strategy:examples.review_functions/RunReview --inputs-json '{"signal":{"title":"Public report","high_impact":true,"risk":0.8,"files":4}}' --output json --trace-out trace.json
Le résultat est un AIR JSON lisible par le runtime et les outils d’inspection.
La stratégie appelle explicitement les cinq fonctions et retourne une décision
typée : approved: false, label: "review: Public report", risk: 0.8.
Le résultat JSON expose aussi messages, sans copier leur texte métier dans la
trace. Les bind des fonctions n’écrasent pas ceux de l’appelant. Aucun SDK,
manifest, secret ou service réseau n’est nécessaire.
À retenir
Les fonctions pures gardent la logique métier simple dans Touba. Les effets externes doivent rester dans des tools.
define ne remplace pas les secrets. Une clé API ou une variable d’environnement sensible doit rester déclarée comme secret handle et résolue explicitement par le runtime.