s-expression-0.0.0: sources/Data/Sexp.hs
{-# 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 <http://en.wikipedia.org/wiki/Common_Lisp#The_function_namespace Lisp-2> 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 =