souffle-haskell 3.2.0 → 3.3.0
raw patch · 8 files changed
+185/−35 lines, 8 files
Files
- CHANGELOG.md +11/−0
- README.md +22/−26
- lib/Language/Souffle/Analysis.hs +24/−0
- lib/Language/Souffle/Class.hs +62/−1
- lib/Language/Souffle/Compiled.hs +2/−0
- lib/Language/Souffle/Interpreted.hs +2/−0
- souffle-haskell.cabal +22/−8
- tests/Test/Language/Souffle/DerivingViaSpec.hs +40/−0
CHANGELOG.md view
@@ -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
README.md view
@@ -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 ()
lib/Language/Souffle/Analysis.hs view
@@ -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 (+++) #-}
lib/Language/Souffle/Class.hs view
@@ -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
lib/Language/Souffle/Compiled.hs view
@@ -15,7 +15,9 @@ -- C++ compilation times. module Language.Souffle.Compiled ( Program(..)+ , ProgramOptions(..) , Fact(..)+ , FactOptions(..) , Marshal(..) , Direction(..) , ContainsInputFact
lib/Language/Souffle/Interpreted.hs view
@@ -12,7 +12,9 @@ -- the compiled alternative once the prototyping phase is finished. module Language.Souffle.Interpreted ( Program(..)+ , ProgramOptions(..) , Fact(..)+ , FactOptions(..) , Marshal(..) , Direction(..) , ContainsInputFact
souffle-haskell.cabal view
@@ -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
+ tests/Test/Language/Souffle/DerivingViaSpec.hs view
@@ -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"]+