Session-Typen sind ein Versuch, Typ-Level-Beschreibungen zu Netzwerkprotokollen zu geben. Die Idee ist, dass, wenn ein Client einen Wert sendet, der Server bereit sein muss, diesen zu empfangen, und umgekehrt.
Also hier ist ein Typ (mit TypeInType
) beschreibt Sitzungen bestehen aus einer Sequenz von Werten zu senden und Werte zu erhalten.
infixr 5 :!, :?
data Session = Type :! Session
| Type :? Session
| E
a :! s
bedeutet "a
einen Wert vom Typ senden, dann mit dem Protokoll weiter s
". a :? s
bedeutet "empfange einen Wert vom Typ a
, dann fahre mit dem Protokoll s
fort".
So Session
stellt eine (Typ-Ebene) Liste von Aktionen. Unsere monadischen Berechnungen werden sich entlang dieser Liste bewegen und Daten senden und empfangen, wie es der Typ erfordert. Genauer gesagt reduziert eine Berechnung des Typs Chan s t a
die verbleibende Arbeit, die zur Erfüllung des Protokolls von s
bis t
zu leisten ist. Ich werde Chan
mit der indexierten freien Monade bauen, die ich in meiner Antwort auf Ihre andere Frage verwendet habe.
class IFunctor f where
imap :: (a -> b) -> f i j a -> f i j b
class IFunctor m => IMonad m where
ireturn :: a -> m i i a
(>>>=) :: m i j a -> (a -> m j k b) -> m i k b
data IFree f i j a where
IReturn :: a -> IFree f i i a
IFree :: f i j (IFree f j k a) -> IFree f i k a
instance IFunctor f => IFunctor (IFree f) where
imap f (IReturn x) = IReturn (f x)
imap f (IFree fx) = IFree (imap (imap f) fx)
instance IFunctor f => IMonad (IFree f) where
ireturn = IReturn
IReturn x >>>= f = f x
IFree fx >>>= f = IFree (imap (>>>= f) fx)
Unsere Basis Aktionen im Chan
Monade einfach Werte senden und empfangen.
data ChanF s t r where
Send :: a -> r -> ChanF (a :! s) s r
Recv :: (a -> r) -> ChanF (a :? s) s r
instance IFunctor ChanF where
imap f (Send x r) = Send x (f r)
imap f (Recv r) = Recv (fmap f r)
send :: a -> Chan (a :! s) s()
send x = IFree (Send x (IReturn()))
recv :: Chan (a :? s) s a
recv = IFree (Recv IReturn)
type Chan = IFree ChanF
type Chan' s = Chan s E -- a "complete" Chan
send
nimmt den aktuellen Status der Sitzung a :! s
-s
, die Erfüllung der Verpflichtung ein a
zu senden. Analog transformiert recv
eine Sitzung von a :? s
zu s
.
Hier ist der lustige Teil. Wenn ein Ende des Protokolls einen Wert sendet, muss das andere Ende bereit sein, es zu empfangen, und umgekehrt. Dies führt zu der Idee einer Dual Sitzung Typ:
type family Dual s where
Dual (a :! s) = a :? Dual s
Dual (a :? s) = a :! Dual s
Dual E = E
In insgesamt Sprache Dual (Dual s) = s
beweisbar wäre, aber leider Haskell ist nicht insgesamt.
Sie können ein Paar von Kanälen verbinden, wenn ihre Typen dual sind. (Ich denke, man dies eine im Speicher nennen würde Simulation einen Client und einen Server zu verbinden.)
connect :: Chan' s a -> Chan' (Dual s) b -> (a, b)
connect (IReturn x) (IReturn y) = (x, y)
connect (IReturn _) (IFree y) = case y of {}
connect (IFree (Send x r)) (IFree (Recv f)) = connect r (f x)
connect (IFree (Recv f)) (IFree (Send y r)) = connect (f y) r
Zum Beispiel, hier ist ein Protokoll für einen Server, der prüft, ob eine Zahl größer als 3 ist.Der Server wartet auf den Empfang einer Int
, sendet eine Bool
zurück und beendet dann die Berechnung.
type MyProtocol = Int :? Bool :! E
server :: Chan' MyProtocol()
server = do -- using RebindableSyntax
x <- recv
send (x > 3)
client :: Chan' (Dual MyProtocol) Bool
client = do
send 5
recv
Und um es zu testen:
ghci> connect server client
((),True)
Session-Typen sind ein Gebiet der aktiven Forschung. Diese spezielle Implementierung ist für sehr einfache Protokolle geeignet, aber Sie können keine Protokolle beschreiben, bei denen der Typ der Daten, die über die Leitung gesendet werden, vom Zustand des Protokolls abhängt. Dafür braucht man Überraschungsüberraschung, abhängige Typen. Eine kurze Demonstration des Stands der Technik von Sitzungstypen finden Sie unter this talk by Brady.
z.B. hier https://www.youtube.com/watch?v=60gUaOuzZsE, machte es fast zu einer Bibliothek, war aber die Komplexität nicht wert. – phadej
Ich würde erwarten, dass Anwendungen von indizierten Monaden eher in allen Sprachen wie Agda und Coq gefunden werden. In Haskell können Sie Teilfunktionen verwenden, von denen Sie wissen, dass sie im Kontext vollständig sind. in diesen Sprachen kann man nicht. * Wenn du eine indizierte Monade brauchst, wird das einen großen Teil dazu beitragen, dich davon zu überzeugen, dass es sich lohnt, mit einem zu arbeiten. – dfeuer
@dfeuer Ich kann mir verschiedene Anwendungen dieser Technik in meinem aktuellen Code vorstellen, wie zum Beispiel korrekte Statusübergänge auf Protokollebene. Ich stimme zu, dass Sie wissen können, dass Ihre Funktionen in einem Kontext vollständig sind, aber wenn Sie für die langfristige Codierung, insbes. In einer Teamumgebung sind die expliziten Trümpfe implizit, so dass ich lieber einen Mechanismus hätte, der den Menschen hilft, sich dieser Kontexthypothesen bewusst zu sein. Ein besonderer Bereich, der mir in den Sinn kommt, ist die Systemkonfiguration, wie es der Propeller tut. – insitu