Perché non esiste una tabella dei tipi per le funzioni?


17

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 ...

  1. 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.

  2. 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, returno arrper i miei tipi, quindi non posso usare Applicative, Monado 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é Categorynon supporta l'applicazione, avrei comunque bisogno di una classe derivata per aggiungere tale operazione.


2
Chiamami pazzo, ma quando penso a una tabella di tipo come quella che stai descrivendo, è per l'applicazione e la composizione, ecc., Penso ai funzioni applicative, quali sono le funzioni. Forse è questa la classe di tipo a cui stai pensando?
Jimmy Hoffa,

1
Non credo Applicativesia 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 -> bdove ae bspecificare 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.
Steve314,

3
se vuoi l'inverso non implicherebbe un gruppo?
jk.

1
@jk. ottimo punto, rendendosi conto che ci sono molte cose da leggere su inversioni di funzioni che possono portare l'OP a trovare quello che sta cercando. Ecco alcune interessanti letture fuori mano sull'argomento. Ma un inverso di google per la funzione haskell offre molti contenuti di lettura curiosi. Forse vuole solo Data.Group
Jimmy Hoffa il

2
@ Steve314 Ho pensato che le funzioni con composizione fossero una categoria monoidale. Sono un monoide se dominio e codice sono sempre gli stessi.
Tim Seguine,

Risposte:


14

Bene, non conosco idee elaborate che si propongono come cose "funzionali". Ma ce ne sono molti che si avvicinano

categorie

Se hai un semplice concetto di funzione che ha identità e composizione, allora hai una categoria.

class Category c where
  id :: c a a
  (.) :: c b c -> c a b -> c a c

Lo svantaggio è che non è possibile creare un bel esempio categoria con una serie di oggetti ( a, b, e c). È possibile creare una classe di categoria personalizzata suppongo.

frecce

Se le tue funzioni hanno una nozione di prodotti e possono iniettare funzioni arbitrarie, allora le frecce sono per te

 class Arrow a where
   arr :: (b -> c) -> a b c
   first :: a b c -> a (b, d) (c, d)
   second :: a b c -> a (d, b) (d, c)

ArrowApply ha una nozione di applicazione che sembra importante per quello che vuoi.

applicativi

I candidati hanno la tua idea di applicazione, li ho usati in un AST per rappresentare l'applicazione di funzioni.

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f b -> f c

Ci sono molte altre idee. Ma un tema comune è quello di costruire una struttura di dati che rappresenti la tua funzione e di passarla a una funzione di interpretazione.

Questo vale anche per quante monadi libere funzionano. Suggerirei di dare un'occhiata a questi se ti senti coraggioso, sono uno strumento potente per le cose che stai suggerendo e essenzialmente ti permettono di costruire una struttura dati usando la donotazione e poi comprimerla in un calcolo con effetti collaterali con diverse funzioni . Ma il bello è che queste funzioni funzionano solo sulla struttura dei dati e non sono davvero consapevoli di come hai fatto tutto. Questo è ciò che suggerirei per il tuo esempio di interprete.


La categoria sembra mancare di applicazione - ($). Le frecce sembrano un enorme sovraccarico a prima vista, ma ArrowApplysembra comunque promettente - fintanto che non ho bisogno di fornire qualcosa che non posso, potrebbe essere OK. +1 per il momento, con più controlli da fare.
Steve314,

3
@ Steve314 Le categorie mancano di applicazione, ma le monadi mancano di un modo universale per eseguirle, non significa che non siano utili
Daniel Gratzer

C'è un motivo comune per cui non posso usare Applicativeo Arrow(o Monad) - Non riesco a racchiudere una normale funzione (in generale) perché i valori del mio tipo rappresentano una funzione ma sono rappresentati da dati e non supporteranno funzioni arbitrarie se c'era un modo per tradurre. Ciò significa che non posso fornire pure, arro returnper esempio. A proposito: quelle classi sono utili ma non posso usarle per questo scopo particolare. Arrownon è "enorme inganno" - è stata una falsa impressione dall'ultima volta che ho provato a leggere il giornale, quando non ero pronto a capirlo.
Steve314,

@ Steve314 L'idea di fornire un'interfaccia monade per creare dati è ciò per cui vengono utilizzate le monadi gratuite, dai un'occhiata
Daniel Gratzer,

Ho visto il video da Haskell Exchange 2013 - Andres Löh lo spiega sicuramente bene, anche se probabilmente devo ancora guardarlo di nuovo, giocare con la tecnica, ecc. Non sono sicuro che qui sia necessario. Il mio obiettivo è quello di avere l'astrazione di una funzione usando una rappresentazione che non è una funzione (ma che ha una funzione di interprete). Non ho bisogno di un'astrazione per gli effetti collaterali e non ho bisogno di una notazione chiara per le operazioni di sequenziamento. Una volta utilizzata questa funzione, le applicazioni e le composizioni verranno eseguite una alla volta all'interno di un algoritmo in un'altra libreria.
Steve314,

2

Come hai sottolineato, il problema principale con l'utilizzo di Applicative qui è che non esiste una definizione ragionevole per pure. Quindi, è Applystato inventato. Almeno, questa è la mia comprensione.

Sfortunatamente, non ho esempi a portata di mano di esempi Applyche non lo sono Applicative. Si sostiene che ciò sia vero IntMap, ma non ho idea del perché. Allo stesso modo, non so se il tuo esempio - numeri interi offset - ammetta Applyun'istanza.


questo sembra più un commento, vedi Come rispondere
moscerino

Scusa. Questa è più o meno la mia prima risposta in assoluto.
user185657

Come mi consigli di migliorare la risposta?
user185657

prendi in considerazione la modifica per aiutare i lettori a vedere come la tua risposta affronta la domanda posta, "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? "
moscerino del

1
Spero che sia meglio
user185657

1

Oltre al citato Category, Arrowe Applicative:

Ho anche scoperto Data.Lambdada Conal Elliott:

Alcune classi simili a funzioni, con costruzione simile a lambda

Sembra interessante, ovviamente, ma difficile da capire senza esempi ...

Esempi

Alcuni esempi possono essere trovati nella pagina wiki sui valori tangibili (TV) che sembrano essere una delle cose che hanno causato la creazione della TypeComposebiblioteca; vedere Ingressi e uscite con valori funzionali .

L'idea della biblioteca TV è di mostrare i valori di Haskell (comprese le funzioni) in modo tangibile.

Per seguire la regola StackOverflow sul non pubblicare bare solo, copio alcuni bit sottostanti che dovrebbero dare l'idea di queste cose:

Il primo esempio dice:

apples, bananas :: CInput Int
apples  = iTitle "apples"  defaultIn
bananas = iTitle "bananas" defaultIn

shoppingO :: COutput (Int -> Int -> Int)
shoppingO = oTitle "shopping list" $
            oLambda apples (oLambda bananas total)

shopping :: CTV (Int -> Int -> Int)
shopping = tv shoppingO (+)

che fornisce quando eseguito come runIO shopping(vedere qui per ulteriori commenti, GUI e altri esempi):

shopping list: apples: 8
bananas: 5
total: 13

come risponde a questa domanda? vedi Come rispondere
moscerino

@gnat Ho pensato che le definizioni in Data.Lambdadanno classi per cose simili a funzioni (che è stato chiesto) ... Non ero sicuro di come usare queste cose. L'ho esplorato un po '. Probabilmente, tuttavia, non forniscono un'astrazione per l'applicazione di funzioni.
imz - Ivan Zakharyaschev,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.