{-# LANGUAGE AutoDeriveTypeable, DeriveDataTypeable, DeriveGeneric, DeriveAnyClass, DeriveFunctor, DeriveFoldable, DeriveTraversable #-} {-# LANGUAGE TemplateHaskell, LambdaCase, TypeFamilies #-} module Data.Sexp where import Derive.List (deriveList) import Control.Lens (Plated (..)) import Data.Foldable (Foldable (..)) import Data.Data (Data) import GHC.Generics (Generic) import GHC.Exts (IsString (..)) import Data.Void (Void) {- | a heterogenous list. a S-expression, where: * @f@ is the function namespace * @a@ is the atom namespace you could define @type Lisp1 a = Sexp a a@. with some caveats: * @f@ is ignored by 'Monad'ic methods like 'joinSexp' * @plate@ doesn't reach the @f@, even when @f ~ a@, as the 'Plated' instance is manual, not automatic via @Data@. the 'List' case is just a specialized @'Sexp' ()@, but easier to work with than: * @Sexp (Maybe f) [Sexp f a]@ (where Nothing would represent 'List') * forcing each concrete @f@ to hold a unit case (which would represent 'List') examples: >>> 'toList' (List [Atom "f",Atom "x",List [Atom "g",Atom "y"],Atom "z"]) ["f","x","g","y","z"] >>> :{ let doubleSexp e = do x <- e listSexp [x,x] :} >>> doubleSexp (List [Atom "f", Sexp () [Atom "a", Atom "b"]]) List [List [Atom "f",Atom "f"],Sexp () [List [Atom "a",Atom "a"],List [Atom "b",Atom "b"]]] -} data Sexp f a = Atom a | List [Sexp f a] | Sexp f [Sexp f a] deriving (Show,Read,Eq,Ord,Functor,Foldable,Traversable,Data,Generic) {-| isomorphic to: @ data Sexp_ a = Atom_ a | List_ [Sexp_ a] @ when you only care about lists (e.g. to interface with other s-expression libraries). -} type Sexp_ = Sexp Void {-| @ data ByteSexp = Atom ByteString | List [ByteSexp] bytesexp2sexp :: ByteSexp -> 'Sexp_' ByteString bytesexp2sexp = 'toSexp' $ \case Atom s -> Left s List es -> Right es @ -} toSexp :: (r -> Either a [r]) -> (r -> Sexp_ a) toSexp f = go where go r = case f r of Left a -> Atom a Right rs -> List (map go rs) -- | default instance via the 'Monad' subclass. instance Applicative (Sexp f) where pure = return (<*>) f x = f >>= (\g -> x >>= (return.g)) {- | definitions: @ 'return' = 'pureSexp' '(>>=)' = 'bindSexp' @ proofs of laws: * left-inverse(1): @join . return = id@ @ join (return m) joinSexp (pureSexp m) joinSexp (Atom m) m @ * left-inverse(2): @join . fmap return = id@ (the Sexp case is elided, the steps being identical to the List case) @ join (fmap return m) joinSexp (fmap pureSexp m) joinSexp (fmap Atom m) -- case analysis case m of Atom x -> joinSexp (Atom (Atom x)) -- by definition of joinSexp Atom x List ms -> joinSexp (List (fmap (fmap Atom) ms) -- by definition of joinSexp List (fmap joinSexp (fmap (fmap Atom) ms)) -- functor composition List (fmap (joinSexp . fmap Atom) ms) List (fmap (join . fmap return) ms) -- by induction List (fmap id ms) -- functor identity List ms -- both cases are identity m @ where: @ fmap f = \case Atom x -> f x List ms -> List (fmap (fmap f) ms) join = \case Atom x -> x List ms -> List (fmap joinSexp ms) @ * associativity(3): @join . join = join . fmap join@ @ TODO @ -} instance Monad (Sexp f) where return = pure (>>=) = bindSexp -- TODO laws, verified as @QuickCheck@ properties: instance Plated (Sexp f a) where plate f = \case Atom a -> Atom <$> pure a List ps -> List <$> traverse f ps Sexp g ps -> Sexp g <$> traverse f ps {-| >>> :set -XOverloadedStrings >>> "x" :: Sexp f String Atom "x" -} instance (IsString a) => IsString (Sexp f a) where fromString = Atom . fromString {-| @pureSexp = 'Atom'@ -} pureSexp :: a -> Sexp f a pureSexp = Atom {-# INLINE pureSexp #-} bindSexp :: Sexp f a -> (a -> Sexp f b) -> Sexp f b bindSexp s f = (joinSexp . fmap f) s {-# INLINE bindSexp #-} {-| -} joinSexp :: Sexp f (Sexp f a) -> Sexp f a joinSexp = \case Atom e -> e List ess -> List (joinSexp <$> ess) Sexp f ess -> Sexp f (joinSexp <$> ess) {-# INLINEABLE joinSexp #-} -- to hit the Atom I hope. deriveList ''Sexp 'List {-| refines any Sexp to a list, which can be given to the 'List'. -} toSexpList :: Sexp f a -> [Sexp f a] {-| >>> appendSexp (Atom "f") (List [Atom "x"]) List [Atom "f",Atom "x"] -} appendSexp :: Sexp f a -> Sexp f a -> Sexp f a {-| @emptySexp = 'List' []@ -} emptySexp :: Sexp f a {-| fold over an sexp. i.e. strictly evaluate a sexp ("all the way") to an atom, within any monadic context. -} evalSexp :: (Monad m) => ([a] -> m a) -> ([a] -> g -> m a) -> Sexp g a -> m a evalSexp list apply = \case Atom a -> pure a List es -> list =<< traverse go es Sexp g es -> (flip apply) g =<< traverse go es where go = evalSexp list apply {-| >>> data ArithFunc = Add | Multiply | Negate deriving Show >>> let badArith = Sexp Negate [Atom 1, Atom 2, Atom 3] :: Sexp ArithFunc Integer >>> let goodArith = Sexp Add [Sexp Multiply [], Sexp Negate [Atom (10::Integer)], Sexp Multiply [Atom 2, Atom 3, Atom 4]] >>> :set -XLambdaCase >>> :{ let evalArith = \case Add -> \case xs -> Just [sum xs] Multiply -> \case xs -> Just [product xs] Negate -> \case [x] -> Just [negate x] _ -> Nothing :} >>> evalSplatSexp (flip evalArith) (fmap (:[]) badArith) -- wrong arity Nothing >>> evalSplatSexp (flip evalArith) (fmap (:[]) goodArith) -- (+ (*) (- 10) (* 2 3 4)) Just [15] specializing, as above, @(m ~ Maybe)@, @(b ~ [Integer])@, @(g ~ ArithFunc)@: @evalSplatSexp :: ([Integer] -> ArithFunc -> Maybe [Integer]) -> (Sexp ArithFunc [Integer] -> Maybe [Integer])@ @evalSplatSexp apply = 'evalSexp' ('pure'.'fold') (apply.'fold')@ -} evalSplatSexp :: (Monad m, Monoid b) => (b -> g -> m b) -> (Sexp g b -> m b) evalSplatSexp apply = evalSexp (pure.fold) (apply.fold) {-# INLINE evalSplatSexp #-} {-| when a Sexp\'s atoms are 'Monoid'al ("list-like"), after evaluating some expressions into atoms, we can "splat" them back together. @splatList@ takes: * an evaluator @eval@ * and a list of s-expressions @es@ to evaluate in sequence. -} splatSexpList :: (Applicative m, Monoid b) => (Sexp g b -> m b) -> [Sexp g b] -> m b splatSexpList eval = fmap fold . traverse eval {-# INLINE splatSexpList #-} {-| inject a list of atoms. >>> listSexp [1,2,3] List [Atom 1,Atom 2,Atom 3] -} listSexp :: [a] -> Sexp f a listSexp = List . map Atom {-# INLINE listSexp #-} -- data SexpF f a r -- = AtomF a -- | FuncF (f r) -- | ListF [r] -- deriving (Show,Read,Eq,Ord,Functor,Data,Generic) -- type Sexp' a = Fix (SexpF a) {- helper when converting from other sexp types, like from a parsing library. @Either Bytestring [Bytestring]@ is isomorphic to: e.g. the @atto-lisp@ package defines: @ data Lisp = Symbol Text -- ^ A symbol (including keyword) | String Text -- ^ A string. | Number Number -- ^ A number | List [Lisp] -- ^ A proper list: @(foo x 42)@ | DotList [Lisp] Lisp -- ^ A list with a non-nil tail: @(foo x -- . 42)@. The list argument must be -- non-empty and the tail must be non-'nil'. @ we can define: @ type AttoLispSexp = Sexp AttoLispFunc AttoLispAtom data AttoLispAtom = SymbolAtom Text | StringAtom Text | NumberAtom Number TODO data AttoLispFunc r = DotListFunc [r] r @ -} -- fromSexp :: -> -- fromSexp = -- toSexp :: -> -- toSexp =