In einem Lernproblem, mit dem ich herumgespielt habe, wurde mir klar, dass ich eine Typenklasse für Funktionen mit Operationen zum Anwenden, Komponieren usw. benötige. Gründe ...
Es kann praktisch sein, eine Darstellung einer Funktion so zu behandeln, als wäre sie die Funktion selbst, sodass bei der impliziten Anwendung der Funktion ein Interpreter verwendet wird und beim Erstellen von Funktionen eine neue Beschreibung abgeleitet wird.
Sobald Sie eine Typklasse für Funktionen haben, können Sie Typklassen für spezielle Arten von Funktionen ableiten - in meinem Fall möchte ich invertierbare Funktionen.
Zum Beispiel könnten Funktionen, die Ganzzahl-Offsets anwenden, durch ein ADT dargestellt werden, das eine Ganzzahl enthält. Das Anwenden dieser Funktionen bedeutet lediglich das Hinzufügen der Ganzzahl. Die Komposition wird durch Hinzufügen der umbrochenen ganzen Zahlen implementiert. Bei der Umkehrfunktion ist die Ganzzahl negiert. Die Identitätsfunktion umschließt Null. Die konstante Funktion kann nicht bereitgestellt werden, da keine geeignete Darstellung dafür vorhanden ist.
Natürlich muss es nicht so geschrieben werden, als ob es sich bei den Werten um echte Haskell-Funktionen handelt, aber als ich auf die Idee kam, dachte ich, dass eine solche Bibliothek bereits existieren muss und vielleicht sogar die Standardschreibweisen verwendet. Aber ich kann eine solche Typenklasse nicht in der Haskell-Bibliothek finden.
Ich habe das Modul Data.Function gefunden , aber es gibt keine Typenklasse - nur einige allgemeine Funktionen, die auch im Prelude verfügbar sind.
Also - warum gibt es keine Typenklasse für Funktionen? Ist es "nur weil es keine gibt" oder "weil es nicht so nützlich ist, wie du denkst"? Oder gibt es ein grundsätzliches Problem mit der Idee?
Das größtmögliche Problem, an das ich bisher gedacht habe, ist, dass die Funktionsanwendung für tatsächliche Funktionen vom Compiler möglicherweise speziell behandelt werden muss, um ein Schleifenproblem zu vermeiden. Um diese Funktion anzuwenden, muss die Funktion application function angewendet werden. und um das zu tun, muss ich die Funktion application function aufrufen, und um das zu tun ...
Weitere Hinweise
Beispielcode, um zu zeigen, was ich anstrebe ...
{-# 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)
Die Anwendung beinhaltet eine Art Vereinheitlichung, bei der vereinheitlichte Werte nicht gleich sind, sondern über diese invertierbaren Funktionen in Beziehung gesetzt werden - Logik im Prolog-Stil, jedoch mit a = f(b)
Einschränkungen anstatt a = b
. Der größte Teil der Komposition ergibt sich aus der Optimierung einer Union-Find-Struktur. Die Notwendigkeit von Inversen sollte offensichtlich sein.
Wenn kein Element in einer einheitlichen Menge einen genauen Wert hat, kann ein bestimmtes Element nur relativ zu einem anderen Element in dieser einheitlichen Menge quantifiziert werden. Deshalb möchte ich keine "echten" Funktionen verwenden - diese relativen Werte berechnen. Ich könnte den gesamten Funktionsaspekt weglassen und nur absolute und relative Größen haben - ich benötige wahrscheinlich nur Zahlen / Vektoren und (+)
- aber mein Astronaut in der inneren Architektur möchte, dass er Spaß hat.
Der einzige Weg, wie ich die Links wieder auseinander bringe, ist das Backtracking, und alles ist rein - das Auffinden von Gewerkschaften erfolgt mit Schlüsseln in einem IntMap
als "Zeiger". Ich habe eine einfache Gewerkschaftsfunktion, aber da ich die umkehrbaren Funktionen noch nicht hinzugefügt habe, hat es keinen Sinn, sie hier aufzulisten.
Gründe, warum ich Applicative, Monad, Arrow usw. Nicht verwenden kann
Die Hauptoperationen, für die ich die Funktionsabstraktionsklasse benötige, sind Anwendung und Komposition. Das klingt vertraut - zum Beispiel der Applicative
(<*>)
, Monad
(>>=)
und Arrow
(>>>)
sind all Zusammensetzung Funktionen. Die Typen, die in meinem Fall die Funktionsabstraktion implementieren, enthalten jedoch eine Datenstruktur, die eine Funktion darstellt, die jedoch keine Funktion ist (und nicht enthalten kann) und die nur einen begrenzten Satz von Funktionen darstellen kann.
Wie ich in der Erklärung des Codes erwähnt habe, kann ich manchmal nur ein Element relativ zu einem anderen quantifizieren, da kein Element in einem "einheitlichen" Cluster einen genauen Wert hat. Ich möchte in der Lage sein, eine Darstellung dieser Funktion abzuleiten, bei der es sich im Allgemeinen um die Zusammensetzung mehrerer bereitgestellter Funktionen (Heraufgehen auf einen gemeinsamen Vorfahren im Vereinigungs- / Suchbaum) und mehrerer umgekehrter Funktionen (Herabgehen auf den anderen) handelt Artikel).
Einfacher Fall - wo die ursprünglichen "Funktionen" auf Ganzzahloffset "Funktionen" beschränkt sind, möchte ich das zusammengesetzte Ergebnis als Ganzzahloffset "Funktion" - fügen Sie die Komponentenoffsets hinzu. Dies ist ein wesentlicher Grund, warum die Kompositionsfunktion sowohl in der Klasse als auch in der Anwendungsfunktion enthalten sein muss.
Dieses Mittel kann ich die Operationen nicht zur Verfügung stellen pure
, return
oder arr
für meine Art, so dass ich nicht verwenden können Applicative
, Monad
oder Arrow
.
Dies ist kein Misserfolg dieser Art - es ist ein Missverhältnis von Abstraktionen. Die Abstraktion, die ich will, ist eine einfache reine Funktion. Beispielsweise gibt es keine Nebenwirkungen, und es ist nicht erforderlich, eine praktische Notation für die Sequenzierung und Komposition der Funktionen zu erstellen, die nicht dem Standard (.) Entspricht, der für alle Funktionen gilt.
Ich könnte zum Beispiel Category
. Ich bin zuversichtlich, dass alle meine Funktionen eine Identität liefern können, obwohl ich sie wahrscheinlich nicht brauche. Da Category
die Anwendung jedoch nicht unterstützt wird, benötige ich trotzdem eine abgeleitete Klasse, um diese Operation hinzuzufügen.
Applicative
das ganz richtig ist - es erfordert, dass die Werte sowie die Funktionen umgebrochen werden, während ich nur die Funktionen umbrechen möchte und die umgebrochenen Funktionen wirklich Funktionen sind, während meine umgebrochenen Funktionen normalerweise nicht (in) sind Im allgemeinsten Fall handelt es sich um ASTs, die Funktionen beschreiben. Wo <*>
hat Typ f (a -> b) -> f a -> f b
, ich möchte einen Anwendungsoperator mit Typ g a b -> a -> b
wo a
und b
spezifizieren die Domäne und Codomäne der umschlossenen Funktion, aber was in der Hülle ist, ist nicht (notwendigerweise) eine echte Funktion. Auf Pfeile - möglicherweise werde ich einen Blick darauf werfen.