#Polycephaly Or, multiplying instance heads
#!/bin/bash | |
# Prompt before pushing to master. If it's a force push or delete, bail | |
# Don't be overly reliant on this, though; I'm not sure what the corner cases are. | |
# As usual, put this is .git/hooks directory, and make it executable with "chmod 755 pre-push" | |
# Requires git >= 1.8.3 | |
PROT_BRANCH='master' | |
UNSAFE='force|delete|\-f' | |
CONFIRM_MSG="Pushing to master - are you absolutely sure you want to continue?" |
{-# LANGUAGE DataKinds #-} | |
{-# LANGUAGE DeriveDataTypeable #-} | |
{-# LANGUAGE PolyKinds #-} | |
{-# LANGUAGE TypeOperators #-} | |
{-# LANGUAGE ScopedTypeVariables #-} | |
import Data.Typeable | |
import Data.Data | |
import Data.Proxy | |
import GHC.TypeLits |
class PushReaderT a where | |
type PushReaderSubType a | |
unPushReaderT :: String -> PushReaderSubType a -> a | |
instance (PushReaderT b) => PushReaderT (a -> b) where | |
type PushReaderSubType (a -> b) = a -> PushReaderSubType b | |
unPushReaderT str f = unPushReaderT str . f | |
instance (PushReaderT a, PushReaderT b) => PushReaderT (a :<|> b) where |
data GetWithCookie (a :: *) | |
type Short a = EitherT (Int,String) IO a | |
instance HasServer (GetWithCookie a) where | |
type Server (GetWithCookie a) = EitherT (Int,String) IO (a, ByteString) -- the ByteString is the cookie | |
route Proxy action request respond = do | |
e <- runEitherT (action) | |
respond . succeedWith $ case e of |
-- completely untested | |
type MyEndpoint = "something" :> Get Int | |
class GetMethod a where | |
getMethod :: Proxy a -> String | |
instance GetMethod (Get x) where | |
getMethod _ = "GET" |
performRequest' :: Method -> Req -> (Int -> Bool) -> BaseUrl -> EitherT ServantError IO (Int, ByteString, MediaType) | |
performRequest' reqMethod req isWantedStatus reqHost = do | |
partialRequest <- liftIO $ reqToRequest req reqHost | |
let request = partialRequest { Client.method = reqMethod | |
, checkStatus = \ _status _headers _cookies -> Nothing | |
} | |
eResponse <- liftIO $ __withGlobalManager $ \ manager -> |
At first we had "Get a" [note that I'm going to be pretty loose about distinguishing type variables in Haskell and in the metalanguage]. OK, the "Get" part should be static - each endpoint can only "be" one method. The 'a' type can be anything, but in the description (i.e., the API type) it is static and that's fine, since these are algebraic data types, and can themselves represent sums and products of other things (in comparison, the method "place" for the endpoints is just a sum.)
So far we pretended status codes didn't need to be represented, because they could be statically determined by the method type. But of course that's bad HTTP practice - an empty body should probably cause a 204 No Content, for instance. So what we did was defined instances that overlapped on the method argument ("a" in "Get a"). Kind of ugly.
Then came content types. That turned out okay, because generally we're fine considering the acceptable content-types for and endpoint to be static. It would be more general to consider
-- first a separate class to interpret the access/permission DSL | |
class IsAccess a where | |
getAccess :: Proxy a -> Id -> Bool | |
-- ^ probably something quite different here. The two main options I see generally | |
-- are evaluating to some normal form (like AccessType) and not requiring Id, or | |
-- having Id and then directly evaluating to Bool | |
-- If you want to be allowed to require other values depending on the type (which I | |
-- think you do) you'd need an associated type synonym). | |
instance (IsAccess a, IsAccess b) => IsAccess (a :|| b) where |
{-# LANGUAGE ConstraintKinds #-} | |
module Main where | |
import Data.Constraint | |
show' :: (Maybe (Dict (Show a))) -> a -> String | |
show' (Just Dict) x = show x | |
show' Nothing _ = "<<not showable>>" | |
t1 = show' (Just Dict) 5 |