Changing the web-root (or path-prefix) at runtime via servant? - haskell

I need to give the ability to change the web-root (or path-prefix) of my API via CLI arguments.
If my server exposes the following API paths...
/enqueue
/run
/cancel
...at startup it should be possible to change them to the following by passing a CLI switch --web-root=/admin:
/admin/enqueue
/admin/run
/admin/cancel
The question is not related to parsing the command-line, which is a solved problem via optparse-applicative. It's about any in-built way in servant, AT RUNTIME, to (a) change the web-root of the server, and (b) make the corresponding change in various safe-links functions (generated via allFieldLinks').

Servant provides no straightforward facility for doing this, and the internals of Servant.Link have been zealously overprotected (an unfortunately common problem with Haskell packages) so as to make it unnecessarily difficult to implement on the link side.
You can mount a servant API under a runtime-specified base path using the usual methods of specifying types at runtime. However, getting safe links to automatically incorporate the base path seems close to impossible. If you're satisfied with fixing up the links after the fact, then the following might work.
Given that you're using allFieldLinks', you're probably using the generic interface, so suppose you have a service:
data HelloService route = HelloService
{ hello :: route :- "hello" :> Get '[PlainText] Text
, world :: route :- "world" :> Get '[PlainText] Text
} deriving (Generic)
helloServer :: HelloService AsServer
helloServer = HelloService
{ hello = return $ "Goto \"localhost:3000/" <> toUrlPiece (world asLink) <> "\""
, world = return "Hello, world!"
} where asLink = allFieldLinks
with the usual boring way of serving it at the root:
main = run 3000 $ genericServe helloServer
If you wanted to serve this off a compile-time base path (e.g., /admin) without modifying the service definition, you could rewrite main as:
main = run 3000 $ serve (Proxy #("admin" :> ToServant HelloService AsApi))
(genericServer helloServer)
To specify the base path component "admin" at runtime, you can define and case-match on an existential symbol:
main = do
let base = "admin"
case someSymbolVal base of
SomeSymbol (_ :: Proxy base) ->
run 3000 $ serve (Proxy #(base :> ToServant HelloService AsApi))
(genericServer helloServer)
This only allows one component in the base path, but you can generalize to a multiple-component base with:
serveUnder :: forall service. HasServer service '[]
=> [String] -> Proxy service -> Server service -> Application
serveUnder [] p s = serve p s
serveUnder (x:xs) _ s = case someSymbolVal x of
SomeSymbol (_ :: Proxy x) -> serveUnder xs (Proxy #(x :> service)) s
main :: IO ()
main = do
let base = ["foo", "bar"] -- mount under /foo/bar
run 3000 $ serveUnder (reverse base)
(genericApi (Proxy #HelloService))
(genericServer helloServer)
If you try this out and visit http://localhost:3000/foo/bar/hello, you'll see that the allFieldLinks doesn't reflect the new mount point. If Servant.Links exposed more internals, this would be trivial to fix. Unfortunately, as it is, the only reasonable way to address this is to pass some form of the runtime path into helloServer and have it fix the safe links as part of the rendering.
The resulting full program would look something like this:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE KindSignatures #-}
module HelloService where
import Data.Text (Text)
import qualified Data.Text as T
import Servant
import Servant.API.Generic
import Servant.Server.Generic
import Network.URI
import Network.Wai.Handler.Warp
import GHC.TypeLits
data HelloService route = HelloService
{ hello :: route :- "hello" :> Get '[PlainText] Text
, world :: route :- "world" :> Get '[PlainText] Text
} deriving (Generic)
helloServer :: Text -> HelloService AsServer
helloServer webroot = HelloService
{ hello = return $ "Goto \"localhost:3000/" <> renderLink (world asLink) <> "\""
, world = return "Hello, world!"
} where asLink = allFieldLinks
renderLink l = webroot <> toUrlPiece l
serveUnder :: forall service. HasServer service '[]
=> [String] -> Proxy service -> Server service -> Application
serveUnder [] p s = serve p s
serveUnder (x:xs) _ s = case someSymbolVal x of
SomeSymbol (_ :: Proxy x) -> serveUnder xs (Proxy #(x :> service)) s
main :: IO ()
main = do
let base = ["foo", "bar"] -- mount under /foo/bar
webroot = "http://localhost:3000/" <> T.intercalate "/" (map escaped base) <> "/"
escaped = T.pack . escapeURIString isUnreserved
run 3000 $ serveUnder (reverse base)
(genericApi (Proxy #HelloService))
(genericServer (helloServer webroot))

Related

Filter the parts of a Request Path which match against a Static Segment in Servant

Supposing I'm running a Servant webserver, with two endpoints, with a type looking like this:
type BookAPI =
"books" :> Get '[JSON] (Map Text Text)
:<|> "book" :> Capture "Name" Text :> ReqBody '[JSON] (Text) :> Post '[JSON] (Text)
λ:T.putStrLn $ layout (Proxy :: Proxy BookAPI)
/
├─ book/
│ └─ <capture>/
│ └─•
└─ books/
└─•
I might want to use something like Network.Wai.Middleware.Prometheus's instrumentHandlerValue to generate a Prometheus metric that fire's every time this API is called, with a handler value set to the path of the request.
However, if I do something like the following:
prometheusMiddlware = instrumentHandlerValue (T.intercalate "\\" . pathInfo)
This is bad, because different requests to the book/<Name> endpoint, such as book/great-expectations and book/vanity-fair result in different labels, this is fine if the number of books is small, but if it's very large then the amount of data used by these metrics is very big, and either my service falls over, or my monitoring bill becomes very large.
I'd quite like a function, that took a Servant API, and a Wai Request, and if it matched, returned a list of segments in a form that was the same for each endpoint.
That is requests to /books would return Just ["books"], requests to /book/little-dorrit would return Just ["book", "Name"], and requests to /films would return Nothing.
I can kind of see how you might go about writing this by pattern matching on Router' from Servant.Server.Internal.Router, but it's not clear to me that relying on an internal package in order to do this is a good idea.
Is there a better way?
The pathInfo function returns all the path segments for a Request. Perhaps we could define a typeclass that, given a Servant API, produced a "parser" for the list of segments, whose result would be a formatted version of the list.
The parser type could be something like:
import Data.Text
import Control.Monad.State.Strict
import Control.Applicative
type PathParser = StateT ([Text],[Text]) Maybe ()
Where the first [Text] in the state are the path segments yet to be parsed, and the second are the formatted path segments we have accumulated so far.
This type has an Alternative instance where failure discards state (basically backtracking) and a MonadFail instance that returns mzero on pattern-match failure inside do-blocks.
The typeclass:
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE OverloadedStrings #-}
import Data.Data ( Proxy )
import GHC.TypeLits
class HasPathParser (x :: k) where
pathParser :: Proxy x -> PathParser
The instance for Symbol moves the path piece from the pending list to the processed list:
instance KnownSymbol piece => HasPathParser (piece :: Symbol) where
pathParser _ = do
(piece : rest, found) <- get -- we are using MonadFail here
guard (piece == Data.Text.pack (symbolVal (Proxy #piece)))
put (rest, piece : found)
The instance for Capture puts the name of the path variable—not the value—on the processed list:
instance KnownSymbol name => HasPathParser (Capture name x) where
pathParser _ = do
(_ : rest, found) <- get -- we are using MonadFail here
put (rest, Data.Text.pack (symbolVal (Proxy #name)) : found)
When we reach a Verb (GET, POST...) we require that no pending path pieces should remain:
instance HasPathParser (Verb method statusCode contextTypes a) where
pathParser _ = do
([], found) <- get -- we are using MonadFail here
put ([], found)
Some other instances:
instance HasPathParser (ReqBody x y) where
pathParser _ = pure ()
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :> b) where
pathParser _ = pathParser (Proxy #a) *> pathParser (Proxy #b)
instance (HasPathParser a, HasPathParser b) => HasPathParser (a :<|> b) where
pathParser _ = pathParser (Proxy #a) <|> pathParser (Proxy #b)
Putting it to work:
main :: IO ()
main = do
do let Just ([], result) = execStateT (pathParser (Proxy #BookAPI)) (["books"],[])
print result
-- ["books"]
do let Just ([], result) = execStateT (pathParser (Proxy #BookAPI)) (["book", "somebookid"],[])
print result
-- ["Name","book"]

Navigating Haskell Servant API with Lucid WebUI

I've learnt that I can define my API with servant and Lucid in the following way:
type ClientAPI =
"users" :> Get '[HTML] (Html ())
:<|> "userdata" :> Get '[HTML] (Html ())
Then if I want to add a link to one of the endpoints in my HTML, I can use "a_" function provided by Lucid, e.g.
a_ [href_ "users"] "Show users"
The problem I have with this approach is that I need to repeat the endpoint's name twice. "users" occurs both in API definition and a_ tag. As a result if I change it in one place, the other one stops working immediately.
Is there a way to define a single symbol that could be used in both places instead ? Something like:
data MySites = UserSite | UserDataSite -- potentially more
type ClientAPI' =
UserSite :> Get '[HTML] (Html ())
-- ......
let html =
...
a_ [href_ UserSite] "Show users"
...
If you just want to abstract over the string "user", you can do this with a type alias, and then use GHC.TypeLits.symbolVal to get the string at the value level:
{-# LANGUAGE DataKinds #-}
import Data.Proxy
import GHC.TypeLits
type UserSite = "user"
html = ... href_ (symbolVal (Proxy :: Proxy UserSite)) ...
You can also make the symbolVal call a bit shorter by defining a helper with AllowAmbiguousTypes:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
urlpath :: forall s . KnownSymbol s => String
urlpath = symbolVal (Proxy :: Proxy s)
html = ... href_ (urlpath #UserSite) ...
(I actually thought something like it was in the standard library somewhere but I can't find it.)

Where is the documentation for autogeneration of Has typeclasses in lens?

I was looking at the penultimate example in this blog post (also here), and after verifying it ran, it seemed to confirm that lens can generate Has typeclasses, which I take was the implication from the author of the blog. However, I miss where this is described, either in the lens contents or the lens tutorial. Any explanations external to official docs for how this is done would also be welcome. But it seems like this may just be standard when using the most basic feature (makeLenses, or in this case, makeLensesWith).
Here is the reproduced code:
#!/usr/bin/env stack
-- stack --resolver lts-8.12 script
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
import Control.Concurrent.Async.Lifted.Safe
import Control.Monad.Reader
import Control.Concurrent.STM
import Say
import Control.Lens
import Prelude hiding (log)
data Env = Env
{ envLog :: !(String -> IO ())
, envBalance :: !(TVar Int)
}
makeLensesWith camelCaseFields ''Env
modify :: (MonadReader env m, HasBalance env (TVar Int), MonadIO m)
=> (Int -> Int)
-> m ()
modify f = do
env <- ask
liftIO $ atomically $ modifyTVar' (env^.balance) f
logSomething :: (MonadReader env m, HasLog env (String -> IO ()), MonadIO m)
=> String
-> m ()
logSomething msg = do
env <- ask
liftIO $ (env^.log) msg
main :: IO ()
main = do
ref <- newTVarIO 4
let env = Env
{ envLog = sayString
, envBalance = ref
}
runReaderT
(concurrently
(modify (+ 1))
(logSomething "Increasing account balance"))
env
balance <- readTVarIO ref
sayString $ "Final balance: " ++ show balance
Field is the word lens uses to describe the pattern of one class per named field, allowing multiple records with the same field name but (optionally) different types. So camelCaseFields, makeFieldOptics, defaultFieldRules all say in their name that they will generate these HasFoo classes, in the usual terse style of lens.
makeClassy also generates classes named Has*, but named after the data type, not the record field, and not following a different pattern.
Your code above generates the following code (shown with -ddump-splices):
makeLensesWith camelCaseFields ''Env
======>
class HasBalance s a | s -> a where
balance :: Lens' s a
instance HasBalance Env (TVar Int) where
{-# INLINE balance #-}
balance f_a4eTr (Env x1_a4eTs x2_a4eTt)
= (fmap (\ y1_a4eTu -> (Env x1_a4eTs) y1_a4eTu)) (f_a4eTr x2_a4eTt)
class HasLog s a | s -> a where
log :: Lens' s a
instance HasLog Env (String -> IO ()) where
{-# INLINE log #-}
log f_a4eTx (Env x1_a4eTy x2_a4eTz)
= (fmap (\ y1_a4eTA -> (Env y1_a4eTA) x2_a4eTz)) (f_a4eTx x1_a4eTy)

A typeclass instance that needs configuration data. What are my options?

Using yesod and persistent, I made what I think is a handy type to handle Markdown data:
{-# LANGUAGE OverloadedStrings #-}
module Utils.MarkdownText where
import Prelude
import Data.Text.Lazy
import Data.Text as T
import Database.Persist
import Database.Persist.Sql
import Text.Blaze
import Text.Markdown
newtype MarkdownText = MarkdownText { rawMarkdown :: T.Text }
instance PersistField MarkdownText where
toPersistValue = PersistText . rawMarkdown
fromPersistValue (PersistText val) = Right $ MarkdownText { rawMarkdown = val }
fromPersistValue _ = Left "invalid type"
instance PersistFieldSql MarkdownText where
sqlType _ = SqlString
instance ToMarkup MarkdownText where
toMarkup = (markdown def) . fromStrict . rawMarkdown
preEscapedToMarkup = toMarkup . rawMarkdown
You may notice in the ToMarkup instance I use def to get markdown parameters. If I would like to change these settings, and not have it hardcoded in this module, what are my options?
I have considered the option of making MarkdownText take the settings information as a parameter, but what other options are there (if any)?
I'm going to simplify the problem so that we only need core libraries. We want to change how we Show a MarkdownText based on some ExampleSettings that contain a prefix and a suffix.
{-# LANGUAGE OverloadedStrings #-}
import Data.Text as T
import Data.Monoid
import Data.String
newtype MarkdownText = MarkdownText { rawMarkdown :: T.Text}
instance IsString MarkdownText where
fromString = MarkdownText . fromString
data ExampleSettings = ExampleSettings { prefix :: T.Text, suffix :: T.Text }
def = ExampleSettings "" ""
emphasise = def { prefix = "*", suffix = "*" }
showWithSettings :: ExampleSettings -> T.Text -> String
showWithSettings set = show . (\x -> prefix set <> x <> suffix set)
instance Show MarkdownText where
show = showWithSettings def . rawMarkdown
main = print $ MarkdownText "Hello World"
There are a number of options for how to solve this problem, first at the value level, then at the type level, and finally globally at the type level.
Add a field
We have a few options for how we can proceed. The simplest option is to add the setting at the value level. We'll wrap up the settings with the MarkdownText.
data ConfiguredMarkdownText = ConfiguredMarkdownText {
markdownText :: MarkdownText,
settings :: ExampleSettings }
instance Show ConfiguredMarkdownText where
show t = showWithSettings (settings t) (rawMarkdown . markdownText $ t)
main = print $ ConfiguredMarkdownText "Hello World" emphasise
For convenience, we added an IsString instance for MarkdownText in the first section.
Add a type parameter
We could carry the extra data we need around at the type level instead of at the value level. We add a type parameter to MarkdownText to indicate which settings to use.
newtype MarkdownText s = MarkdownText { rawMarkdown :: T.Text}
We make types to represent the possible settings
data Def = Def
data Emphasise = Emphasise
We can add a type class for types that determine settings, and instances for the possible settings.
{-# LANGUAGE FunctionalDependencies #-}
class Setting v k | k -> v where
setting :: proxy k -> v
instance Setting ExampleSettings Def where
setting _ = def
instance Setting ExampleSettings Emphasise where
setting _ = emphasise
We can Show any MarkdownText s as long as s provides the Setting.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Setting ExampleSettings s) => Show (MarkdownText s) where
show t = showWithSettings (setting t) (rawMarkdown t)
main = print ("Hello World" :: MarkdownText Emphasise)
MarkdownText :: * -> * requires a slightly different IsString instance.
instance IsString (MarkdownText s) where
fromString = MarkdownText . fromString
Reflect the value from a type parameter
The reflection package provides a way to temporarily associate a value with a type. This lets us do the same thing as in the previous example, but without needing to make types of our own to represent the settings.
import Data.Reflection
We start by adding an extra type parameter to MarkdownText, the same as in the previous section.
newtype MarkdownText s = MarkdownText { rawMarkdown :: T.Text}
The reflection package defines a class, Reifies, that is almost identical to the Setting class we made for the previous section. This lets us jump straight to defining the Show instance.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Reifies s ExampleSettings) => Show (MarkdownText s) where
show t = showWithSettings (reflect t) (rawMarkdown t)
We'll define a little convenience function to tag the type parameter of MarkdownTexts
markdownText :: proxy s -> T.Text -> MarkdownText s
markdownText _ = MarkdownText
and complete the example of setting the ExampleSettings to be used when showing a MarkdownText. We provide the reified value with reify :: a -> (forall s. Reifies s a => Proxy s -> r) -> r, which passes back a proxy for the type the value has been reified to.
main = reify emphasise (\p -> print (markdownText p "Hello World"))
This has an advantage over the simpler version from the next section; multiple settings can be used for MarkdownTexts with different type parameters.
main = reify emphasise $ \p1 ->
reify def $ \p2 ->
do
print (markdownText p1 "Hello World")
print (markdownText p2 "Goodbye")
Reflect a global configuration
The reflection package also defines a simpler class, Given. It's defined as class Given a where given :: a. It represents values that can be determined from the type of the value itself. This allows us to provide a single global configuration value for a specific type, like ExampleSettings. We can jump straight to writing the show instance for MarkdownText.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (Given ExampleSettings) => Show (MarkdownText) where
show = showWithSettings given . rawMarkdown
We provide the given ExampleSettings with give :: a -> (Given a => r) -> r.
main = give emphasise $ print (MarkdownText "Hello World")

using types to prevent conflicting port numbers in a list

Propellor represents a system it deploys as [Property], and for the sake of simplification, let's say that data Property = Property (Set Port) SatisfyProperty
So, there might be a apacheInstalled Property, which uses ports 80 and 443, and also a torBridge property, which uses port 443. It does not make sense for a system to have both properties at the same time, since they use the same port 443.
I wonder if there's a feasible way for the type checker to prevent a system from being assigned both? Then port conflicts could be caught at build time. I suppose that type level Ints would be the first step, but I haven't a clue as to the second one..
This is pretty tricky, but entirely possible with the an up to date ghc version (the latest haskell platform will work). There isn't many examples about this (as it is all pretty new), so I hope this helps you.
You are right that using type level naturals will work. You will need two modules - one to define the constructs and to provide a safe interface, and another to define the actual services.
This is the code in use:
{-# LANGUAGE TypeOperators, PolyKinds, RankNTypes #-}
{-# LANGUAGE KindSignatures, DataKinds, TypeFamilies, UndecidableInstances #-}
module DefinedServices where
import ServiceTypes
import Control.Monad
apacheInstalled :: Service '[443] ServiceDetails
apacheInstalled = makeService "apache" $ putStrLn "Apache service"
torBridge :: Service [80,443] ServiceDetails
torBridge = makeService "tor" $ putStrLn "Tor service"
httpService :: Service [80, 8080] ServiceDetails
httpService = makeService "http" $ putStrLn "Http service"
serviceList1 :: [ServiceDetails]
serviceList1 = getServices $
noServices `addService` httpService `addService` apacheInstalled
-- serviceList2 :: [ServiceDetails]
-- serviceList2 = getServices $
-- noServices `addService` apacheInstalled `addService` torBridge
main = startServices serviceList1
Note how the ports for each service are defined in the type. serviceList1 makes use of the httpService and the apacheInstalled service. This compiles, as their ports don't conflict. serviceList2 is commented out, and causes this compile error if uncommented:
DefinedServices.hs:22:56:
Couldn't match type 'False with 'True
Expected type: 'True
Actual type: ServiceTypes.UniquePorts '[443, 80, 443]
In the second argument of `($)', namely
`noServices `addService` apacheInstalled `addService` torBridge'
In the expression:
getServices
$ noServices `addService` apacheInstalled `addService` torBridge
In an equation for `serviceList2':
serviceList2
= getServices
$ noServices `addService` apacheInstalled `addService` torBridge
Failed, modules loaded: ServiceTypes.
This describes pretty well the problem: UniquePorts ends up being false as 443 is used twice.
So here is how it is done in ServiceTypes.hs:
{-# LANGUAGE TypeOperators, PolyKinds, RankNTypes #-}
{-# LANGUAGE KindSignatures, DataKinds, TypeFamilies, UndecidableInstances #-}
module ServiceTypes (
makeService, noServices, addService, Service, ServiceDetails(..)
, getServices, startServices) where
import GHC.TypeLits
import Control.Monad
import Data.Type.Equality
import Data.Type.Bool
We need a whole bunch of language extensions to get this to work. Also, the safe interface is defined.
First, a type level function is needed to check if a list is unique. This leverages the type family operators in Data.Type.Equality and Data.Type.Bool. Note that the following code is only ever executed by the typechecker.
type family UniquePorts (list1 :: [Nat]) :: Bool
type instance UniquePorts '[] = True
type instance UniquePorts (a ': '[]) = True
type instance UniquePorts (a ': b ': rest) = Not (a == b) && UniquePorts (a ': rest) && UniquePorts (b ': rest)
It is just a recursive definition of unique.
Next, as we will use multiple services at once, there will need to be a way to combine two lists into one:
type family Concat (list1 :: [a]) (list2 :: [a]) :: [a]
type instance Concat '[] list2 = list2
type instance Concat (a ': rest) list2 = a ': Concat rest list2
That is all the type level functions we require!
Next, I will define a Service type, that wraps another type with the ports needed:
data Service (ports :: [Nat]) service = Service service
Next, for the actual details of a single service. You should customize this to what you need:
data ServiceDetails = ServiceDetails {
serviceName :: String
, runService :: IO ()
}
I also added a helper function to wrap a service in a Service type with defined ports:
makeService :: String -> IO () -> Service ports ServiceDetails
makeService name action = Service $ ServiceDetails name action
Now finally for the multiple services lists. `noServices just defines an empty list of services, which obviously uses no ports:
noServices :: Service '[] [ServiceDetails]
noServices = Service []
addService is where it all comes together:
addService :: (finalPorts ~ Concat ports newPorts, UniquePorts finalPorts ~ True)
=> Service ports [ServiceDetails]
-> Service newPorts ServiceDetails
-> Service finalPorts [ServiceDetails]
addService (Service serviceList) (Service newService) =
Service $ (newService : serviceList)
finalPorts ~ Concat ports newPorts just makes finalPorts the combination of the ports in the list of services and the new service. UniquePorts finalPorts ~ True ensures the final ports don't contain any duplicate ports. The rest of the function is completely trivial.
getServices unwraps the [ServiceDetails] from a Service ports [ServiceDetails]. As the Service constructor isn't made public, the only way to create a Service ports [ServiceDetails] type is through the noServices and addService functions, which are guaranteed to be safe.
getServices :: Service ports [ServiceDetails] -> [ServiceDetails]
getServices (Service details) = details
Finally a testing function to run the services:
startServices :: [ServiceDetails] -> IO ()
startServices services = forM_ services $ \service -> do
putStrLn $ "Starting service " ++ (serviceName service)
runService service
putStrLn "------"
The possibilities for this new functionality are nearly endless, and is a huge improvement over previous ghc versions (it was still possible, but much more difficult). This code is pretty simple once you get your head around using values in types.

Resources