has a more interesting =zip code=
-- column.

-- #+BEGIN_EXAMPLE
-- λ> :i U2
-- type U2 =
--   Record
--     '["user id" :-> Int, "age" :-> Int, "gender" :-> Text,
--       "occupation" :-> Text, "zip code" :-> ZipT]
-- #+END_EXAMPLE

-- Let's take the occupations of the first 10 users from New England,
-- New Jersey, and other places whose zip codes begin with a zero.

neOccupations :: (U2zipCode ∈ rs, U2occupation ∈ rs, Monad m)
              => Pipe (Record rs) Text m r
neOccupations = P.filter (isNewEngland . view u2zipCode)
                >-> P.map (view u2occupation)
  where isNewEngland (ZipUS 0 _ _ _ _) = True
        isNewEngland _ = False

-- #+BEGIN_EXAMPLE
-- λ> runEffect $ movieStream2 >-> neOccupations >-> P.take 10 >-> P.print
-- "administrator"
-- "student"
-- "other"
-- "programmer"
-- "librarian"
-- "entertainment"
-- "marketing"
-- "programmer"
-- "educator"
-- "healthcare"
-- #+END_EXAMPLE

-- So there we go! We've done both row and column subset queries with a
-- strongly typed query (namely, ~isNewEngland~). Another situation in
-- which one might want to define a custom universe of column types is
-- when dealing with dates. This would let you both reject rows with
-- badly formatted dates, for example, and efficiently query the data set
-- with richly-typed queries.

-- Even better, did you notice the types of ~writers~ and
-- ~neOccupations~? They are polymorphic over the full row type!
-- That's what the ~(Occupation ∈ rs)~ constraint signifies: such a
-- function will work for record types with any set of fields, ~rs~, so
-- long as ~Occupation~ is an element of that set. This means that if
-- your schema changes, or you switch to a related but different data
-- set, *these functions can still be used without even touching the
-- code*. Just recompile against the new data set, and you're good to go.

-- * Appendix
-- ** User Types

-- Here are the definitions needed to define the ~MyColumns~ type with
-- its more descriptive ~ZipT~ type. We have to define these things in
-- a separate module from our main work due to GHC's stage
-- restrictions regarding Template Haskell. Specifically, ~ZipT~ and
-- its instances are used at compile time to infer the record type
-- needed to represent the data file. Notice the extension point here
-- is not too rough: you prepend new, more refined, type compatibility
-- checks to the head of ~CommonColumns~, or you can build up your own
-- list of expected types.

-- This may not be something you'd want to do for every data
-- set. However, the /ability/ to refine the structure of parsed data
-- is in keeping with the overall goal of ~Frames~: it's easy to take
-- off, and the sky's the limit.

