diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,9 +3,20 @@
 All notable changes to this project (as seen by library users) will be documented in this file.
 The CHANGELOG is available on [Github](https://github.com/luc-tielen/souffle-haskell.git/CHANGELOG.md).
 
+## [3.3.0] - 2022-02-27
+
+### Added
+
+- New `DerivingVia`-style API for binding to a Datalog program.
+
 ## [3.2.0] - 2022-02-20
 
+### Added
+
 - Add `Analysis` type for composing multiple Datalog programs.
+
+### Changed
+
 - souffle-haskell now supports Souffle version 2.2.
 
 ## [3.1.0] - 2021-09-30
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -43,8 +43,11 @@
 
 ```haskell
 -- Enable some necessary extensions:
-{-# LANGUAGE ScopedTypeVariables, DataKinds, TypeFamilies, DeriveGeneric #-}
+{-# LANGUAGE DeriveGeneric, DeriveAnyClass, DerivingVia, DataKinds, UndecidableInstances #-}
 
+-- NOTE: The usage of "deriving stock", "deriving anyclass" and "deriving via" in the
+-- examples below matters in order for the library to work correctly!
+
 module Main ( main ) where
 
 import Data.Foldable ( traverse_ )
@@ -54,40 +57,33 @@
 import qualified Language.Souffle.Compiled as Souffle
 
 
--- We define a data type representing our datalog program.
+-- First, we define a data type representing our datalog program.
 data Path = Path
+  -- By making Path an instance of Program, we provide Haskell with information
+  -- about the datalog program. It uses this to perform compile-time checks to
+  -- limit the amount of possible programmer errors to a minimum.
+  deriving Souffle.Program
+  via Souffle.ProgramOptions Path "path" '[Edge, Reachable]
 
 -- Facts are represented in Haskell as simple product types,
 -- Numbers map to Int32, unsigned to Word32, floats to Float,
 -- symbols to Strings / Text.
 
 data Edge = Edge String String
-  deriving (Eq, Show, Generic)
+  deriving stock (Eq, Show, Generic)
+  -- For simple product types, we can automatically generate the
+  -- marshalling/unmarshalling code of data between Haskell and datalog.
+  deriving anyclass Souffle.Marshal
+  -- By making a data type an instance of Fact, we give Haskell the
+  -- necessary information to bind to the datalog fact.
+  deriving Souffle.Fact
+  via Souffle.FactOptions Edge "edge" 'Souffle.Input
 
 data Reachable = Reachable String String
-  deriving (Eq, Show, Generic)
-
--- By making Path an instance of Program, we provide Haskell with information
--- about the datalog program. It uses this to perform compile-time checks to
--- limit the amount of possible programmer errors to a minimum.
-instance Souffle.Program Path where
-  type ProgramFacts Path = [Edge, Reachable]
-  programName = const "path"
-
--- By making a data type an instance of Fact, we give Haskell the
--- necessary information to bind to the datalog fact.
-instance Souffle.Fact Edge where
-  type FactDirection Edge = 'Souffle.Input
-  factName = const "edge"
-
-instance Souffle.Fact Reachable where
-  type FactDirection Reachable = 'Souffle.Output
-  factName = const "reachable"
-
--- For simple product types, we can automatically generate the
--- marshalling/unmarshalling code of data between Haskell and datalog.
-instance Souffle.Marshal Edge
-instance Souffle.Marshal Reachable
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass Souffle.Marshal
+  deriving Souffle.Fact
+  via Souffle.FactOptions Reachable "reachable" 'Souffle.Output
 
 
 main :: IO ()
diff --git a/lib/Language/Souffle/Analysis.hs b/lib/Language/Souffle/Analysis.hs
--- a/lib/Language/Souffle/Analysis.hs
+++ b/lib/Language/Souffle/Analysis.hs
@@ -40,48 +40,62 @@
            -> m b         -- ^ Function for retrieving the 'Analysis' results from Souffle.
            -> Analysis m a b
 mkAnalysis f r g = Analysis f r (const g)
+{-# INLINABLE mkAnalysis #-}
 
 -- | Converts an 'Analysis' into an effectful function, so it can be executed.
 execAnalysis :: Applicative m => Analysis m a b -> (a -> m b)
 execAnalysis (Analysis f r g) a = f a *> r *> g a
+{-# INLINABLE execAnalysis #-}
 
 instance Functor m => Functor (Analysis m a) where
   fmap func (Analysis f r g) =
     Analysis f r (fmap func <$> g)
+  {-# INLINABLE fmap #-}
 
 instance Functor m => Profunctor (Analysis m) where
   lmap fn (Analysis f r g) =
     Analysis (lmap fn f) r (lmap fn g)
+  {-# INLINABLE lmap #-}
+
   rmap = fmap
+  {-# INLINABLE rmap #-}
 
 instance (Monoid (m ()), Applicative m) => Applicative (Analysis m a) where
   pure a = Analysis mempty mempty (const $ pure a)
+  {-# INLINABLE pure #-}
 
   Analysis f1 r1 g1 <*> Analysis f2 r2 g2 =
     Analysis (f1 <> f2) (r1 <> r2) (\a -> g1 a <*> g2 a)
+  {-# INLINABLE (<*>) #-}
 
 instance (Semigroup (m ()), Semigroup (m b)) => Semigroup (Analysis m a b) where
   Analysis f1 r1 g1 <> Analysis f2 r2 g2 =
     Analysis (f1 <> f2) (r1 <> r2) (g1 <> g2)
+  {-# INLINABLE (<>) #-}
 
 instance (Monoid (m ()), Monoid (m b)) => Monoid (Analysis m a b) where
   mempty = Analysis mempty mempty mempty
+  {-# INLINABLE mempty #-}
 
 instance (Monoid (m ()), Monad m) => Category (Analysis m) where
   id = Analysis mempty mempty pure
+  {-# INLINABLE id #-}
 
   Analysis f1 r1 g1 . Analysis f2 r2 g2 = Analysis f r1 g
     where
       f = execAnalysis (Analysis f2 r2 g2) >=> f1
       -- NOTE: lazyness avoids work here in g2 in cases where "const" is used
       g = g2 >=> g1
+  {-# INLINABLE (.) #-}
 
 instance Functor m => Strong (Analysis m) where
   first' (Analysis f r g) =
     Analysis (f . fst) r $ \(b, d) -> (,d) <$> g b
+  {-# INLINABLE first' #-}
 
   second' (Analysis f r g) =
     Analysis (f . snd) r $ \(d, b) -> (d,) <$> g b
+  {-# INLINABLE second' #-}
 
 instance Applicative m => Choice (Analysis m) where
   left' (Analysis f r g) = Analysis f' r g'
@@ -92,6 +106,7 @@
       g' = \case
         Left b -> Left <$> g b
         Right d -> pure $ Right d
+  {-# INLINABLE left' #-}
 
   right' (Analysis f r g) = Analysis f' r g'
     where
@@ -101,27 +116,35 @@
       g' = \case
         Left d -> pure $ Left d
         Right b -> Right <$> g b
+  {-# INLINABLE right' #-}
 
 instance (Monad m, Monoid (m ()), Category (Analysis m)) => Arrow (Analysis m) where
   arr f = Analysis mempty mempty (pure . f)
+  {-# INLINABLE arr #-}
 
   first = first'
+  {-# INLINABLE first #-}
 
   second = second'
+  {-# INLINABLE second #-}
 
   Analysis f1 r1 g1 *** Analysis f2 r2 g2 =
     Analysis (\(b, b') -> f1 b *> f2 b') (r1 <> r2) $ \(b, b') -> do
       c <- g1 b
       c' <- g2 b'
       pure (c, c')
+  {-# INLINABLE (***) #-}
 
   Analysis f1 r1 g1 &&& Analysis f2 r2 g2 =
     Analysis (f1 <> f2) (r1 <> r2) $ \b -> (,) <$> g1 b <*> g2 b
+  {-# INLINABLE (&&&) #-}
 
 instance (Monad m, Monoid (m ())) => ArrowChoice (Analysis m) where
   left = left'
+  {-# INLINABLE left #-}
 
   right = right'
+  {-# INLINABLE right #-}
 
   Analysis f1 r1 g1 +++ Analysis f2 r2 g2 = Analysis f' (r1 <> r2) g'
     where
@@ -131,4 +154,5 @@
       g' = \case
         Left b -> Left <$> g1 b
         Right b' -> Right <$> g2 b'
+  {-# INLINABLE (+++) #-}
 
diff --git a/lib/Language/Souffle/Class.hs b/lib/Language/Souffle/Class.hs
--- a/lib/Language/Souffle/Class.hs
+++ b/lib/Language/Souffle/Class.hs
@@ -1,5 +1,5 @@
 {-# LANGUAGE DataKinds, UndecidableInstances, FlexibleContexts #-}
-{-# LANGUAGE TypeFamilies, TypeOperators #-}
+{-# LANGUAGE TypeFamilies, TypeOperators, TypeApplications #-}
 
 -- | This module provides the top level API for Souffle related operations.
 --   It makes use of Haskell's powerful typesystem to make certain invalid states
@@ -15,7 +15,9 @@
 --   type safety and user-friendly error messages.
 module Language.Souffle.Class
   ( Program(..)
+  , ProgramOptions(..)
   , Fact(..)
+  , FactOptions(..)
   , Marshal.Marshal(..)
   , Direction(..)
   , ContainsInputFact
@@ -35,6 +37,7 @@
 import Data.Proxy
 import Data.Kind
 import Data.Word
+import GHC.TypeLits
 import qualified Language.Souffle.Marshal as Marshal
 import Type.Errors.Pretty
 
@@ -115,6 +118,29 @@
   --   This has to be the same as the name of the .dl file (minus the extension).
   programName :: a -> String
 
+-- | A helper data type, used in combination with the DerivingVia extension to
+--   automatically generate code to bind Haskell to a Souffle Datalog program.
+--
+-- The following is an example how to bind to a Datalog program "path"
+-- (saved as path.dl / path.cpp), that uses two facts called "edge" and
+-- "reachable" (represented with the Edge and Reachable types):
+--
+-- @
+-- data Path = Path
+--   deriving Souffle.Program
+--   via Souffle.ProgramOptions Path "path" '[Edge, Reachable]
+-- @
+--
+-- See also: 'FactOptions'.
+newtype ProgramOptions (prog :: Type) (progName :: Symbol) (facts :: [Type])
+  = ProgramOptions prog
+
+instance KnownSymbol progName => Program (ProgramOptions prog progName facts) where
+  type ProgramFacts (ProgramOptions _ _ facts) = facts
+
+  programName = const $ symbolVal (Proxy @progName)
+  {-# INLINABLE programName #-}
+
 -- | A typeclass for data types representing a fact in datalog.
 --
 -- Example usage:
@@ -135,6 +161,41 @@
   --
   -- It uses a 'Proxy' to select the correct instance.
   factName :: Proxy a -> String
+
+-- | A helper data type, used in combination with the DerivingVia extension to
+--   automatically generate code to bind a Haskell datatype to a Souffle
+--   Datalog fact.
+--
+-- The following is an example how to bind to a Datalog fact "edge"
+-- that contains two symbols (strings in Haskell) that is an input (from the
+-- Datalog point of view):
+--
+-- @
+-- data Edge = Edge String String
+--   deriving (Eq, Show, Generic)
+--   deriving anyclass Souffle.Marshal
+--   deriving Souffle.Fact
+--   via Souffle.FactOptions Edge "edge" 'Souffle.Input
+-- @
+--
+-- See also: 'ProgramOptions'.
+newtype FactOptions (fact :: Type) (factName :: Symbol) (dir :: Direction)
+  = FactOptions fact
+
+instance Marshal.Marshal fact => Marshal.Marshal (FactOptions fact name dir) where
+  push (FactOptions fact) = Marshal.push fact
+  {-# INLINABLE push #-}
+  pop = FactOptions <$> Marshal.pop
+  {-# INLINABLE pop #-}
+
+instance ( Marshal.Marshal fact
+         , KnownSymbol factName
+         ) => Fact (FactOptions fact factName dir) where
+  type FactDirection (FactOptions _ _ dir) = dir
+
+  factName = const $ symbolVal (Proxy @factName)
+  {-# INLINABLE factName #-}
+
 
 -- | A datatype describing which operations a certain fact supports.
 --   The direction is from the datalog perspective, so that it
diff --git a/lib/Language/Souffle/Compiled.hs b/lib/Language/Souffle/Compiled.hs
--- a/lib/Language/Souffle/Compiled.hs
+++ b/lib/Language/Souffle/Compiled.hs
@@ -15,7 +15,9 @@
 --   C++ compilation times.
 module Language.Souffle.Compiled
   ( Program(..)
+  , ProgramOptions(..)
   , Fact(..)
+  , FactOptions(..)
   , Marshal(..)
   , Direction(..)
   , ContainsInputFact
diff --git a/lib/Language/Souffle/Interpreted.hs b/lib/Language/Souffle/Interpreted.hs
--- a/lib/Language/Souffle/Interpreted.hs
+++ b/lib/Language/Souffle/Interpreted.hs
@@ -12,7 +12,9 @@
 --   the compiled alternative once the prototyping phase is finished.
 module Language.Souffle.Interpreted
   ( Program(..)
+  , ProgramOptions(..)
   , Fact(..)
+  , FactOptions(..)
   , Marshal(..)
   , Direction(..)
   , ContainsInputFact
diff --git a/souffle-haskell.cabal b/souffle-haskell.cabal
--- a/souffle-haskell.cabal
+++ b/souffle-haskell.cabal
@@ -1,11 +1,11 @@
 cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.34.2.
+-- This file has been generated from package.yaml by hpack version 0.34.6.
 --
 -- see: https://github.com/sol/hpack
 
 name:           souffle-haskell
-version:        3.2.0
+version:        3.3.0
 synopsis:       Souffle Datalog bindings for Haskell
 description:    Souffle Datalog bindings for Haskell.
 category:       Logic Programming, Foreign Binding, Bindings
@@ -88,7 +88,10 @@
       Paths_souffle_haskell
   hs-source-dirs:
       lib
-  default-extensions: OverloadedStrings LambdaCase ScopedTypeVariables
+  default-extensions:
+      OverloadedStrings
+      LambdaCase
+      ScopedTypeVariables
   ghc-options: -Wall -Weverything -Wno-safe -Wno-unsafe -Wno-implicit-prelude -Wno-missed-specializations -Wno-all-missed-specializations -Wno-missing-import-lists -Wno-type-defaults -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-deriving-strategies -Wno-prepositive-qualified-module -Wno-missing-safe-haskell-mode -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits
   cxx-options: -std=c++17 -Wall
   include-dirs:
@@ -152,7 +155,7 @@
     , mtl >=2.0 && <3
     , process >=1.6 && <2
     , profunctors >=5.6.2 && <6
-    , template-haskell >=2 && <3
+    , template-haskell ==2.*
     , temporary >=1.3 && <2
     , text >=1.0 && <2
     , text-short >=0.1.3 && <1
@@ -169,12 +172,18 @@
   other-modules:
       Test.Language.Souffle.AnalysisSpec
       Test.Language.Souffle.CompiledSpec
+      Test.Language.Souffle.DerivingViaSpec
       Test.Language.Souffle.InterpretedSpec
       Test.Language.Souffle.MarshalSpec
       Paths_souffle_haskell
+  autogen-modules:
+      Paths_souffle_haskell
   hs-source-dirs:
       tests
-  default-extensions: OverloadedStrings LambdaCase ScopedTypeVariables
+  default-extensions:
+      OverloadedStrings
+      LambdaCase
+      ScopedTypeVariables
   ghc-options: -Wall -Weverything -Wno-safe -Wno-unsafe -Wno-implicit-prelude -Wno-missed-specializations -Wno-all-missed-specializations -Wno-missing-import-lists -Wno-type-defaults -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-deriving-strategies -Wno-prepositive-qualified-module -Wno-missing-safe-haskell-mode -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits
   cxx-options: -std=c++17 -D__EMBEDDED_SOUFFLE__
   include-dirs:
@@ -245,7 +254,7 @@
     , process >=1.6 && <2
     , profunctors >=5.6.2 && <6
     , souffle-haskell
-    , template-haskell >=2 && <3
+    , template-haskell ==2.*
     , temporary >=1.3 && <2
     , text >=1.0 && <2
     , text-short >=0.1.3 && <1
@@ -261,9 +270,14 @@
   main-is: bench.hs
   other-modules:
       Paths_souffle_haskell
+  autogen-modules:
+      Paths_souffle_haskell
   hs-source-dirs:
       benchmarks
-  default-extensions: OverloadedStrings LambdaCase ScopedTypeVariables
+  default-extensions:
+      OverloadedStrings
+      LambdaCase
+      ScopedTypeVariables
   ghc-options: -Wall -Weverything -Wno-safe -Wno-unsafe -Wno-implicit-prelude -Wno-missed-specializations -Wno-all-missed-specializations -Wno-missing-import-lists -Wno-type-defaults -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-deriving-strategies -Wno-prepositive-qualified-module -Wno-missing-safe-haskell-mode -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits +RTS -N1 -RTS
   cxx-options: -std=c++17 -D__EMBEDDED_SOUFFLE__ -std=c++17 -march=native
   include-dirs:
@@ -329,7 +343,7 @@
     , process >=1.6 && <2
     , profunctors >=5.6.2 && <6
     , souffle-haskell
-    , template-haskell >=2 && <3
+    , template-haskell ==2.*
     , temporary >=1.3 && <2
     , text >=1.0 && <2
     , text-short >=0.1.3 && <1
diff --git a/tests/Test/Language/Souffle/DerivingViaSpec.hs b/tests/Test/Language/Souffle/DerivingViaSpec.hs
new file mode 100644
--- /dev/null
+++ b/tests/Test/Language/Souffle/DerivingViaSpec.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE UndecidableInstances, DataKinds, DeriveGeneric, DeriveAnyClass, DerivingVia #-}
+
+module Test.Language.Souffle.DerivingViaSpec
+  ( module Test.Language.Souffle.DerivingViaSpec
+  ) where
+
+import Test.Hspec
+import GHC.Generics
+import Data.Maybe
+import qualified Language.Souffle.Interpreted as Souffle
+
+
+data Path = Path
+  deriving Souffle.Program
+  via Souffle.ProgramOptions Path "path" '[Edge, Reachable]
+
+data Edge = Edge String String
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass Souffle.Marshal
+  deriving Souffle.Fact
+  via Souffle.FactOptions Edge "edge" 'Souffle.InputOutput
+
+data Reachable = Reachable String String
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass Souffle.Marshal
+  deriving Souffle.Fact
+  via Souffle.FactOptions Reachable "reachable" 'Souffle.Output
+
+
+spec :: Spec
+spec = describe "Souffle DerivingVia-style API" $ do
+  it "can get and put facts from souffle" $ do
+
+    edges <- Souffle.runSouffle Path $ \handle -> do
+      let prog = fromJust handle
+      Souffle.addFacts prog [Edge "e" "f", Edge "f" "g"]
+      Souffle.run prog
+      Souffle.getFacts prog
+    edges `shouldBe` [Edge "a" "b", Edge "b" "c", Edge "e" "f", Edge "f" "g"]
+
