Serialisierung


... [ Programmiersprachen und Sprachsysteme ] ... [ Cloud Haskell ] ... [ << Cloud Haskell API ] ... [ Beispiel Anwendung >> ] ...

In Cloud Haskell


Das Thema rund um die Serialisierung ist eines der Hauptthemengebiete von Cloud Haskell. In anderen Sprachen wird Serialisierung häufig im Verborgenen etwa durch die Virtual Machine übernommen und dem Entwickler somit verschleiert. Dies hat zum Nachteil, dass dem Entwickler das Kostenmodell der jeweiligen Serialisierung nicht ersichtlich wird. Außerdem lassen sich dadurch, einige Datentypen nicht wie im Falle von ReceivePort, explizit als nicht serialisierbar deklarieren. Da diese Entscheidung jedoch dem Entwickler frei überlassen werden sollte, wird die Verantwortung der Serialisierung in Cloud Haskell dem Entwickler durch Typklassen überlassen.
Der konkrete Ablauf der Serialisierung von Funktionen verhält sich dabei etwa so, dass in geschlossene Funktionen und Funktionen mit freien Variablen unterschieden wird. Geschlossene Funktionen, oder auch "Pure Functions" genannt, lassen sich relativ leicht serialisieren, da es unter der Prämisse, dass alle Nodes sich die gleiche Codebasis teilen, ausreicht, die Code Adresse im Speicher zu übermitteln. Solche geschlossenen Funktionen werden in Cloud Haskell als static deklariert und weisen auf die vereinfachte Serialisierung hin. Damit diese Art der Serialisierung jedoch vorgenommen werden könnte, bedarf es einer neuen Built-in Typ Erweiterung, sowie entsprechende GHC Erweiterungen, welche nach jetzigem Stand noch nicht implementiert wurden.

1
2
f x = send c (\y -> y + 1)
=> f x = send c (static (\y -> y+1))


Bei Funktionen mit freien Variablen bedarf es einer Umtransformierung der Funktion zu einer static Funktion mit einem entsprechendem expliziten Environment. Der Ablauf solch einer Transformation wird im folgenden Beispiel demonstriert:

1
2
3
4
f x = send c (\y -> y + x + 1)
=> f x = send c (static (\x y -> y + x + 1), x)

data Closure a = Closure (Static (ByteString -> a)) ByteString


Wie in Zeile 1 zu sehen ist, ist x die freie Variable, welche es nun gilt, zu einer gebundenen Variable zu transformieren. Dieser Schritt wird nun vorgenommen, indem die eigentliche Funktion gekapselt und durch die nun expliziten Parameter x erweitert wird (Zeile 2). Der tatsächliche Kontext aus x wird dabei als Environment verpackt und in Kombination mit der nun static Funktion zu einem sogenannten Closure transformiert. Dieses Closure kann nun in dieser Form serialisiert und an andere Prozesse gesendet werden. Damit eine Funktion nun aber solch einen Kontext im Nachhinein binden kann, bedarf es eines weiteren Transformationsschritts, welcher diesmal aber vom Entwickler getätigt werden muss. Falls eine Funktion in einer gecurried Version besteht, muss diese zu einer uncurried Version umgeschrieben werden. Aus 'workerProc :: String -> Int -> Process ()' wird dann z. B. 'workerProc :: (String, Int) -> Process ()'. Anschließend ist es möglich den serialisierten Kontext '(String, Int)' an die Funktion zu binden. Um die Entwicklung mit Cloud Haskell zu vereinfachen, bestehen etwa für die zuvor beschriebene Closure Konversion ebenfalls Template Haskell Funktionen wie z. B. mkClosure. Diese Funktion arbeitet dabei lediglich auf dem Namen der Funktion, welche in der RemoteTable definiert wurde und dem Environment. Lediglich diese beiden Dinge werden dann im Falle der Serialisierung übertragen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
...

work :: (String, Int) -> Process ()
work = ...

remotable ['work]

spawnRemote :: NodeId -> Process ()
spawnRemote nodeId = do
    _ <- spawn nId ($(mkClosure 'work) ("Question", 42))
    return ()
...
newLocalNode transport $ __remoteTable initRemoteTable

...


Betrachtet man die zuvor beschriebene Tatsachen, dass lediglich der Funktionsname und das Environment übertragen wird, ließen sich, wie im folgendem Beispiel gezeigt wird, unterschiedliche Funktionen mit gleichem Namen und Signatur auf unterschiedlichen Prozessen ausführen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
...

pingClient :: ProcessId -> Process ()
pingClient serverPid = do
    self <- getSelfPid

    send serverPid (Ping self)
    send serverPid (Ping self)
    send serverPid (Ping self)
    
    Pong from <- expect
    liftIO . putStrLn $ "OTHER CLIENT IMPLEMENTATION - got pong from: " ++ show from
    liftIO $ threadDelay (1*1000000)
    pingClient serverPid

remotable ['pingClient]

...


Es handelt sich hier im Vergleich zu den anderen PingPong Beispielen um eine andere Funktion, jedoch mit gleicher Signatur (Zeile 3 bis 14). Es liegt also in der Verantwortung des Entwicklers, sicherzustellen, dass auf allen Nodes auch tatsächlich die gleiche Codebasis besteht.


... [ Programmiersprachen und Sprachsysteme ] ... [ Cloud Haskell ] ... [ << Cloud Haskell API ] ... [ Beispiel Anwendung >> ] ...
generated by schmidt-doku-generator (GitHub)