In this post I'm going to show how I'm using Haskell Views in a small Scheme implementation to deal with syntactic sugar.
One example of syntactic sugar in Scheme is the way to define functions. You can do it the following way:
(define square (lambda (x) (* x x))
But you can also use the following shortcut:
(define (square x) (* x x))
I'm going to take advantage of Haskell Views to simplify the implementation of this feature. The following code shows the data type representing Scheme code.
data Expr a where
ScSymbol :: String -> Expr a
ScString :: String -> Expr a
ScNumber :: Integer -> Expr a
ScDouble :: Double -> Expr a
ScCons :: (Expr a) -> (Expr a) -> (Expr a)
ScNil :: Expr a
ScBool :: Bool -> Expr a
ScQuote :: (Expr a) -> Expr a
ScEnv :: (Expr a)
ScChar :: Char -> Expr a
ScPort :: Handle -> Expr a
ScClosure :: ScExecutable a => [String] -> a -> (Env a) -> Expr a
ScPrimitive :: ([Expr a] -> ScInterpreterMonad (Expr a)) -> Expr a
As a side note,I'm using ScCons/ScNil to represent lists which is different from the way "Write Yourself a Scheme in 48 Hours" defines them (a wrapper around a Haskell list). This decision turned out to be incorrect since manipulating a Haskell list is easier.
Given Scheme datatype definition we can represent the following function definition:
(define (id x) x)
As the following structure:
(ScCons
(ScSymbol "define")
(ScCons
(ScCons
(ScSymbol "id")
(ScCons
(ScSymbol "x")
ScNil))
(ScCons
(ScSymbol "x")
ScNil)))
And the following code:
(define id (lambda (x) x))
Using the following structure
(ScCons
(ScSymbol "define")
(ScCons
(ScSymbol "id")
(ScCons
(ScCons
(ScSymbol "lambda")
(ScCons
(ScCons
(ScSymbol "x")
ScNil)
(ScCons
(ScSymbol "x")
ScNil)))
ScNil)))
Using Haskell Views we can create a view that returns the desired parts:
defineView :: (Expr a) -> Maybe (String, Expr a)
defineView (ScCons
(ScSymbol "define")
(ScCons
name@(ScSymbol symbolToDefine)
(ScCons
value
ScNil))) = Just (symbolToDefine, value)
defineView (ScCons
(ScSymbol "define")
(ScCons
(ScCons
name@(ScSymbol symbolToDefine)
args)
(ScCons
value
ScNil))) = Just (symbolToDefine,
(ScCons (ScSymbol "lambda")
(ScCons args
(ScCons value ScNil))))
defineView _ = Nothing
As shown above, this view will extract the parts of the original expression or create the necessary elements in the case of the function definition. Also it will fail in case of non supported syntax.
This view is used in the following code:
prepare (defineView -> Just (symbolToDefine, value)) =
do
preparedValue <- prepare value
return $ ScSeqDefine symbolToDefine preparedValue
Code for this post is available in the ToyScheme repo.