Skip to content

Instantly share code, notes, and snippets.

@dyokomizo
Forked from copumpkin/Routing.agda
Created March 26, 2012 11:39
Show Gist options
  • Save dyokomizo/2204549 to your computer and use it in GitHub Desktop.
Save dyokomizo/2204549 to your computer and use it in GitHub Desktop.
Routing
module Routing where
open import Function hiding (type-signature)
open import Data.Bool hiding (_β‰Ÿ_)
open import Data.Maybe
open import Data.Char hiding (_β‰Ÿ_)
open import Data.String as String
open import Data.List as List hiding ([_])
open import Data.Product hiding (curry; uncurry)
open import Data.Unit hiding (decTotalOrder; _≀_; _β‰Ÿ_)
open import Data.Nat hiding (_β‰Ÿ_)
open import Data.Fin hiding (_<_; _≀_)
open import Data.Vec hiding ([_]; _∈_)
open import Relation.Nullary
open import Relation.Binary
open import Relation.Binary.PropositionalEquality
-- A type tagged with a String name, just because I can
record _∢_ (name : String) (A : Set) : Set where
constructor inj
field
proj : A
open _∢_
-- I don't care about parsing details, but I'll probably slot in NAD's Total Parser Combinators at some point
module Parsers where
postulate Parser : Set β†’ Set
postulate _∈_ : βˆ€ {A} β†’ String β†’ Parser A β†’ Set
postulate parse : βˆ€ {A} (p : Parser A) (s : String) β†’ Dec (s ∈ p)
postulate get : βˆ€ {A} {p : Parser A} {s} β†’ s ∈ p β†’ A
-- Hah!
postulate ∈-unique : βˆ€ {A s} {p : Parser A} (x y : s ∈ p) β†’ x ≑ y
postulate nat : Parser β„•
postulate string : Parser String
postulate vec : βˆ€ {A} β†’ Parser A β†’ (n : β„•) β†’ Parser (Vec A n)
open Parsers
infixl 6 _β–»_
record Empty : Set where
constructor Ξ΅
mutual
-- This telescopey thing is probably the most general list-like container possible.
-- For intuition of how this works, think of how a heterogeneous list generalizes
-- a non-dependent pair. A Telescope generalizes a dependent pair in a similar way,
-- by letting types further down the list depend on the value at the head.
-- This needs to be left-nested for the types to make sense, but that makes it rather uncomfortable for other purposes.
data Telescope : Set₁ where
Ξ΅ : Telescope
_β–»_ : (xs : Telescope) (x : ⟦ xs ⟧ β†’ Set) β†’ Telescope
-- An interpretation function of the telescope, letting us create very dependent values (a bunch of nested sigmas)
-- This is the recursive side of induction-recursion. I'd prefer it to be induction-induction
-- but Agda isn't clever enough to figure that out yet.
⟦_⟧ : Telescope β†’ Set
⟦ Ρ ⟧ = Empty
⟦ xs β–» x ⟧ = Sigma xs x
-- Just for the sake of having prettier patterns
record Sigma (xs : Telescope) (x : ⟦ xs ⟧ β†’ Set) : Set where
constructor _β–»_
field
fst : ⟦ xs ⟧
snd : x fst
-- A telescope is an n-ary dependent tuple, so we can curry it just like a regular tuple into an n-ary dependent function
Curried : βˆ€ t β†’ (⟦ t ⟧ β†’ Set) β†’ Set
Curried Ξ΅ R = R Ξ΅
Curried (xs β–» x) R = Curried xs (Ξ» q β†’ βˆ€ r β†’ R (q β–» r))
-- Transform an uncurried function into a curried one
curry : βˆ€ {t} (R : ⟦ t ⟧ β†’ Set) β†’ ((v : ⟦ t ⟧) β†’ R v) β†’ Curried t R
curry {Ξ΅} R f = f Ξ΅
curry {xs β–» x} R f = curry {xs} _ (Ξ» v r β†’ f (v β–» r))
-- And back again
uncurry : βˆ€ {t} (R : ⟦ t ⟧ β†’ Set) β†’ Curried t R β†’ ((v : ⟦ t ⟧) β†’ R v)
uncurry {Ξ΅} R c v = c
uncurry {xs β–» x} R c (vs β–» v) = uncurry {xs} _ c vs v
-- Sometimes you just want a simple list of types.
-- For example, ⟦ Simple (Bool ∷ β„• ∷ String ∷ []) ⟧ is just a heterogeneous list
Simple : List Set β†’ Telescope
Simple = List.foldl (Ξ» xs x β†’ xs β–» const x) Ξ΅
infixl 6 _/_ _//_ _//β€²_
-- A route is an annotated telescope. _/_ is for static path components and _//_ is for parameter parser components
data Route : Telescope β†’ Set₁ where
Ξ΅ : Route Ξ΅
_/_ : βˆ€ {xs} (rs : Route xs) (k : String) β†’ Route xs
_//_ : βˆ€ {xs x} (rs : Route xs) (r : ((q : ⟦ xs ⟧) β†’ Parser (x q)) Γ— String) β†’ Route (xs β–» x)
-- If you don't want a dependent path component (probably most of the time)
_//β€²_ : βˆ€ {xs A} β†’ Route xs β†’ (Parser A Γ— String) β†’ Route (xs β–» const A)
rs //β€² (p , s) = rs // (const p , s)
-- Propagate the annotated parameter parser names into the telescope so we get named arguments
mutual
Named : βˆ€ {t} β†’ Route t β†’ Telescope
Named Ξ΅ = Ξ΅
Named (rs / k) = Named rs
Named (_//_ {x = x} rs (p , s)) = Named rs β–» (Ξ» q β†’ s ∢ x (extract-Named q))
extract-Named : βˆ€ {t} {r : Route t} β†’ ⟦ Named r ⟧ β†’ ⟦ t ⟧
extract-Named {r = Ξ΅} v = v
extract-Named {r = rs / k} v = extract-Named {r = rs} v
extract-Named {r = rs // r} (vs β–» v) = extract-Named {r = rs} vs β–» proj v
-- This type is only inhabited if the list of path components (backwards, due to the inherent left-nesting of telescopes) matches the route
mutual
data Match : βˆ€ {t} β†’ Route t β†’ List String β†’ Set₁ where
Ξ΅ : Match Ξ΅ []
_/_ : βˆ€ {ss xs} {rs : Route xs} (ms : Match rs ss) (s : String) β†’ Match (rs / s) (s ∷ ss)
_//_ : βˆ€ {ss s xs} {x : ⟦ xs ⟧ β†’ Set} {rs : Route xs} {r : ((q : ⟦ xs ⟧) β†’ Parser (x q)) Γ— String} (ms : Match rs ss) (m : s ∈ proj₁ r (payload ms)) β†’ Match (rs // r) (s ∷ ss)
-- If we have a match, we can pull out the actual data
payload : βˆ€ {t ss} {r : Route t} β†’ Match r ss β†’ ⟦ t ⟧
payload Ξ΅ = Ξ΅
payload (ms / s) = payload ms
payload (ms // m) = payload ms β–» get m
-- Simple lemma
Match-equal : βˆ€ {t k ss s} {rs : Route t} β†’ Match (rs / k) (s ∷ ss) β†’ k ≑ s
Match-equal (ms / ._) = refl
-- All matches are unique
Match-unique : βˆ€ {t ss} {r : Route t} (x y : Match r ss) β†’ x ≑ y
Match-unique Ξ΅ Ξ΅ = refl
Match-unique (ms / s) (msβ€² / .s) rewrite Match-unique ms msβ€² = refl
Match-unique (ms // m) (msβ€² // mβ€²) rewrite Match-unique ms msβ€² | ∈-unique m mβ€² = refl
-- Decision procedure to see if the given list of path components (backwards) matches the route
match : βˆ€ {t} (r : Route t) (s : List String) β†’ Dec (Match r s)
match Ξ΅ [] = yes Ξ΅
match Ρ (x ∷ xs) = no (λ ())
match (rs / k) [] = no (Ξ» ())
match (rs / k) (x ∷ xs) with k β‰Ÿ x
match (rs / k) (x ∷ xs) | yes p with match rs xs
match (rs / k) (.k ∷ xs) | yes refl | yes q = yes (q / k)
match (rs / k) (.k ∷ xs) | yes refl | no Β¬q = no (Ξ» { (ms / .k) β†’ Β¬q ms })
match (rs / k) (x ∷ xs) | no Β¬p = no (Ξ» q β†’ Β¬p (Match-equal q))
match (rs // r) [] = no (Ξ» ())
match (rs // r) (x ∷ xs) with match rs xs
match (rs // r) (x ∷ xs) | yes p with parse (proj₁ r (payload p)) x
match (rs // r) (x ∷ xs) | yes p | yes q = yes (p // q)
match (rs // r) (x ∷ xs) | yes p | no Β¬q = no (Ξ» { (ms // m) β†’ Β¬q (subst (Ξ» q β†’ x ∈ proj₁ r (payload q)) (Match-unique ms p) m) })
match (rs // r) (x ∷ xs) | no Β¬p = no (Ξ» { (ms // m) β†’ Β¬p ms })
-- A few HTTP methods
module Http where
data Method : Set where
HEAD GET POST PUT DELETE OPTIONS : Method
-- We might eventually specify what kind of state modifications different methods are allowed to make
postulate Handler : Http.Method β†’ Set
-- The actual route stores the method (or maybe multiple ones, someday), a route, and a handler function
record RouteSpec : Set₁ where
constructor rs
field
m : Http.Method
{t} : Telescope
r : Route t
-- Should we let the method of the Handler depend on its arguments? :P
-- for example: /foo/5/bar can accept PUT but /foo/4/bar can accept GET
h : Curried (Named r) (const (Handler m))
Routes = List RouteSpec
module Examples where
-- A dependent route. Would accept (assuming correct parsers):
-- "/foo/5/bar/[1,2,3,4,5]"
-- "/foo/4/bar/[5,25,1,4]"
-- but would reject:
-- "/foo/5/bar/[1,2,3,4]"
r : Route (Ξ΅ β–» (Ξ» { Ξ΅ β†’ β„• }) β–» (Ξ» { (Ξ΅ β–» n) β†’ Vec β„• n }))
r = Ξ΅ / "foo" //β€² (nat , "foo_id") / "bar" // ((Ξ» { (Ξ΅ β–» n) β†’ vec nat n }) , "bar_id")
-- If you only use non-dependent components, the type can be "inferred"!
s = Ξ΅ / "bar" //β€² (nat , "bar_id") / "mooooo" //β€² (string , "mooooo_id")
-- What a handler might look like
foo_handler : (n : "foo_id" ∢ β„•) β†’ ("bar_id" ∢ Vec β„• (proj n)) β†’ Handler Http.GET
foo_handler (inj foo_id) (inj bar_id) = {!!}
bar_handler : ("bar_id" ∢ β„•) β†’ ("mooooo_id" ∢ String) β†’ Handler Http.PUT
bar_handler (inj bar_id) (inj mooooo_id) = {!!}
-- List of handlers
routes : Routes
routes
= rs Http.GET r foo_handler
∷ rs Http.PUT s bar_handler
∷ []
-- More to come later, including efficient route lookup!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment