In un problema di apprendimento con cui ho fatto confusione, mi sono reso conto che avevo bisogno di una classe di caratteri per le funzioni con operazioni per l'applicazione, la composizione, ecc. Ragioni ...
Può essere conveniente trattare una rappresentazione di una funzione come se fosse la funzione stessa, in modo che l'applicazione della funzione usi implicitamente un interprete e la composizione delle funzioni deriva una nuova descrizione.
Una volta che hai una typeclass per le funzioni, puoi avere derivate le typeclass per tipi speciali di funzioni - nel mio caso, voglio funzioni invertibili.
Ad esempio, le funzioni che applicano offset di interi possono essere rappresentate da un ADT contenente un intero. Applicare queste funzioni significa solo aggiungere l'intero. La composizione viene implementata aggiungendo gli interi incartati. La funzione inversa ha l'intero negato. La funzione di identità si sposta a zero. La funzione costante non può essere fornita perché non esiste una rappresentazione adatta per essa.
Ovviamente non ha bisogno di sillabare le cose come se i valori fossero autentiche funzioni di Haskell, ma una volta che ho avuto l'idea, ho pensato che una libreria del genere doveva già esistere e forse anche usare l'ortografia standard. Ma non riesco a trovare una simile macchina da scrivere nella biblioteca di Haskell.
Ho trovato il modulo Data.Function , ma non esiste una typeclass, solo alcune funzioni comuni disponibili anche da Prelude.
Quindi - perché non esiste una tabella dei tipi per le funzioni? È "solo perché non c'è" o "perché non è così utile come pensi"? O forse c'è un problema fondamentale con l'idea?
Il problema più grande possibile a cui ho pensato finora è che l'applicazione di funzioni su funzioni effettive dovrebbe probabilmente essere creata in modo speciale dal compilatore per evitare un problema di loop - per applicare questa funzione ho bisogno di applicare la funzione di applicazione delle funzioni, e per farlo devo chiamare la funzione application application, e per farlo ...
Più indizi
Codice di esempio per mostrare ciò a cui sto puntando ...
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
-- In my first version, Doable only had the one argument f. This version
-- seemed to be needed to support the UndoableOffset type.
--
-- It seems to work, but it also seems strange. In particular,
-- the composition function - a and b are in the class, but c isn't,
-- yet there's nothing special about c compared with a and b.
class Doable f a b where
fwdApply :: f a b -> a -> b
compDoable :: f b c -> f a b -> f a c
-- In the first version, I only needed a constraint for
-- Doable f a b, but either version makes sense.
class (Doable f a b, Doable f b a) => Undoable f a b where
bwd :: f a b -> f b a
bwdApply :: f a b -> b -> a
bwdApply f b = fwdApply (bwd f) b
-- Original ADT - just making sure I could wrap a pair of functions
-- and there were no really daft mistakes.
data UndoableFn a b = UFN { getFwd :: a -> b, getBwd :: b -> a }
instance Doable UndoableFn a b where
fwdApply = getFwd
compDoable f g = UFN ((getFwd f) . (getFwd g)) ((getBwd g) . (getBwd f))
instance Undoable UndoableFn a b where
bwd f = UFN (getBwd f) (getFwd f)
bwdApply = getBwd
-- Making this one work led to all the extensions. This representation
-- can only represent certain functions. I seem to need the typeclass
-- arguments, but also to need to restrict which cases can happen, hence
-- the GADT. A GADT with only one constructor still seems odd. Perhaps
-- surprisingly, this type isn't just a toy (except that the whole thing's
-- a toy really) - it's one real case I need for the exercise. Still a
-- simple special case though.
data UndoableOffset a b where
UOFF :: Int -> UndoableOffset Int Int
instance Doable UndoableOffset Int Int where
fwdApply (UOFF x) y = y+x
compDoable (UOFF x) (UOFF y) = UOFF (x+y)
instance Undoable UndoableOffset Int Int where
bwdApply (UOFF x) y = y-x
bwd (UOFF x) = UOFF (-x)
-- Some value-constructing functions
-- (-x) isn't shorthand for subtraction - whoops.
undoableAdd :: Int -> UndoableFn Int Int
undoableAdd x = UFN (+x) (\y -> y-x)
undoableMul :: Int -> UndoableFn Int Int
undoableMul x = UFN (*x) (`div` x)
-- With UndoableFn, it's possible to define an invertible function
-- that isn't invertible - to break the laws. To prevent that, need
-- the UFN constructor to be private (and all public ops to preserve
-- the laws). undoableMul is already not always invertible.
validate :: Undoable f a b => Eq a => f a b -> a -> Bool
validate f x = (bwdApply f (fwdApply f x)) == x
-- Validating a multiply-by-zero invertible function shows the flaw
-- in the validate-function plan. Must try harder.
main = do putStrLn . show $ validate (undoableAdd 3) 5
putStrLn . show $ validate (undoableMul 3) 5
--putStrLn . show $ validate (undoableMul 0) 5
fb1 <- return $ UOFF 5
fb2 <- return $ UOFF 7
fb3 <- return $ compDoable fb1 fb2
putStrLn $ "fwdApply fb1 3 = " ++ (show $ fwdApply fb1 3)
putStrLn $ "bwdApply fb1 8 = " ++ (show $ bwdApply fb1 8)
putStrLn $ "fwdApply fb3 2 = " ++ (show $ fwdApply fb3 2)
putStrLn $ "bwdApply fb3 14 = " ++ (show $ bwdApply fb3 14)
L'applicazione prevede un tipo di unificazione in cui i valori unificati non sono uguali, ma sono correlati tramite quelle funzioni invertibili - Logica in stile Prolog ma con a = f(b)
vincoli anziché a = b
. Gran parte della composizione deriverà dall'ottimizzazione di una struttura di unione. La necessità di inversioni dovrebbe essere ovvia.
Se nessun articolo in un set unificato ha un valore esatto, un determinato articolo può essere quantificato solo rispetto a un altro articolo in quel set unificato. Ecco perché non voglio usare funzioni "reali" - calcolare quei valori relativi. Potrei abbandonare l'intero aspetto della funzione e avere solo quantità assolute e relative - probabilmente ho solo bisogno di numeri / vettori e (+)
- ma il mio astronauta nell'architettura interna vuole il suo divertimento.
L'unico modo per spezzare di nuovo i collegamenti è tramite il backtracking e tutto è puro - union-find verrà fatto usando le chiavi in un IntMap
"puntatore". Ho un semplice funzionamento di unione-ricerca, ma poiché non ho ancora aggiunto le funzioni invertibili, non ha senso elencarlo qui.
Motivi per cui non posso usare Applicativo, Monade, Freccia ecc
Le operazioni principali che mi servono per fornire la classe di astrazione sono l'applicazione e la composizione. Sembra familiare, ad esempio Applicative
(<*>)
, Monad
(>>=)
e Arrow
(>>>)
sono tutte funzioni di composizione. Tuttavia, i tipi che implementano l'astrazione della funzione nel mio caso conterranno una struttura di dati che rappresenta una funzione, ma che non è (e non può contenere) una funzione e che può rappresentare solo un insieme limitato di funzioni.
Come ho detto nella spiegazione del codice, a volte posso solo quantificare un elemento rispetto a un altro perché nessun elemento in un cluster "unificato" ha un valore esatto. Voglio essere in grado di derivare una rappresentazione di quella funzione, che in generale sarà la composizione di diverse funzioni fornite (camminando fino a un antenato comune nell'unione / trova l'albero) e di diverse funzioni inverse (tornando all'altra articolo).
Caso semplice - in cui le "funzioni" originali sono limitate alle "funzioni" di offset intero, desidero il risultato composto come una "funzione" di offset intero - aggiungere gli offset dei componenti. Questa è una grande parte del motivo per cui la funzione di composizione deve essere nella classe così come la funzione dell'applicazione.
Questo significa che non posso fornire le operazioni pure
, return
o arr
per i miei tipi, quindi non posso usare Applicative
, Monad
o Arrow
.
Questo non è un fallimento di questi tipi - è una discrepanza di astrazioni. L'astrazione che voglio è di una semplice funzione pura. Non ci sono effetti collaterali, ad esempio, e non è necessario creare una notazione conveniente per il sequenziamento e la composizione di funzioni diverse da un equivalente dello standard (.) Che si applica a tutte le funzioni.
Ho potuto esempio Category
. Sono fiducioso che tutte le mie cose funzionali saranno in grado di fornire un'identità, anche se probabilmente non ne ho bisogno. Ma poiché Category
non supporta l'applicazione, avrei comunque bisogno di una classe derivata per aggiungere tale operazione.
Applicative
sia giusto: richiede che i valori siano racchiusi così come le funzioni, mentre io voglio solo avvolgere le funzioni e le funzioni avvolte sono davvero funzioni, mentre le mie funzioni avvolte normalmente non lo saranno (in il caso più generale, sono AST che descrivono le funzioni). Dove <*>
ha il tipo f (a -> b) -> f a -> f b
, voglio un operatore dell'applicazione con il tipo g a b -> a -> b
dove a
e b
specificare il dominio e il codice della funzione wrapped, ma ciò che è all'interno del wrapper non è (necessariamente) una vera funzione. Su Frecce - forse, darò un'occhiata.