Skip to content

Instantly share code, notes, and snippets.

@LightAndLight
Created March 11, 2020 06:43
Show Gist options
  • Save LightAndLight/f232637f5dc279b8aa7bc926640b2ae6 to your computer and use it in GitHub Desktop.
Save LightAndLight/f232637f5dc279b8aa7bc926640b2ae6 to your computer and use it in GitHub Desktop.
Sketch of well-typed RPC
{-# language DataKinds, GADTs, ScopedTypeVariables #-}
{-# language RankNTypes, TypeApplications #-}
{-# language LambdaCase #-}
{-# language ImplicitParams #-}
module Server where
import Data.Bifunctor (first)
import Data.ByteString (ByteString)
import Data.Proxy
import Data.Serialize
import Data.Serialize.Text
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Type.Equality
import GHC.Generics
import GHC.TypeLits
data Registered a b where
Registered ::
( Serialize a
, Serialize b
) =>
(a -> b) ->
Registered a b
newtype Registry name where
Registry :: (forall a b. name a b -> Registered a b) -> Registry name
withEntry ::
name a b ->
Registry name ->
((Serialize a, Serialize b) => (a -> b) -> r) ->
r
withEntry n (Registry lookup) k = case lookup n of; Registered f -> k f
data RPCError where
DecodeError :: String -> RPCError
decodeAndRunCall ::
GSerialize2 name =>
Registry name ->
(ByteString, ByteString) ->
Either RPCError ByteString
decodeAndRunCall reg (bsName, bsArg) = do
Some2 (name :: name a b) <- first DecodeError $ runGet gget2 bsName
withEntry name reg $ \f -> do
a :: a <- first DecodeError $ decode bsArg
let b = f a
pure $ encode b
encodeCall ::
(GSerialize2 name, Serialize a) =>
Registry name ->
name a b ->
a ->
(ByteString, ByteString)
encodeCall reg name a =
withEntry name reg $ \_ -> (runPut $ gput2 name, encode a)
data MyName a b where
MyAdd :: MyName (Int, Int) Int
MyMult :: MyName (Int, Int) Int
MyNot :: MyName Bool Bool
data Some2 f where
Some2 :: f a b -> Some2 f
class GSerialize2 f where
gget2 :: Get (Some2 f)
gput2 :: Putter (f a b)
instance GSerialize2 MyName where
gget2 = do
tag <- get @Word
case tag of
0 -> pure $ Some2 MyAdd
1 -> pure $ Some2 MyMult
2 -> pure $ Some2 MyNot
_ -> fail $ "Invalid tag: " <> show tag
gput2 n =
case n of
MyAdd -> put @Word 0
MyMult -> put @Word 1
MyNot -> put @Word 2
myRegistry :: Registry MyName
myRegistry =
Registry $
\case
MyAdd -> Registered $ uncurry (+)
MyMult -> Registered $ uncurry (*)
MyNot -> Registered not
testEncodeCall :: (ByteString, ByteString)
testEncodeCall =
encodeCall myRegistry MyAdd (1, 2)
data Connection
call ::
( ?conn :: Connection, ?reg :: Registry name
, GSerialize2 name, Serialize a, Serialize b
) =>
name a b ->
a ->
IO b
call name a = do
res <- _ $ encodeCall ?reg name a
either error pure (decode =<< res)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment