tl; dr Credo statico ArrowChoice
è ciò che stai cercando.
Per approfondire la tua domanda interessante: se vogliamo avere calcoli di ramificazione, dobbiamo essere in grado di passare gli output di un calcolo come input ai seguenti.
Per Applicative
s gli effetti non possono dipendere dai risultati, possiamo immaginare che prima eseguiamo tutti gli effetti e quindi combiniamo tutti i risultati usando un puro calcolo. Pertanto possiamo rappresentare tale computazione con l'input i
, l'output o
e l'effetto m
come m (i -> o)
.
Gli effetti di Monad
s possono dipendere completamente dagli input. Quindi possiamo rappresentare calcoli come i -> m o
.
Se vogliamo ottenere una via di mezzo, dobbiamo parametrizzare i nostri calcoli sia in input che in output. Ed è così che arriviamo a Arrow
s, che copre lo spazio tra Applicative
se Monad
s.
Proprio come ogni monade, ogni freccia a
aumenta di un Applicative
. Se fissiamo l'input, possiamo combinare due frecce a i o1
e a i o2
usando &&&
(e quindi mappare la parte pura del risultato con >>>
e arr
).
D'altra parte, da ogni% applicativo% co_de possiamo definire una freccia come f
. Questo è il tipo più debole di una freccia: la sua potenza è equivalente solo a f (i -> o)
. Tali frecce sono chiamate frecce statiche e possono essere riconosciute avendo un isomorfismo tra f
e a i o
(mentre abbiamo a () (i -> o)
per qualsiasi freccia, esiste l'opposto solo per quelle statiche). In altre parole, l'effetto interno della freccia non dipende dal suo input.
All'altra estremità dello spettro abbiamo a () (i -> o) -> a i o
frecce. Per qualsiasi monade possiamo costruire arrow Kleisli
, il cui potere è equivalente alla monade.
La parte interessante è che possiamo avere frecce tra i -> m o
e Monad
. Un bell'esempio, che era la motivazione per inventare le frecce, è la combinazione di parser statici e dinamici ¸ Il parser ha una parte statica che determina l'insieme di caratteri che il parser è in grado di consumare successivamente, e questa parte statica è calcolata senza dover eseguire la freccia.
Immagino che la tua idea sia avere
ifA :: forall a. f Boolean -> f a -> f a -> f a
e per passare valori a calcoli ramificati usando le operazioni di stack. Direi che per le impostazioni funzionali è più naturale incorporare i valori nella ramificazione. Infatti, l'operazione più vicina in Applicative
( che descrive i calcoli che possono diramarsi) è
(|||) :: a b d -> a c d -> a (Either b c) d
C'è un concetto ancora più generale - frecce generalizzate . Permette di rappresentare calcoli che non sono un superset di Haskell (in ArrowChoice
possiamo incorporare qualsiasi funzione pura usando Arrow
), come i circuiti bidirezionali che possono essere usati sia per l'analisi che per la stampa carina.