-- -- #+begin_src haskell
-- {-# LANGUAGE DataKinds, DeriveDataTypeable, TypeFamilies, TypeOperators #-}
-- module TutorialZipCode where
-- import Control.Monad (mzero)
-- import qualified Data.Char as C
-- import Data.Readable (Readable(fromText))
-- import qualified Data.Text as T
-- import Data.Typeable
-- import qualified Data.Vector as V
-- import Frames.InCore (VectorFor)
-- import Frames

-- data ZipT = ZipUS Int Int Int Int Int
--           | ZipWorld Char Char Char Char Char
--   deriving (Eq, Ord, Show, Typeable)

-- type instance VectorFor ZipT = V.Vector

-- instance Readable ZipT  where
--   fromText t
--       | T.length t == 5 = let cs@[v,w,x,y,z] = T.unpack t
--                               [a,b,c,d,e] = map C.digitToInt cs
--                           in if all C.isDigit cs
--                              then return $ ZipUS a b c d e
--                              else return $ ZipWorld v w x y z
--       | otherwise = mzero

-- type MyColumns = ZipT ': CommonColumns
-- #+end_src

-- ** Splice Dump

-- The Template Haskell splices we use produce quite a lot of
-- code. The raw dumps of these splices can be hard to read, but I
-- have included some elisp code for cleaning up that output in the
-- design notes for =Frames=. Here is what we get from the
-- @@html:<code>@@tableTypes'@@html:</code>@@ splice shown above.

-- The overall structure is this:

-- - A ~Record~ type called ~User~ with all necessary columns
-- - A ~userParser~ value that overrides parsing defaults
-- - A type synonym for each column that pairs the column name with its
--   type
-- - A lens to work with each column on any given row

-- Remember that for CSV files that include a header, the splice you
-- write in your code need not include the column names or separator
-- character.

-- -- #+begin_src haskell
--   tableTypes'
--     (rowGen
--        {rowTypeName = "User",
--         columnNames = ["user id", "age", "gender", "occupation",
--                        "zip code"],
--         separator = "|"})
--     "data/ml-100k/u.user"
-- ======>
--   type User =
--       Record ["user id" :-> Int, "age" :-> Int, "gender" :-> Text, "occupation" :-> Text, "zip code" :-> Text]

--   userParser :: ParserOptions
--   userParser
--     = ParserOptions
--         (Just
--            (map
--               T.pack
--               ["user id", "age", "gender", "occupation", "zip code"]))
--         (T.pack "|")

--   type UserId = "user id" :-> Int

--   userId ::
--     forall f_adkB rs_adkC. (Functor f_adkB,
--                             RElem UserId rs_adkC (RIndex UserId rs_adkC)) =>
--     (Int -> f_adkB Int) -> Record rs_adkC -> f_adkB (Record rs_adkC)
--   userId = rlens (Proxy :: Proxy UserId)

--   userId' ::
--     forall g_adkD f_adkE rs_adkF. (Functor f_adkE,
--                                    Functor g_adkD,
--                                    RElem UserId rs_adkF (RIndex UserId rs_adkF)) =>
--     (g_adkD UserId -> f_adkE (g_adkD UserId))
--     -> Rec g_adkD rs_adkF -> f_adkE (Rec g_adkD rs_adkF)
--   userId' = rlens' (Proxy :: Proxy UserId)

--   type Age = "age" :-> Int

--   age ::
--     forall f_adkG rs_adkH. (Functor f_adkG,
--                             RElem Age rs_adkH (RIndex Age rs_adkH)) =>
--     (Int -> f_adkG Int) -> Record rs_adkH -> f_adkG (Record rs_adkH)
--   age = rlens (Proxy :: Proxy Age)

--   age' ::
--     forall g_adkI f_adkJ rs_adkK. (Functor f_adkJ,
--                                    Functor g_adkI,
--                                    RElem Age rs_adkK (RIndex Age rs_adkK)) =>
--     (g_adkI Age -> f_adkJ (g_adkI Age))
--     -> Rec g_adkI rs_adkK -> f_adkJ (Rec g_adkI rs_adkK)
--   age' = rlens' (Proxy :: Proxy Age)

--   type Gender = "gender" :-> Text

--   gender ::
--     forall f_adkL rs_adkM. (Functor f_adkL,
--                             RElem Gender rs_adkM (RIndex Gender rs_adkM)) =>
--     (Text -> f_adkL Text) -> Record rs_adkM -> f_adkL (Record rs_adkM)
--   gender = rlens (Proxy :: Proxy Gender)

--   gender' ::
--     forall g_adkN f_adkO rs_adkP. (Functor f_adkO,
--                                    Functor g_adkN,
--                                    RElem Gender rs_adkP (RIndex Gender rs_adkP)) =>
--     (g_adkN Gender -> f_adkO (g_adkN Gender))
--     -> Rec g_adkN rs_adkP -> f_adkO (Rec g_adkN rs_adkP)
--   gender' = rlens' (Proxy :: Proxy Gender)

--   type Occupation = "occupation" :-> Text

--   occupation ::
--     forall f_adkQ rs_adkR. (Functor f_adkQ,
--                             RElem Occupation rs_adkR (RIndex Occupation rs_adkR)) =>
--     (Text -> f_adkQ Text) -> Record rs_adkR -> f_adkQ (Record rs_adkR)
--   occupation = rlens (Proxy :: Proxy Occupation)

--   occupation' ::
--     forall g_adkS f_adkT rs_adkU. (Functor f_adkT,
--                                    Functor g_adkS,
--                                    RElem Occupation rs_adkU (RIndex Occupation rs_adkU)) =>
--     (g_adkS Occupation -> f_adkT (g_adkS Occupation))
--     -> Rec g_adkS rs_adkU -> f_adkT (Rec g_adkS rs_adkU)
--   occupation' = rlens' (Proxy :: Proxy Occupation)

--   type ZipCode = "zip code" :-> Text

--   zipCode ::
--     forall f_adkV rs_adkW. (Functor f_adkV,
--                             RElem ZipCode rs_adkW (RIndex ZipCode rs_adkW)) =>
--     (Text -> f_adkV Text) -> Record rs_adkW -> f_adkV (Record rs_adkW)
--   zipCode = rlens (Proxy :: Proxy ZipCode)

--   zipCode' ::
--     forall g_adkX f_adkY rs_adkZ. (Functor f_adkY,
--                                    Functor g_adkX,
--                                    RElem ZipCode rs_adkZ (RIndex ZipCode rs_adkZ)) =>
--     (g_adkX ZipCode -> f_adkY (g_adkX ZipCode))
--     -> Rec g_adkX rs_adkZ -> f_adkY (Rec g_adkX rs_adkZ)
--   zipCode' = rlens' (Proxy :: Proxy ZipCode)
-- #+end_src

-- ** Thanks
-- Thanks to Greg Hale and Ben Gamari for reviewing early drafts of this document.

-- #+DATE:
-- #+TITLE: Frames Tutorial
-- #+OPTIONS: html-link-use-abs-url:nil html-postamble:nil

neOccupations :: (U2zipCode ∈ rs, U2occupation ∈ rs, Monad m)
              => Pipe (Record rs) Text m r
neOccupations = P.filter (isNewEngland . view u2zipCode)
                >-> P.map (view u2occupation)
  where isNewEngland (ZipUS 0 _ _ _ _) = True
        isNewEngland _ = False

-- #+BEGIN_EXAMPLE
-- λ> runEffect $ movieStream2 >-> neOccupations >-> P.take 10 >-> P.print
-- "administrator"
-- "student"
-- "other"
-- "programmer"
-- "librarian"
-- "entertainment"
-- "marketing"
-- "programmer"
-- "educator"
-- "healthcare"
-- #+END_EXAMPLE

-- So there we go! We've done both row and column subset queries with a
-- strongly typed query (namely, ~isNewEngland~). Another situation in
-- which one might want to define a custom universe of column types is
-- when dealing with dates. This would let you both reject rows with
-- badly formatted dates, for example, and efficiently query the data set
-- with richly-typed queries.

-- Even better, did you notice the types of ~writers~ and
-- ~neOccupations~? They are polymorphic over the full row type!
-- That's what the ~(Occupation ∈ rs)~ constraint signifies: such a
-- function will work for record types with any set of fields, ~rs~, so
-- long as ~Occupation~ is an element of that set. This means that if
-- your schema changes, or you switch to a related but different data
-- set, *these functions can still be used without even touching the
-- code*. Just recompile against the new data set, and you're good to go.

-- * Appendix
-- ** User Types

-- Here are the definitions needed to define the ~MyColumns~ type with
-- its more descriptive ~ZipT~ type. We have to define these things in
-- a separate module from our main work due to GHC's stage
-- restrictions regarding Template Haskell. Specifically, ~ZipT~ and
-- its instances are used at compile time to infer the record type
-- needed to represent the data file. Notice the extension point here
-- is not too rough: you prepend new, more refined, type compatibility
-- checks to the head of ~CommonColumns~, or you can build up your own
-- list of expected types.

-- This may not be something you'd want to do for every data
-- set. However, the /ability/ to refine the structure of parsed data
-- is in keeping with the overall goal of ~Frames~: it's easy to take
-- off, and the sky's the limit.

-- -- #+begin_src haskell
-- {-# LANGUAGE DataKinds, DeriveDataTypeable, TypeFamilies, TypeOperators #-}
-- module TutorialZipCode where
-- import Control.Monad (mzero)
-- import qualified Data.Char as C
-- import Data.Readable (Readable(fromText))
-- import qualified Data.Text as T
-- import Data.Typeable
-- import qualified Data.Vector as V
-- import Frames.InCore (VectorFor)
-- import Frames

-- data ZipT = ZipUS Int Int Int Int Int
--           | ZipWorld Char Char Char Char Char
--   deriving (Eq, Ord, Show, Typeable)

-- type instance VectorFor ZipT = V.Vector

-- instance Readable ZipT  where
--   fromText t
--       | T.length t == 5 = let cs@[v,w,x,y,z] = T.unpack t
--                               [a,b,c,d,e] = map C.digitToInt cs
--                           in if all C.isDigit cs
--                              then return $ ZipUS a b c d e
--                              else return $ ZipWorld v w x y z
--       | otherwise = mzero

-- type MyColumns = ZipT ': CommonColumns
-- #+end_src

-- ** Splice Dump

-- The Template Haskell splices we use produce quite a lot of
-- code. The raw dumps of these splices can be hard to read, but I
-- have included some elisp code for cleaning up that output in the
-- design notes for =Frames=. Here is what we get from the
-- @@html:<code>@@tableTypes'@@html:</code>@@ splice shown above.

-- The overall structure is this:

-- - A ~Record~ type called ~User~ with all necessary columns
-- - A ~userParser~ value that overrides parsing defaults
-- - A type synonym for each column that pairs the column name with its
--   type
-- - A lens to work with each column on any given row

-- Remember that for CSV files that include a header, the splice you
-- write in your code need not include the column names or separator
-- character.

-- -- #+begin_src haskell
--   tableTypes'
--     (rowGen
--        {rowTypeName = "User",
--         columnNames = ["user id", "age", "gender", "occupation",
--                        "zip code"],
--         separator = "|"})
--     "data/ml-100k/u.user"
-- ======>
--   type User =
--       Record ["user id" :-> Int, "age" :-> Int, "gender" :-> Text, "occupation" :-> Text, "zip code" :-> Text]

--   userParser :: ParserOptions
--   userParser
--     = ParserOptions
--         (Just
--            (map
--               T.pack
--               ["user id", "age", "gender", "occupation", "zip code"]))
--         (T.pack "|")

--   type UserId = "user id" :-> Int

--   userId ::
--     forall f_adkB rs_adkC. (Functor f_adkB,
--                             RElem UserId rs_adkC (RIndex UserId rs_adkC)) =>
--     (Int -> f_adkB Int) -> Record rs_adkC -> f_adkB (Record rs_adkC)
--   userId = rlens (Proxy :: Proxy UserId)

--   userId' ::
--     forall g_adkD f_adkE rs_adkF. (Functor f_adkE,
--                                    Functor g_adkD,
--                                    RElem UserId rs_adkF (RIndex UserId rs_adkF)) =>
--     (g_adkD UserId -> f_adkE (g_adkD UserId))
--     -> Rec g_adkD rs_adkF -> f_adkE (Rec g_adkD rs_adkF)
--   userId' = rlens' (Proxy :: Proxy UserId)

--   type Age = "age" :-> Int

--   age ::
--     forall f_adkG rs_adkH. (Functor f_adkG,
--                             RElem Age rs_adkH (RIndex Age rs_adkH)) =>
--     (Int -> f_adkG Int) -> Record rs_adkH -> f_adkG (Record rs_adkH)
--   age = rlens (Proxy :: Proxy Age)

--   age' ::
--     forall g_adkI f_adkJ rs_adkK. (Functor f_adkJ,
--                                    Functor g_adkI,
--                                    RElem Age rs_adkK (RIndex Age rs_adkK)) =>
--     (g_adkI Age -> f_adkJ (g_adkI Age))
--     -> Rec g_adkI rs_adkK -> f_adkJ (Rec g_adkI rs_adkK)
--   age' = rlens' (Proxy :: Proxy Age)

--   type Gender = "gender" :-> Text

--   gender ::
--     forall f_adkL rs_adkM. (Functor f_adkL,
--                             RElem Gender rs_adkM (RIndex Gender rs_adkM)) =>
--     (Text -> f_adkL Text) -> Record rs_adkM -> f_adkL (Record rs_adkM)
--   gender = rlens (Proxy :: Proxy Gender)

--   gender' ::
--     forall g_adkN f_adkO rs_adkP. (Functor f_adkO,
--                                    Functor g_adkN,
--                                    RElem Gender rs_adkP (RIndex Gender rs_adkP)) =>
--     (g_adkN Gender -> f_adkO (g_adkN Gender))
--     -> Rec g_adkN rs_adkP -> f_adkO (Rec g_adkN rs_adkP)
--   gender' = rlens' (Proxy :: Proxy Gender)

--   type Occupation = "occupation" :-> Text

--   occupation ::
--     forall f_adkQ rs_adkR. (Functor f_adkQ,
--                             RElem Occupation rs_adkR (RIndex Occupation rs_adkR)) =>
--     (Text -> f_adkQ Text) -> Record rs_adkR -> f_adkQ (Record rs_adkR)
--   occupation = rlens (Proxy :: Proxy Occupation)

--   occupation' ::
--     forall g_adkS f_adkT rs_adkU. (Functor f_adkT,
--                                    Functor g_adkS,
--                                    RElem Occupation rs_adkU (RIndex Occupation rs_adkU)) =>
--     (g_adkS Occupation -> f_adkT (g_adkS Occupation))
--     -> Rec g_adkS rs_adkU -> f_adkT (Rec g_adkS rs_adkU)
--   occupation' = rlens' (Proxy :: Proxy Occupation)

--   type ZipCode = "zip code" :-> Text

--   zipCode ::
--     forall f_adkV rs_adkW. (Functor f_adkV,
--                             RElem ZipCode rs_adkW (RIndex ZipCode rs_adkW)) =>
--     (Text -> f_adkV Text) -> Record rs_adkW -> f_adkV (Record rs_adkW)
--   zipCode = rlens (Proxy :: Proxy ZipCode)

--   zipCode' ::
--     forall g_adkX f_adkY rs_adkZ. (Functor f_adkY,
--                                    Functor g_adkX,
--                                    RElem ZipCode rs_adkZ (RIndex ZipCode rs_adkZ)) =>
--     (g_adkX ZipCode -> f_adkY (g_adkX ZipCode))
--     -> Rec g_adkX rs_adkZ -> f_adkY (Rec g_adkX rs_adkZ)
--   zipCode' = rlens' (Proxy :: Proxy ZipCode)
-- #+end_src

-- ** Thanks
-- Thanks to Greg Hale and Ben Gamari for reviewing early drafts of this document.

-- #+DATE:
-- #+TITLE: Frames Tutorial
-- #+OPTIONS: html-link-use-abs-url:nil html-postamble:nil
neOccupations = P.filter (isNewEngland . view u2zipCode)
                >-> P.map (view u2occupation)
  where isNewEngland (ZipUS 0 _ _ _ _) = True
        isNewEngland _ = False

-- #+BEGIN_EXAMPLE
-- λ> runEffect $ movieStream2 >-> neOccupations >-> P.take 10 >-> P.print
-- "administrator"
-- "student"
-- "other"
-- "programmer"
-- "librarian"
-- "entertainment"
-- "marketing"
-- "programmer"
-- "educator"
-- "healthcare"
-- #+END_EXAMPLE

-- So there we go! We've done both row and column subset queries with a
-- strongly typed query (namely, ~isNewEngland~). Another situation in
-- which one might want to define a custom universe of column types is
-- when dealing with dates. This would let you both reject rows with
-- badly formatted dates, for example, and efficiently query the data set
-- with richly-typed queries.

-- Even better, did you notice the types of ~writers~ and
-- ~neOccupations~? They are polymorphic over the full row type!
-- That's what the ~(Occupation ∈ rs)~ constraint signifies: such a
-- function will work for record types with any set of fields, ~rs~, so
-- long as ~Occupation~ is an element of that set. This means that if
-- your schema changes, or you switch to a related but different data
-- set, *these functions can still be used without even touching the
-- code*. Just recompile against the new data set, and you're good to go.

-- * Appendix
-- ** User Types

-- Here are the definitions needed to define the ~MyColumns~ type with
-- its more descriptive ~ZipT~ type. We have to define these things in
-- a separate module from our main work due to GHC's stage
-- restrictions regarding Template Haskell. Specifically, ~ZipT~ and
-- its instances are used at compile time to infer the record type
-- needed to represent the data file. Notice the extension point here
-- is not too rough: you prepend new, more refined, type compatibility
-- checks to the head of ~CommonColumns~, or you can build up your own
-- list of expected types.

-- This may not be something you'd want to do for every data
-- set. However, the /ability/ to refine the structure of parsed data
-- is in keeping with the overall goal of ~Frames~: it's easy to take
-- off, and the sky's the limit.

-- -- #+begin_src haskell
-- {-# LANGUAGE DataKinds, DeriveDataTypeable, TypeFamilies, TypeOperators #-}
-- module TutorialZipCode where
-- import Control.Monad (mzero)
-- import qualified Data.Char as C
-- import Data.Readable (Readable(fromText))
-- import qualified Data.Text as T
-- import Data.Typeable
-- import qualified Data.Vector as V
-- import Frames.InCore (VectorFor)
-- import Frames

-- data ZipT = ZipUS Int Int Int Int Int
--           | ZipWorld Char Char Char Char Char
--   deriving (Eq, Ord, Show, Typeable)

-- type instance VectorFor ZipT = V.Vector

-- instance Readable ZipT  where
--   fromText t
--       | T.length t == 5 = let cs@[v,w,x,y,z] = T.unpack t
--                               [a,b,c,d,e] = map C.digitToInt cs
--                           in if all C.isDigit cs
--                              then return $ ZipUS a b c d e
--                              else return $ ZipWorld v w x y z
--       | otherwise = mzero

-- type MyColumns = ZipT ': CommonColumns
-- #+end_src

-- ** Splice Dump

-- The Template Haskell splices we use produce quite a lot of
-- code. The raw dumps of these splices can be hard to read, but I
-- have included some elisp code for cleaning up that output in the
-- design notes for =Frames=. Here is what we get from the
-- @@html:<code>@@tableTypes'@@html:</code>@@ splice shown above.

-- The overall structure is this:

-- - A ~Record~ type called ~User~ with all necessary columns
-- - A ~userParser~ value that overrides parsing defaults
-- - A type synonym for each column that pairs the column name with its
--   type
-- - A lens to work with each column on any given row

-- Remember that for CSV files that include a header, the splice you
-- write in your code need not include the column names or separator
-- character.

-- -- #+begin_src haskell
--   tableTypes'
--     (rowGen
--        {rowTypeName = "User",
--         columnNames = ["user id", "age", "gender", "occupation",
--                        "zip code"],
--         separator = "|"})
--     "data/ml-100k/u.user"
-- ======>
--   type User =
--       Record ["user id" :-> Int, "age" :-> Int, "gender" :-> Text, "occupation" :-> Text, "zip code" :-> Text]

--   userParser :: ParserOptions
--   userParser
--     = ParserOptions
--         (Just
--            (map
--               T.pack
--               ["user id", "age", "gender", "occupation", "zip code"]))
--         (T.pack "|")

--   type UserId = "user id" :-> Int

--   userId ::
--     forall f_adkB rs_adkC. (Functor f_adkB,
--                             RElem UserId rs_adkC (RIndex UserId rs_adkC)) =>
--     (Int -> f_adkB Int) -> Record rs_adkC -> f_adkB (Record rs_adkC)
--   userId = rlens (Proxy :: Proxy UserId)

--   userId' ::
--     forall g_adkD f_adkE rs_adkF. (Functor f_adkE,
--                                    Functor g_adkD,
--                                    RElem UserId rs_adkF (RIndex UserId rs_adkF)) =>
--     (g_adkD UserId -> f_adkE (g_adkD UserId))
--     -> Rec g_adkD rs_adkF -> f_adkE (Rec g_adkD rs_adkF)
--   userId' = rlens' (Proxy :: Proxy UserId)

--   type Age = "age" :-> Int

--   age ::
--     forall f_adkG rs_adkH. (Functor f_adkG,
--                             RElem Age rs_adkH (RIndex Age rs_adkH)) =>
--     (Int -> f_adkG Int) -> Record rs_adkH -> f_adkG (Record rs_adkH)
--   age = rlens (Proxy :: Proxy Age)

--   age' ::
--     forall g_adkI f_adkJ rs_adkK. (Functor f_adkJ,
--                                    Functor g_adkI,
--                                    RElem Age rs_adkK (RIndex Age rs_adkK)) =>
--     (g_adkI Age -> f_adkJ (g_adkI Age))
--     -> Rec g_adkI rs_adkK -> f_adkJ (Rec g_adkI rs_adkK)
--   age' = rlens' (Proxy :: Proxy Age)

--   type Gender = "gender" :-> Text

--   gender ::
--     forall f_adkL rs_adkM. (Functor f_adkL,
--                             RElem Gender rs_adkM (RIndex Gender rs_adkM)) =>
--     (Text -> f_adkL Text) -> Record rs_adkM -> f_adkL (Record rs_adkM)
--   gender = rlens (Proxy :: Proxy Gender)

--   gender' ::
--     forall g_adkN f_adkO rs_adkP. (Functor f_adkO,
--                                    Functor g_adkN,
--                                    RElem Gender rs_adkP (RIndex Gender rs_adkP)) =>
--     (g_adkN Gender -> f_adkO (g_adkN Gender))
--     -> Rec g_adkN rs_adkP -> f_adkO (Rec g_adkN rs_adkP)
--   gender' = rlens' (Proxy :: Proxy Gender)

--   type Occupation = "occupation" :-> Text

--   occupation ::
--     forall f_adkQ rs_adkR. (Functor f_adkQ,
--                             RElem Occupation rs_adkR (RIndex Occupation rs_adkR)) =>
--     (Text -> f_adkQ Text) -> Record rs_adkR -> f_adkQ (Record rs_adkR)
--   occupation = rlens (Proxy :: Proxy Occupation)

--   occupation' ::
--     forall g_adkS f_adkT rs_adkU. (Functor f_adkT,
--                                    Functor g_adkS,
--                                    RElem Occupation rs_adkU (RIndex Occupation rs_adkU)) =>
--     (g_adkS Occupation -> f_adkT (g_adkS Occupation))
--     -> Rec g_adkS rs_adkU -> f_adkT (Rec g_adkS rs_adkU)
--   occupation' = rlens' (Proxy :: Proxy Occupation)

--   type ZipCode = "zip code" :-> Text

--   zipCode ::
--     forall f_adkV rs_adkW. (Functor f_adkV,
--                             RElem ZipCode rs_adkW (RIndex ZipCode rs_adkW)) =>
--     (Text -> f_adkV Text) -> Record rs_adkW -> f_adkV (Record rs_adkW)
--   zipCode = rlens (Proxy :: Proxy ZipCode)

--   zipCode' ::
--     forall g_adkX f_adkY rs_adkZ. (Functor f_adkY,
--                                    Functor g_adkX,
--                                    RElem ZipCode rs_adkZ (RIndex ZipCode rs_adkZ)) =>
--     (g_adkX ZipCode -> f_adkY (g_adkX ZipCode))
--     -> Rec g_adkX rs_adkZ -> f_adkY (Rec g_adkX rs_adkZ)
--   zipCode' = rlens' (Proxy :: Proxy ZipCode)
-- #+end_src

-- ** Thanks
-- Thanks to Greg Hale and Ben Gamari for reviewing early drafts of this document.

-- #+DATE:
-- #+TITLE: Frames Tutorial
-- #+OPTIONS: html-link-use-abs-url:nil html-postamble:nil