diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
 # Revision history for reflex-libtelnet
 
+## 0.2.0.0 -- 2022-05-21
+
+* Remove `instance Reflex t => Default (TelnetConfig t)`; use
+  `newTelnetConfig` instead.
+
+* New instances:
+  - `instance Generic (TelnetConfig t)`
+  - `instance Generic (TelnetEvents t)`
+
+* Remove the dependency on `lens`:
+  - As GHC is now better at dealing with duplicate record fields, we
+    can give them nice unprefixed names.
+  - Use
+    [`generic-lens`](https://hackage.haskell.org/package/generic-lens)
+    or
+    [`generic-optics`](https://hackage.haskell.org/package/generic-optics)
+    to get lenses for your preferred flavour of optic. Make sure to
+    use the `field' @"fieldName"` version (i.e. not `#fieldName` or
+    un-primed `field`), otherwise GHC will struggle to infer the type
+    of `t` when setting fields in `ReflexConfig t`.
+
 ## 0.1.0.0 -- 2019-12-03
 
 * First version. Released on an unsuspecting world.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -14,17 +14,26 @@
 
 1. Assemble a `config :: TelnetConfig t`, which holds the event
    streams that feed into a telnet state tracker. The easiest way to
-   do this is to call `def` (there is an `instance Reflex t => Default
-   (TelnetConfig t)`) and overwrite the fields you care about using
-   record updates or lenses. You will almost certainly want to
-   overwrite `_cRecv` to be the stream of incoming data from a socket,
-   and `_cSend` to be the stream of outgoing data from your application.
+   do this is to call `newTelnetConfig` and overwrite the fields you
+   care about using record updates or lenses. You will almost
+   certainly want to overwrite `recv` to be the stream of incoming
+   data from a socket, and `send` to be the stream of outgoing data
+   from your application.
 
+   * If you want to use lenses, use the
+     [`generic-lens`](https://hackage.haskell.org/package/generic-lens)
+     or
+     [`generic-optics`](https://hackage.haskell.org/package/generic-optics)
+     to get your preferred flavour of optic. Make sure to use the
+     `field' @"fieldName"` version (i.e. not `#fieldName` or un-primed
+     `field`), otherwise GHC will struggle to infer the type of `t`
+     when setting fields in `ReflexConfig t`.
+
 2. Call `telnet config`, and save the resulting `TelnetEvents t`. This
    structure holds the output event streams from a single telnet state
    tracker.
 
-3. Wire the ouput events into your application.
+3. Wire the output events into your application.
 
 ## Other Resources
 
diff --git a/reflex-libtelnet.cabal b/reflex-libtelnet.cabal
--- a/reflex-libtelnet.cabal
+++ b/reflex-libtelnet.cabal
@@ -1,37 +1,42 @@
-cabal-version:       2.2
-name:                reflex-libtelnet
-version:             0.1.0.0
-synopsis:            Reflex bindings for libtelnet
-description:         Wraps https://git.sr.ht/~jack/libtelnet-haskell , so you
-                     can handle telnet data streams using reflex
-                     'Reflex.Event's. See @README.md@ or
-                     "Reflex.LibTelnet" to get started.
-homepage:            https://git.sr.ht/~jack/reflex-libtelnet
-bug-reports:         https://todo.sr.ht/~jack/reflex-libtelnet
-license:             GPL-3.0-or-later
-license-file:        LICENSE
-author:              Jack Kelly
-maintainer:          jack@jackkelly.name
-copyright:           (c) 2019 Jack Kelly
-category:            Network
-build-type:          Simple
-extra-source-files:  CHANGELOG.md, README.md
-tested-with:         GHC == 8.6.5
+cabal-version:      2.2
+name:               reflex-libtelnet
+version:            0.2.0.0
+synopsis:           Reflex bindings for libtelnet
+description:
+  Wraps https://git.sr.ht/~jack/libtelnet-haskell , so you
+  can handle telnet data streams using reflex
+  'Reflex.Event's. See @README.md@ or
+  "Reflex.LibTelnet" to get started.
 
+homepage:           https://git.sr.ht/~jack/reflex-libtelnet
+bug-reports:        https://todo.sr.ht/~jack/reflex-libtelnet
+license:            GPL-3.0-or-later
+license-file:       LICENSE
+author:             Jack Kelly
+maintainer:         jack@jackkelly.name
+copyright:          (c) 2019, 2022 Jack Kelly
+category:           Network
+build-type:         Simple
+extra-source-files:
+  CHANGELOG.md
+  README.md
+
+tested-with:        GHC ==8.8.4 || ==8.10.7 || ==9.0.2
+
 library
-  ghc-options:         -Wall
-  exposed-modules:     Reflex.LibTelnet
-  build-depends:       base >= 4.11 && < 4.13
-                     , bytestring >= 0.10.8.2 && < 0.11
-                     , data-default >= 0.7.1.1 && < 0.8
-                     , dependent-map >= 0.3 && < 0.4
-                     , dependent-sum >= 0.6.2.0 && <= 0.7
-                     , dependent-sum-template >= 0.1 && < 0.2
-                     , lens >= 4.16.1 && < 4.18
-                     , libtelnet >= 0.1 && < 0.2
-                     , reflex >= 0.5 && < 0.7
-  hs-source-dirs:      src
-  default-language:    Haskell2010
+  ghc-options:      -Wall
+  exposed-modules:  Reflex.LibTelnet
+  build-depends:
+    , base                    >=4.11     && <4.16
+    , bytestring              >=0.10.8.2 && <0.12
+    , dependent-map           >=0.3      && <0.5
+    , dependent-sum           >=0.6.2.0  && <=0.8
+    , dependent-sum-template  ^>=0.1
+    , libtelnet               ^>=0.1
+    , reflex                  >=0.5      && <0.9
+
+  hs-source-dirs:   src
+  default-language: Haskell2010
 
 source-repository head
   type:     git
diff --git a/src/Reflex/LibTelnet.hs b/src/Reflex/LibTelnet.hs
--- a/src/Reflex/LibTelnet.hs
+++ b/src/Reflex/LibTelnet.hs
@@ -1,14 +1,16 @@
-{-# LANGUAGE FlexibleContexts    #-}
-{-# LANGUAGE GADTs               #-}
-{-# LANGUAGE LambdaCase          #-}
-{-# LANGUAGE RankNTypes          #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE TemplateHaskell     #-}
+{-# LANGUAGE DeriveGeneric         #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE GADTs                 #-}
+{-# LANGUAGE LambdaCase            #-}
+{-# LANGUAGE RankNTypes            #-}
+{-# LANGUAGE ScopedTypeVariables   #-}
+{-# LANGUAGE TemplateHaskell       #-}
 
 {-|
 Module      : Reflex.LibTelnet
 Description : Reflex wrapper around libtelnet
-Copyright   : (c) 2019 Jack Kelly
+Copyright   : (c) 2019, 2022 Jack Kelly
 License     : GPL-3.0-or-later
 Maintainer  : jack@jackkelly.name
 Stability   : experimental
@@ -16,8 +18,8 @@
 
 How to run a libtelnet state tracker off Reflex 'Event's:
 
-1. Construct a 'TelnetConfig' by using 'def' to get an empty config,
-   and then fill it out using record updates or lenses.
+1. Construct a 'TelnetConfig' by using 'newTelnetConfig' to get an
+   empty config, and then fill it out using record updates or lenses.
 
 2. Start a state tracker by calling 'telnet' on your config.
 
@@ -30,58 +32,21 @@
 
     -- * Input Events
   , TelnetConfig(..)
+  , newTelnetConfig
 
     -- * Output Events
   , TelnetEvents(..)
-
-    -- * Lenses
-    -- ** TelnetConfig
-  , cOptions
-  , cFlags
-  , cRecv
-  , cSend
-  , cIac
-  , cNegotiate
-  , cSubnegotiation
-  , cBeginCompress2
-  , cNewEnvironSend
-  , cNewEnviron
-  , cTTypeSend
-  , cTTypeIs
-  , cSendZmp
-  , cSendMssp
-
-    -- ** TelnetEvents
-  , eReceived
-  , eSend
-  , eWarning
-  , eError
-  , eIac
-  , eWill
-  , eWont
-  , eDo
-  , eDont
-  , eSubnegotiation
-  , eZmp
-  , eTerminalTypeSend
-  , eTerminalTypeIs
-  , eCompress
-  , eEnvironSend
-  , eEnviron
-  , eMssp
-  , eException
   ) where
 
 import           Control.Exception (catch)
-import           Control.Lens (Lens', (^.), makeLenses)
 import           Control.Monad.IO.Class (MonadIO(..))
 import           Data.ByteString (ByteString)
-import           Data.Default (Default(..))
 import qualified Data.Dependent.Map as DMap
 import           Data.Dependent.Sum ((==>))
 import           Data.Functor ((<&>))
 import           Data.GADT.Compare.TH (deriveGCompare, deriveGEq)
 import           Data.GADT.Show.TH (deriveGShow)
+import           GHC.Generics (Generic)
 import           Network.Telnet.LibTelnet (Telnet)
 import qualified Network.Telnet.LibTelnet as Telnet
 import           Network.Telnet.LibTelnet.Iac (Iac)
@@ -94,126 +59,124 @@
 -- event" describes all events the state tracker listens to; it
 -- doesn't care whether the events are coming from "above" or "below"
 -- itself in the application stack. Data arriving on the socket
--- ('_cRecv') is an "input event" from "below"; data pasing through
--- libtelnet on its way out to the socket ('_cSend') is also an "input
+-- (@recv@) is an "input event" from "below"; data pasing through
+-- libtelnet on its way out to the socket (@send@) is also an "input
 -- event", but from "above".
 --
 -- You will almost certainly want to:
 --
--- 1. use 'def' to get an empty 'TelnetConfig';
--- 2. replace the '_cRecv' event with incoming socket data; and
--- 3. replace the '_cSend' event with outgoing data from your application.
+-- 1. use 'newTelnetConfig' to get an empty 'TelnetConfig';
+-- 2. replace the @recv@ event with incoming socket data; and
+-- 3. replace the @send@ event with outgoing data from your application.
 --
 -- @since 0.1.0.0
 data TelnetConfig t = TelnetConfig
-  { _cOptions :: [Telnet.OptionSpec]
+  { options :: [Telnet.OptionSpec]
     -- ^ Passed to 'Telnet.telnetInit'
-  , _cFlags :: [Telnet.Flag]
+  , flags :: [Telnet.Flag]
     -- ^ Passed to 'Telnet.telnetInit'
-  , _cRecv :: Event t ByteString
+  , recv :: Event t ByteString
     -- ^ 'Telnet.telnetRecv' - "I just received this data, please decode it"
-  , _cSend :: Event t ByteString
+  , send :: Event t ByteString
     -- ^ 'Telnet.telnetSend' - "I want to send this data out, please encode it"
-  , _cIac :: Event t Iac
+  , iac :: Event t Iac
     -- ^ 'Telnet.telnetIac'
-  , _cNegotiate :: Event t (Iac, Option)
+  , negotiate :: Event t (Iac, Option)
     -- ^ 'Telnet.telnetNegotiate'
-  , _cSubnegotiation :: Event t (Option, ByteString)
+  , subnegotiation :: Event t (Option, ByteString)
     -- ^ 'Telnet.telnetSubnegotiation'
-  , _cBeginCompress2 :: Event t ()
+  , beginCompress2 :: Event t ()
     -- ^ 'Telnet.telnetBeginCompress2'
-  , _cNewEnvironSend :: Event t [(Telnet.Var, ByteString)]
+  , newEnvironSend :: Event t [(Telnet.Var, ByteString)]
     -- ^ 'Telnet.telnetNewEnvironSend'
-  , _cNewEnviron :: Event t ( Telnet.IsInfo
+  , newEnviron :: Event t ( Telnet.IsInfo
                             , [(Telnet.Var, ByteString, ByteString)]
                             )
     -- ^ 'Telnet.telnetNewEnviron'
-  , _cTTypeSend :: Event t ()
+  , tTypeSend :: Event t ()
     -- ^ 'Telnet.telnetTTypeSend'
-  , _cTTypeIs :: Event t ByteString
+  , tTypeIs :: Event t ByteString
     -- ^ 'Telnet.telnetTTypeIs'
-  , _cSendZmp :: Event t [ByteString]
+  , sendZmp :: Event t [ByteString]
     -- ^ 'Telnet.telnetSendZmp'
-  , _cSendMssp :: Event t [(ByteString, [ByteString])]
+  , sendMssp :: Event t [(ByteString, [ByteString])]
     -- ^ 'Telnet.telnetSendMssp'
   }
-
-$(makeLenses ''TelnetConfig)
+  deriving Generic -- ^ @since 0.2.0.0
 
 -- | No options set and all events are 'never'.
 --
--- @since 0.1.0.0
-instance Reflex t => Default (TelnetConfig t) where
-  def = TelnetConfig
-    { _cOptions = []
-    , _cFlags = []
-    , _cRecv = never
-    , _cSend = never
-    , _cIac = never
-    , _cNegotiate = never
-    , _cSubnegotiation = never
-    , _cBeginCompress2 = never
-    , _cNewEnvironSend = never
-    , _cNewEnviron = never
-    , _cTTypeSend = never
-    , _cTTypeIs = never
-    , _cSendZmp = never
-    , _cSendMssp = never
-    }
+-- @since 0.2.0.0
+newTelnetConfig :: Reflex t => TelnetConfig t
+newTelnetConfig = TelnetConfig
+  { options = []
+  , flags = []
+  , recv = never
+  , send = never
+  , iac = never
+  , negotiate = never
+  , subnegotiation = never
+  , beginCompress2 = never
+  , newEnvironSend = never
+  , newEnviron = never
+  , tTypeSend = never
+  , tTypeIs = never
+  , sendZmp = never
+  , sendMssp = never
+  }
 
 -- | The libtelnet 'Telnet.Event' type is fanned out into a set of
 -- individual "output events". An "output event" describes all events
 -- that should be listened to, regardless of whether they are going
--- "up" or "down" the application stack: parsed data ('_eReceived') is
+-- "up" or "down" the application stack: parsed data (@received@) is
 -- an "output event" that should be listened to by the layer "above";
--- encoded data that should go to a socket ('_eSend') is also an
+-- encoded data that should go to a socket (@send@) is also an
 -- "output event", but listened to by the layer below.
 --
 -- @since 0.1.0.0
 data TelnetEvents t = TelnetEvents
-  { _eReceived :: Event t ByteString
+  { received :: Event t ByteString
     -- ^ 'Telnet.Received' - "Here is some decoded data, please send
     -- it up to the application"
-  , _eSend :: Event t ByteString
+  , send :: Event t ByteString
     -- ^ 'Telnet.Send' - "Here is some encoded data, please send it
     -- out on the socket"
-  , _eWarning :: Event t Telnet.Err
+  , warning :: Event t Telnet.Err
     -- ^ 'Telnet.Warning'
-  , _eError :: Event t Telnet.Err
+  , error :: Event t Telnet.Err
     -- ^ 'Telnet.Error'
-  , _eIac :: Event t Iac
+  , iac :: Event t Iac
     -- ^ 'Telnet.Iac'
-  , _eWill :: Event t Option
+  , will :: Event t Option
     -- ^ 'Telnet.Will'
-  , _eWont :: Event t Option
+  , wont :: Event t Option
     -- ^ 'Telnet.Wont'
-  , _eDo :: Event t Option
+  , do_ :: Event t Option
     -- ^ 'Telnet.Do'
-  , _eDont :: Event t Option
+  , dont :: Event t Option
     -- ^ 'Telnet.Dont'
-  , _eSubnegotiation :: Event t (Option, ByteString)
+  , subnegotiation :: Event t (Option, ByteString)
     -- ^ 'Telnet.Subnegotiation'
-  , _eZmp :: Event t [ByteString]
+  , zmp :: Event t [ByteString]
     -- ^ 'Telnet.Zmp'
-  , _eTerminalTypeSend :: Event t ()
+  , terminalTypeSend :: Event t ()
     -- ^ 'Telnet.TerminalTypeSend'
-  , _eTerminalTypeIs :: Event t ByteString
+  , terminalTypeIs :: Event t ByteString
     -- ^ 'Telnet.TerminalTypeIs'
-  , _eCompress :: Event t Bool
+  , compress :: Event t Bool
     -- ^ 'Telnet.Compress'
-  , _eEnvironSend :: Event t [(Telnet.Var, ByteString)]
+  , environSend :: Event t [(Telnet.Var, ByteString)]
     -- ^ 'Telnet.EnvironSend'
-  , _eEnviron :: Event t (Telnet.IsInfo, [(Telnet.Var, ByteString, ByteString)])
+  , environ :: Event t (Telnet.IsInfo, [(Telnet.Var, ByteString, ByteString)])
     -- ^ 'Telnet.Environ'
-  , _eMssp :: Event t [(ByteString, [ByteString])]
+  , mssp :: Event t [(ByteString, [ByteString])]
     -- ^ 'Telnet.Mssp'
-  , _eException :: Event t Telnet.TelnetException
+  , exception :: Event t Telnet.TelnetException
     -- ^ Exceptions thrown by the binding are caught and emitted
     -- here. Protocol errors and warnings are emitted on the
-    -- '_eWarning' and '_eError' events.
+    -- @warning@ and @error@ events.
   }
-
-$(makeLenses ''TelnetEvents)
+  deriving Generic -- ^ @since 0.2.0.0
 
 data EventKey a where
   Received :: EventKey ByteString
@@ -239,9 +202,9 @@
 $(deriveGShow ''EventKey)
 
 -- | Process telnet streams using
--- <https://github.com/seanmiddleditch/libtelnet libtelnet>. The
--- easiest way to get a @'TelnetConfig' t@ is through its 'Default'
--- instance; see 'TelnetConfig' for details.
+-- <https://github.com/seanmiddleditch/libtelnet libtelnet>. Use
+-- 'newTelnetConfig' to get a @'TelnetConfig' t@, and update it as
+-- required.
 --
 -- @since 0.1.0.0
 telnet
@@ -256,31 +219,31 @@
   (telnetE, fireTelnetE) <- newTriggerEvent
   (telnetExcE, fireTelnetExcE) <- newTriggerEvent
   t <- liftIO $ Telnet.telnetInit
-    (config ^. cOptions)
-    (config ^. cFlags)
+    (options config)
+    (flags config)
     (const fireTelnetE)
 
   let
     perform
       :: (Telnet -> a -> IO ())
-      -> Lens' (TelnetConfig t) (Event t a)
+      -> (TelnetConfig t -> Event t a)
       -> m ()
-    perform f l = performEvent_ $ action <$> config ^. l where
-      action x = liftIO $ f t x `catch` \(ex :: Telnet.TelnetException) ->
+    perform act get = performEvent_ $ action <$> get config where
+      action x = liftIO $ act t x `catch` \(ex :: Telnet.TelnetException) ->
         fireTelnetExcE ex
 
-  perform Telnet.telnetRecv cRecv
-  perform Telnet.telnetSend cSend
-  perform Telnet.telnetIac cIac
-  perform (uncurry . Telnet.telnetNegotiate) cNegotiate
-  perform (uncurry . Telnet.telnetSubnegotiation) cSubnegotiation
-  perform (const . Telnet.telnetBeginCompress2) cBeginCompress2
-  perform Telnet.telnetNewEnvironSend cNewEnvironSend
-  perform (uncurry . Telnet.telnetNewEnviron) cNewEnviron
-  perform (const . Telnet.telnetTTypeSend) cTTypeSend
-  perform Telnet.telnetTTypeIs cTTypeIs
-  perform Telnet.telnetSendZmp cSendZmp
-  perform Telnet.telnetSendMssp cSendMssp
+  perform Telnet.telnetRecv recv
+  perform Telnet.telnetSend send
+  perform Telnet.telnetIac iac
+  perform (uncurry . Telnet.telnetNegotiate) negotiate
+  perform (uncurry . Telnet.telnetSubnegotiation) subnegotiation
+  perform (const . Telnet.telnetBeginCompress2) beginCompress2
+  perform Telnet.telnetNewEnvironSend newEnvironSend
+  perform (uncurry . Telnet.telnetNewEnviron) newEnviron
+  perform (const . Telnet.telnetTTypeSend) tTypeSend
+  perform Telnet.telnetTTypeIs tTypeIs
+  perform Telnet.telnetSendZmp sendZmp
+  perform Telnet.telnetSendMssp sendMssp
 
   let
     selector :: EventSelector t EventKey
@@ -304,22 +267,22 @@
       Telnet.Mssp msg -> Mssp ==> msg
 
   pure $ TelnetEvents
-    { _eReceived = selector `select` Received
-    , _eSend = selector `select` Send
-    , _eWarning = selector `select` Warning
-    , _eError = selector `select` Error
-    , _eIac = selector `select` Iac
-    , _eWill  = selector `select` Will
-    , _eWont  = selector `select` Wont
-    , _eDo  = selector `select` Do
-    , _eDont  = selector `select` Dont
-    , _eSubnegotiation  = selector `select` Subnegotiation
-    , _eZmp  = selector `select` Zmp
-    , _eTerminalTypeSend  = selector `select` TerminalTypeSend
-    , _eTerminalTypeIs  = selector `select` TerminalTypeIs
-    , _eCompress  = selector `select` Compress
-    , _eEnvironSend  = selector `select` EnvironSend
-    , _eEnviron  = selector `select` Environ
-    , _eMssp  = selector `select` Mssp
-    , _eException = telnetExcE
+    { received = selector `select` Received
+    , send = selector `select` Send
+    , warning = selector `select` Warning
+    , error = selector `select` Error
+    , iac = selector `select` Iac
+    , will = selector `select` Will
+    , wont = selector `select` Wont
+    , do_ = selector `select` Do
+    , dont = selector `select` Dont
+    , subnegotiation  = selector `select` Subnegotiation
+    , zmp = selector `select` Zmp
+    , terminalTypeSend = selector `select` TerminalTypeSend
+    , terminalTypeIs = selector `select` TerminalTypeIs
+    , compress = selector `select` Compress
+    , environSend = selector `select` EnvironSend
+    , environ = selector `select` Environ
+    , mssp = selector `select` Mssp
+    , exception = telnetExcE
     }
