PARTE IV FUNZIONI RICORSIVE E LINGUAGGI FUNZIONALI Funzioni ricorsive Accenno al lambda-calcolo Formalismo di Mc Carthy e linguaggi funzionali 1 4.1 FUNZIONI RICORSIVE Tra gli approcci sviluppati negli anni ’30 per definire il concetto di algoritmo un ruolo interessante hanno le funzioni ricorsive, introdotte da S. Kleene nel 1936. Anche in questo caso i risultati hanno portato una conferma alla Tesi di Church-Turing. Lo stesso Kleene ha subito dimostrato che la classe di funzioni definibili come funzioni ricorsive coincideva con la classe delle funzioni calcolabili secondo Turing e con la classe delle funzioni definibili con il lambdacalcolo. 2 Nel seguito considereremo solo funzioni del tipo Nx...xN → N Def. La classe delle funzioni ricorsive primitive è la (più piccola) classe di funzioni che contiene le funzioni base: - zero, cioè le funzioni O(n) (x1, ... , xn) = 0 nel caso n = 0 la funzione O(0) ( ) corrisponde alla costante 0 - successore, cioè la funzione S (x) = x + 1 - selettive (o identità), cioè le funzioni Ui(n) (x1, ... , xn) = xi 3 ed è chiusa rispetto alle operazioni di - composizione: date le funzioni g1, ..., gm di n argomenti e la funzione h di m argomenti definiamo f(x1, ... , xn) = h (g1(x1, ... , xn), ... , gm (x1, ... , xn)) - ricursione primitiva: date le funzioni g, di n argomenti e h di n+2 argomenti definiamo f(x1, ..., xn, 0) = g(x1, ... , xn) f(x1, ..., xn, y+1) = h(x1, ... , xn, y, f(x1, ... , xn, y)) 4 ESEMPI Nota bene. I numeri naturali si ottengono come costanti utilizzando le funzioni base e la composizione nel seguente modo: O(0) , S(O(0)), S(S(O(0))), S(S(S(O(0)))), ... Inoltre possiamo utilizzare le funzioni base e la composizione per definire ad esempio le funzioni di più argomenti ma con valore costante: uno (x) = S(O(1)(x)), due (x) = S(S(O(1)(x))), ecc. oppure le funzioni che incrementano un valore dato: piùdue (x) = S(S(x)), piùtre (x) = S(S(S(x))), ecc. 5 Per definire altre funzioni usiamo la ricursione primitiva Possiamo ad esempio dimostrare che la somma è una funzione ricorsiva primitiva: somma (x, 0) = U1(1) (x) somma (x, y+1) = S(U3(3) (x, y, somma (x, y))) Cioè in questo caso le funzione g ed h che compaiono nello schema della ricursione primitiva (e che devono già essere ricorsive primitive) sono rispettivamente la funzione U1(1) e una funzione ottenuta dalla composizione di S e U3(3). Più semplicemente potremo scrivere: somma (x, 0) = x somma (x, y+1) = S(somma (x, y)) 6 ALTRI ESEMPI predecessore (0) = O(0) = 0 predecessore (y+1) = U2(3) (x, y, predecessore (y)) = y sub (x, 0) = U1(1) (x) = x sub (x, y+1) = predecessore (U3(3) (x, y, sub (x, y))) = predecessore (sub (x, y)) prodotto (x, 0) = 0 prodotto (x, y+1) = somma (x, prodotto (x, y)) esp (x, 0) = 1 esp (x, y+1) = prodotto (x, esp (x, y)) 7 Poiché operiamo solo con interi non negativi dobbiamo convenire che: 0–1=0 e che a – b = 0 se a ≤ b Esercizio. Supponendo che le funzioni g(x) e h(x) siano ricorsive primitive mostrare che la seguente funzione è ricorsiva primitiva: f(x, y) = g(x) se x ≤ y = h(x) se x > y 8 Si noti che la ricursione primitiva può essere realizzata in un linguaggio di programmazione con il solo costrutto ‘for’. 9 Le funzioni ricorsive primitive non esauriscono l’insieme delle funzioni calcolabili: 1.innanzitutto sono tutte funzioni totali e noi sappiamo che tra le funzioni calcolabili ci sono necessariamente funzioni parziali, ad esempio la funzione f(dM, x) = 1 se la macchina di Turing M si arresta su input x, indefinito altrimenti 2.in secondo luogo esistono funzioni definite mediante meccanismi di ricursione più complessi della ricursione primitiva (ad es. ricursione doppia) che sono calcolabili mediante algoritmi ma non ricorsive primitive 10 Consideriamo la seguente funzione: torre (x, 0) = 1 torre (x, y+1) = esp (x, torre (x, y)) Si noti che la funzione torre ha valori che crescono molto rapidamente. Ad esempio torre (3, 4) = esp (3, esp (3, esp (3, 3))) = esp (3, esp (3, 27)) = esp (3, 7 x 1012)) = esp (37,1012)) che è maggiore di esp (220,1012)) > 10 6000 miliardi Nota che il numero di atomi dell’universo è inferiore a 10100. Il procedimento che ci ha fatto passare dall’esponenziale alla torre può essere ripetuto all’infinito. 11 Funzione di Ackermann. Consideriamo le seguenti funzioni: f0 (x, y) = x+y f1 (x, y) = x y e, per n ≥ 2 fn (x, y) = se y=0 allora 1 altrimenti fn-1(x, fn (x, y-1)) Esse sono tutte ricorsive primitive ma se le consideriamo come una unica funzione di tre variabili ciò non è più vero. f(n, x, y) = se n = 0 allora x+y altrimenti se n = 1 allora xy altrimenti se y = 0 allora 1 altrimenti f(n-1, x, f(n, x, y-1)) 12 Teorema La funzione A(z) = f(z, z, z) (funzione di Ackermann) non è ricorsiva primitiva Cenno di dimostrazione. Si può dimostrare che essa cresce più rapidamente di ogni funzione ricorsiva di una variabile. Tuttavia la funzione di Ackermann è chiaramente calcolabile. Per definire le funzioni calcolabili gli operatori di composizione e ricursione primitiva non sono sufficienti. E’ necessario introdurre un’ulteriore operazione per definire una classe di funzioni più ampia. 13 Def. La classe delle funzioni ricorsive generali è la (più piccola) classe di funzioni che contiene le funzioni base ed è chiusa rispetto alle operazioni di - composizione - ricursione primitiva - minimalizzazione: data la funzione g di n+1 argomenti possiamo definire f(x1, ... , xn) = µt (g(x1, ... , xn, t) = 0) che si interpreta nel seguente modo: il valore di f è il minimo valore di t che soddisfa il predicato g(x1, ... , xn, t) = 0 (se per nessun valore di t accade che g(x1, ... , xn, t) = 0 allora f(x1, ... , xn) non è definita. 14 NOTA BENE Per comodità possiamo usare una formulazione leggermente modificata dell’operazione di minimalizzazione. Anzichè porre come condizione da verificare il predicato g(x1, ..., xn, t) = 0 per una opportuna funzione g, possiamo utilizzare più in generale un predicato del tipo s (x1, ..., xn, t) > r (x1, ..., xn, t) dove s e r sono funzioni ricorsive. Nota che il predicato a = b può essere espresso anche come g (a, b) = 0 se si definisce g (a, b) = (a – b) + (b – a) 15 Esempio (√x) = µt ((t+1)(t+1) > x) x=1 t=0 t=1 (t+1)(t+1) = 1 (t+1)(t+1) = 4 x=2 t=0 t=1 (t+1)(t+1) = 1 (t+1)(t+1) = 4 x=3 t=0 t=1 (t+1)(t+1) = 1 (t+1)(t+1) = 4 x=4 t=0 t=1 t=2 (t+1)(t+1) = 1 (t+1)(t+1) = 4 (t+1)(t+1) = 9 16 Si noti che l’operazione di minimalizzazione (corrispondente al costrutto ‘while ... do ...’ o a costrutti iterativi simili presenti nei linguaggi di programmazione) è molto comoda anche per definire funzioni che (come nel caso della parte intera della radice) sono in realtà ricorsive primitive. A tal fine è utile introdurre una variante dell’operazione di minimalizzazione, chiamata µ-limitato. Teorema Siano h e g funzioni ricorsive primitive; la funzione così definita (µ-limitato): f(x1, ... , xn) = µt ≤ h(x1, ... , xn)(g(x1, ... , xn, t) = 0) = µt (g(x1, ... , xn, t) = 0) se esiste t ≤ h(x1, ... , xn) tale che g(x1, ... , xn, t) = 0, 0 altrimenti è ricorsiva primitiva. 17 Esercizio Dimostrare il teorema precedente. Suggerimento: realizzare prima i seguenti esercizi. Data una funzione g(t) che sicuramente assume valore 0 nell’intervallo [0, T], definire le seguenti funzioni: g’(t) = 0 se g(t) > 0 = 1 se g(t) = 0 g’’(t) = t’ se g(t’) = 0 e per ogni t<t’ g(t) > 0 = 0 altrimenti 18 Si può facilmente verificare che le funzioni ricorsive sono calcolabili mediante macchine di Turing o mediante macchine a registri. Più difficile è mostrare il risultato opposto e cioè: Teorema. Ogni funzione calcolabile con una macchina a registri è ricorsiva. Dim. Per semplificare la dimostrazione possiamo partire, anziché dalle macchine a registri, dalle macchine a registri elementari e mostrare che la funzione calcolata da una macchina a registri elementare è ricorsiva. Ricordiamo che una MREL viene inizializzata con i valori x1, ..., xn nei registri R1, ..., Rn e quando termina (una MREL termina se esegue un salto all’istruzione 0) il valore del risultato è contenuto nel registro R1. 19 Osserviamo innanzitutto che la configurazione di una MREL, è completamente determinata se forniamo il contenuto dei suoi registri e il contenuto del ‘program counter’, cioè il numero d’ordine della prossima istruzione da eseguire. Per trasformare tali valori in numeri interi gestibili da una MREL possiamo adottare le seguenti codifiche. Data una sequenza di interi x1, ..., xn denotiamo con < x1, ..., xn> l’intero 2x13x25x3 ... pnxn Naturalmente dato un intero z = < x1, ..., xn> i valori di x1, ..., xn si possono recuperare mediante la decomposizione in fattori primi. Indichiamo con (z)k l’esponente del primo pk nella decomposizione di z. Se pn è il massimo numero primo che divide z, abbiamo evidentemente 20 z = <(z)1, ..., (z)n> Utilizzando tale codifica possiamo codificare il contenuto dei registri e la configurazione di una MREL nel seguente modo: c(registri) = <cont(R1), ..., cont(Rn)> c(configurazione) = <cont(PC), c(registri)> ed una computazione eseguita da una MREL, cioè una sequenza di configurazioni c0, c1, ..., ck, come c(computazione) = <c(c0), c(c1), ..., c(ck)> A questo punto, dato un programma π per MREL si può definire un predicato Tπ che dato un intero t ed una n-pla di interi x1, ..., xn, verifica se t sia il codice di una computazione legale compiuta dal programma π con input x1, ..., xn. 21 Il predicato assume quindi valore vero se e solo se: - (t)0 è la codifica della configurazione iniziale - (t)n è la codifica della configurazione finale se e solo se pn è il massimo numero primo che divide t e il PC contiene 0 - per ogni i ≥ 0, (t)i+1 è correttamente la configurazione seguente alla configurazione (t)i in base a quanto previsto dalle istruzioni del programma π. Supponendo che un intero t soddisfi il predicato Tπ, per il dato input x1, ... , xn, il valore della funzione calcolata dal programma π sarà recuperabile facilmente dalla configurazione (t)n poiché non è altro che il contenuto del registro R1 nella configurazione finale della computazione, e cioè il valore Uπ(t) = (((t)n)2)1 In definitiva avremo che la funzione f calcolata dalla MREL soddisfa la seguente forma normale (Forma Normale di Kleene): f(x1, ... , xn) = Uπ (µt (Tπ (x1, ... , xn, t))) 22 4.2 LAMBDA CALCOLO Il lambda calcolo (introdotto dal logico americano A. Church negli anni 1932-36) costituisce un modello formale che ha un ruolo importante nello studio dei fondamenti della programmazione. Infatti esso ha determinato l’introduzione di una notazione per la rappresentazione di funzioni nonché altri importanti concetti di programmazione ed è stato utilizzato per lo studio delle proprietà semantiche dei programmi. Notazione lambda Sia data l’espressione x + y2. A seconda di come la si considera essa può assumere diversi significati. - se x ed y sono variabili di tipo intero di cui assumiamo fissato il valore, l’espressione corrisponde al valore intero z = x + y2, il suo tipo è quindi il tipo ‘intero’, cioe’ N; 23 - se x ed y sono variabili di tipo intero ma di esse non e’ fissato il valore, l’espressione corrisponde ad una regola che consente di associare ad ogni coppia di valori x ed y il valore x + y2. Il suo tipo è quindi quello di una funzione intera di due argomenti, cioè NxN→N - se assumiamo che x ed y siano due variabili ma che i loro valori siano noti in momenti successivi l’espressione può essere considerata una regola che mi permette di associare ad ogni valore di x una funzione (del solo argomento y) che a sua volta mi permetterà di associare ad ogni valore y, il valore x + y2. Il suo tipo è dunque N→ (N → N) 24 La notazione lambda mi permette di distinguere i tre casi scrivendo rispettivamente: x + y2 λ(x, y). x + y2 λx. λy. x + y2 La notazione lambda corrisponde alla dichiarazione del tipo di una funzione e dei suoi parametri formali. 25 NOTA BENE La seconda e la terza espressione sono formalmente distinte ma sostanzialmente equivalenti, in quanto ogni funzione di due argomenti può essere considerata come una infinità di funzioni di un solo argomento (il secondo) per le quali il primo argomento costituisce un parametro. Nel caso in oggetto abbiamo le infinite funzioni (della sola variabile y) λy. 0 + y2 λy. 1 + y2 ... λy. k + y2 ... Quando si determina il valore k della variabile x automaticamente (con un procedimento di valutazione parziale) selezioniamo la funzione di una sola variabile λy. k + y2 il cui valore sarà poi determinato al momento della conoscenza del valore della y. 26 La trasformazione dalla seconda alla terza forma si chiama Currizzazione, dal nome del logico Curry che ne ha studiato le proprietà. In realtà la notazione lambda non è solo una notazione ma pone le premesse per un vero e proprio sistema di calcolo. 27 Formule. Chiamiamo lambda-formule le espressioni così definite: - una variabile x, y, z, ... è una lambda-formula - se A e B sono lambda-formule l’espressione (AB) è una lambdaformula (applicazione) - se x è una variabile ed A è una lambda formula, l’espressione B = λx. A è una lambda-formula (lambda-astrazione). La variabile x si dice che occorre legata nella formula B. Le variabili legate hanno lo stesso ruolo dei parametri formali di una funzione in un programma. Si noti inoltre che possiamo sempre attribuire un nome (ad es. A) ad una formula (ad esempio la formula (λx. (λy. ((x y) y))) 28 Regole del calcolo (riduzioni). α-riduzione (ridenominazione delle variabili legate). se una variabile occorre legata in una formula A essa può essere ridenominata con una variabile che non occorre in A. β-riduzione. Se la formula λx. A si applica ad una formula B l’effetto della β-riduzione è di sostituire B in ogni occorrenza di x in A (denotato con A [x/B]), curando che nessuna variabile libera in B diventi una variabile legata in A [x/B] η-riduzione. Se nella formula λx. Bx la variabile x non occorre in B la formula λx. Bx si riduce a B. 29 Esempi Il primo esempio di utilizzazione delle β-riduzioni (esempio informale, perché fa uso in modo misto della notazione lambda e di usuali espressioni algebriche) è il seguente. Data la formula: (((λx. λy. x + y2) 3) 4) essa si riduce prima a ((λy. 3 + y2) 4) e poi a 3 + 42 Un altro esempio, più astratto, è il seguente: ((λx. (λy. ((x y) y))) λu. u w) si riduce a λy. (((λu. u w) y) y) e poi a λy. ((y w) y) 30 Il seguente esempio, una semplice variante del precedente, richiede prima una α-riduzione: ((λx. (λy. ((x y) y))) λw. y w) --> ((λx. (λu. ((x u) u))) λw. y w) perchè y è libera in λw. y w e diventerebbe legata a seguito della sostituzione di λw. y w al posto di x. Si ottiene così λu. (((λw. y w) u) u) e poi λu. ((y u) u) 31 Prima di proseguire osserviamo che per alleggerire la notazione possiamo introdurre le seguenti proprietà di associatività: - l’applicazione associa a sinistra, cioè la formula ((x y) z) può essere scritta anche x y z - la lambda astrazione associa a destra, cioè la formula (λx. (λy. A)) può essere anche scritta λx. λy. A 32 Calcolo di funzioni numeriche mediante le regole del lambda-calcolo Per poter definire funzioni numeriche assumiamo innanzitutto che alcune (una infinità) di formule vengano interpretate come rappresentazioni dei numeri naturali. Esistono vari sistemi di rappresentazione. Il più classico e comodo è il seguente: 0 --> 1 --> 2 --> 3 --> .... n --> .... λx. λy. y λx. λy. xy λx. λy. (x (xy)) λx. λy. (x (x (xy))) λx. λy. (x (x (x ... (xy)...)) n volte Le formule corrispondenti ai naturali sono rappresentate in genere con il valore del naturale sottolineato: 0, 1, 2, ..., n, ... 33 Le seguenti proprietà sono facilmente dimostrabili: (iterazione) 1fx = fx n f x = f (f ( … (f x)…)) = f n x n volte Nota bene che, coerentemente, abbiamo 0 f x = f0 x = x e quindi se I = λx.x è la formula che svolge il ruolo dell’identità abbiamo che per ogni f , 0 f = f0 = I (esponenziale) (composizione) B f g x = f (g x) n m = mn Sia B = λx. λy. λz. (x (yz)) Bnm = nxm 34 Potremmo così proseguire definendo formule per tutte le funzioni base, per gli operatori di ricursione e di minimalizzazione e quindi ottenendo in definitiva il seguente risultato. Teorema Il lambda-calcolo consente di calcolare tutte e sole le funzioni calcolabili secondo Turing Questo risultato, ottenuto da Turing nel 1936, è alla base della affermazione (molto più forte e, chiaramente, indimostrabile) chiamata tesi di Church o di Church-Turing: Ogni funzione calcolabile mediante un qualunque sistema di calcolo o un qualunque algoritmo è calcolabile secondo Turing. 35 4.3 FORMALISMO DI MC CARTHY E LINGUAGGI FUNZIONALI I linguaggi di programmazione funzionali devono la loro origine a vari filoni culturali: - all’approccio basato sulle funzioni ricorsive, come metodo per la definizione del concetto di calcolabilità - al lambda-calcolo, come notazione per la rapresentazione di funzioni e come sistema di calcolo che prevede una notevole flessibilità di meccanismi di valutazione (esecuzione) e prevede la possibilità di elaborare oggetti di tipo diverso (dati, funzioni ecc.) Anello di congiunzione tra questi filoni ed i veri e propri linguaggi funzionali è il formalismo di Mc Carthy (definito a metà degli anni ‘50) e strettamente collegato al primo e più famoso dei linguaggi di programmazione funzionali, il linguaggio LISP. 36 Il formalismo di Mc Carthy consente di definire la struttura sintattica dei programmi (schemi di programmi). Le diverse interpretazioni dei simboli utilizzati nel formalismo conducono a diversi possibili linguaggi di programmazione. Simboli disponibili Υ = insieme contenente infiniti simboli di variabile, x0, x1, x2, ... , x, y, z, ... Β = insieme contenente infiniti simboli di funzioni base, b0, b1, b2, ..., a, b, c, ... Φ = insieme contenente infiniti simboli di funzioni variabili, F0, F1, F2, ..., F, G, H, ... 37 a: N --> N, funzione che determina la arità delle funzioni base A: N --> N, funzione che determina la arità delle funzioni variabili Ad esempio se a (1) = 3 vuol dire che la funzione base b1 ha tre argomenti Nota bene. Se la arità di una funzione base bi è 0, bi ha il ruolo di una costante. 38 Termini su < Υ, Β, Φ > Definizione induttiva: - un simbolo di variabile è un termine; - un simbolo di funzione base con arità 0 (una costante) è un termine; - se t1, ..., tn sono termini e la arità di bi è n allora - bi (t1, t2, ..., tn) è un termine - se t1, ..., tn sono termini e la arità di Fi è n allora - Fi (t1, t2, ..., tn) è un termine. Null’altro è un termine. L’insieme dei termini su < Υ, Β, Φ > è denotato Τ (Υ, Β, Φ ). 39 Se un termine non contiene variabili è chiamato termine chiuso (o ‘ground’, o di base). L’insieme dei termini chiusi su <Υ, Β, Φ > è denotato con Τ0 (Υ, Β, Φ ). Esempi. Se a(0) = 0, a(1) = 2, a(2) = 1, A(0) = 2, i seguenti sono termini in Τ (Υ, Β, Φ ): b1 (b0, b0) F0 (F0 (b0, b2 (b0)), b1 (b2 (b0), b0)) F0 (F0 (x, y), b1 (b2 (x), b0)) Il primo e il secondo sono termini chiusi. 40 Dichiarazioni e programmi su < Υ, Β, Φ > Uno schema di programma su < Υ, Β, Φ > è un insieme di coppie <Fi, ti> dove Fi ∈ Φ e t i ∈ Τ (Υ, Β, Φ ), seguito da un termine chiuso t0 ∈ Τ0 (Υ, Β, Φ ). Ogni coppia <Fi, ti> costituisce di fatto una dichiarazione di funzione e viene rappresentata nel seguente modo: Fi ⇐ ti 41 Esempi F (x) ⇐ b2 (x, b1 (b0), b0 (x, F (b1 (y)))) F (b1 (b0)) F (x, y) ⇐ b1 (F (x, b2 (y)) F (b1(b1(b0)), b2 (b0)) 42 Interpretazione di schemi di programmi: il linguaggio SLF Per passare da schemi di programmi a programmi (cioè per passare dal formalismo di Mc Carthy ad un vero e proprio linguaggio di programmazione) dobbiamo interpretare i simboli di Υ e Β; l’interpretazione di Φ si otterrà di conseguenza. 43 Assumiamo quindi in particolare la seguente interpretazione: - i simboli di variabile in Υ si interpretano come variabili intere; - i simboli di funzione base in Β si interpretano come funzioni intere; - in particolare assumiamo che Β sia costituito dalle seguenti funzioni: - per ogni numero naturale c, infinite funzioni costanti CC(n) (x1, ... , xn) = c, di arità n ≥ 0 che restituiscono il valore intero costante c; per ogni c, anziché scrivere la funzione CC(0) utilizziamo il corrispondente naturale c; - S (x) = x + 1, il successore, di arità 1; - P (x) = x - 1, il predecessore, di arità 1; - if-then-else (x, y, z) = se x = 0 allora y, altrimenti z. 44 Il linguaggio SLF (Semplice Linguaggio Funzionale) è l’insieme dei programmi che si ottengono interpretando nel modo detto gli schemi di programmi del formalismo di Mc Carthy. Esempio: il programma che calcola il fattoriale di 4. F1 (x, y) ⇐ if-then-else (y, x, S ( F1 (x, P (y)))) F2 (x, y) ⇐ if-then-else (y, 0, F1 ( x, F2 (x, P (y)))) F3 (x) ⇐ if-then-else (x, 1, F2 ( x, F3 (P (x)))) F3 (4) 45 Calcolabilità delle funzioni ricorsive generali in SLF. Non è difficile mostrare che tutte le funzioni ricorsive sono calcolabili in SLF. Funzioni base. Le funzioni base zero e successore sono disponibili come funzioni base nel repertorio del linguaggio SLF. Le funzioni selettive si possono calcolare con il programma: Ui(n) (x1, ... , xn) ⇐ xi 46 Composizione. Una funzione F definita per composizione della funzione h con le funzioni g1, .. , gm si può definire in SLF come segue: F (x1, .. , xn) ⇐ h (g1(x1, ... , xn), .. , gm (x1, .. , xn)) 47 Ricursione primitiva Una funzione F definita per ricursione primitiva a partire dalle funzioni g ed h: F (x1, ..., xn, 0) = g(x1, ... , xn) F (x1, ..., xn, y+1) = h(x1, ... , xn, y, F (x1, ... , xn, y)) può essere definita in SLF come segue: F (x1, ..., xn, y) ⇐ if-then-else (y, g(x1, ... , xn), h (x1, ... , xn, P(y), F (x1, ... , xn, P(y)))) 48 Minimalizzazione Una funzione f definita per minimalizzazione a partire dalla funzione g: F (x1, ... , xn) = µt (g(x1, ... , xn, t) = 0) può essere definita in SLF come segue: F (x1, ... , xn) ⇐ G (x1, ... , xn, 0) G (x1, ... , xn, t) ⇐ if-then-else (g(x1, ... , xn, t), t, G (x1, ... , xn, S (t))) 49