Skip to content

Syntax Reference

Joshua Grosso edited this page Jan 31, 2020 · 74 revisions

Introduction

Axel's semantics are very close to Haskell's – in fact, if you remove Axel's metaprogramming capabilities, you can think of Axel as just an alternate syntax for Haskell. In this article, we'll cover these syntactic differences.

Axel's syntax is modeled off of the Lisp family of programming languages. The main conceptual difference from Haskell is that Axel requires parentheses around all function calls. For example, foo $ bar (baz quux) translates into Axel as (foo (bar (baz quux))). This extends to statements as well (e.g. foo a = a + 1 becomes (= (foo a) (+ a 1))). This may seem arbitrary and weird at first, but this style allows for incredible flexibility via macros. Because Axel programs are represented by trees of nested parentheses, they are very easy to manipulate and reshape to your heart's content. (For an introduction to Axel's metaprogramming system, please see Introduction to Macros.)

Because all Axel programs are written in this parenthesized prefix notation, we need to translate Haskell's basic syntactic constructs (let blocks, case expressions, import statements, etc.) into this parenthesized style. Also, because Axel's syntax is much simpler than Haskell's, many more characters are allowed in identifier names. We'll cover all this and more below, so keep reading!

The Basics

-- (Single Line Comment)

Syntax: -- some comment

Haskell equivalent: Comments are not currently kept in the transpiled output, but we're working on it (https://github.com/axellang/axel/issues/25)!

Examples:

(foo bar) -- Should we be using `foo` here?
-- TRANSPILES INTO
(foo bar)

Notes:

Axel's single line comment syntax is the same as Haskell's.

{-/-} (Multiline Comment)

Syntax: {- some comment, can contain newlines -}

Haskell equivalent: Comments are not currently kept in the transpiled output, but we're working on it (https://github.com/axellang/axel/issues/25)!

Examples:

{-
Module description:
1) Foo
2) Bar
-}
(foo bar)
-- TRANSPILES INTO
(foo bar)

Notes:

Axel's multiline comment syntax is the same as Haskell's.

raw (Raw Haskell Injection)

Syntax: (raw "some Haskell statement") or (raw "some Haskell expression")

Haskell equivalent: The Haskell statement or expression provided.

Examples:

(raw "foo :: Foo")
(= foo (bar (raw "@Baz"))) -- Transpiles into: foo = bar @Baz
-- TRANSPILES INTO
foo :: Foo
foo = bar @Baz

Notes:

Axel doesn't currently support all of Haskell's syntax natively (but we're working on it! https://github.com/axellang/axel/issues/2). So, if you run into a limitation of Axel's syntax, or just want to quickly use a Haskell snippet inside an Axel program, you can use Axel's provided escape-hatch: raw.

For example, if you want to use Template Haskell inside an Axel program – e.g. makeLenses ''Foo – just wrap the statement with a call to raw and everything will work as you expect – e.g. (raw "makeLenses ''Foo"). Or, let's say you want to use a type application, which doesn't yet have dedicated Axel syntax – e.g. foo @Bar baz. Use raw, like so: (foo (raw "@Bar") baz).

This even means that you can gradually convert a Haskell file to Axel, by wrapping each statement in its own call to raw and then converting them as you find convenient!

(...) (Function Application)

Syntax: (foo a b c)

Haskell equivalent: foo a b c

Examples:

(putStrLn (<> "Hello, " "world!"))
-- TRANSPILES INTO:
putStrLn (<> "Hello, " "world!")

Notes:

Note that infix operators are also applied in prefix form by default, so (+ 1) transpiles into ((+) 1) rather than (+ 1).

{...} (Infix Function Application) [Prelude Macro]

Syntax: {arg1 function arg2}

Haskell equivalent: arg1 `function` arg2, but function can be any valid Axel form (instead of just a symbol, as in Haskell).

Examples:

{1 + 2}
{'a' elem "abc"}
{'a' (\ [x y] (elem x y)) "abc"}
-- TRANSPILES INTO:
1 + 2
'a' `elem` "abc"
(\x y -> elem x y) 'a' "abc"

Notes:

{a op b} is converted by the parser into (applyInfix a op b).

applyInfix is a macro from the Axel Prelude which, when given three arguments, transpiles its input into the form (op a b).

{...} (Operator Section) [Prelude Macro]

Syntax: {function arg2}

Haskell equivalent: (`function` arg2), but function can be any valid Axel form (instead of just a symbol, as in Haskell).

Examples:

{+ 1}
{elem "abc"}
({(\ [x y] (elem x y)) "abc"} 'a')
-- TRANSPILES INTO:
(+ 1)
(`elem` "abc")
((\x y -> elem x y) "abc") 'a'

Notes:

{op b} is converted by the parser into (applyInfix op b).

applyInfix is a macro from the Axel Prelude which, when given two arguments, transpiles its input into the form (flip op b).

Identifiers

Notes:

Value-level identifiers (e.g. function names, macro names, argument names) can contain any character other than (, ), {, }, [, ], and " and they must not start with ', `, or ~. Names are qualified with ., e.g. Data.Maybe.fromJust.

Type-level identifiers (e.g. type names, type class names) have one additional restriction: If they would not be infix operators in Haskell, they cannot start with a non-alphanumeric character. For example, ~>, Foo, and Bar->Int are valid Axel type names; however, -Foo and >Bar are not. NOTE: This restriction is obviously not ideal, and progress on removing it is being tracked in #72.

Examples of valid Axel identifiers are fooBar, a->b, <+>, and isn't. However, 'foo and ~bar (for example) would both be considered invalid identifiers.

Any Haskell function, typeclass, etc. can be used in Axel as expected. However, since Axel is less restrictive than Haskell in this regard, an Axel identifier (e.g. a->b) might not be valid in Haskell. Thus, we only recommend using an Axel function, typeclass, etc. in Haskell code if its name is valid in both languages, since Axel has to specially escape characters that Haskell disallows. Currently, the way in which it does so should be considered an implementation detail; thus, escaped function names may change without notice between Axel versions.

Literals

#\ (Character Literal)

Syntax: #\x

Haskell equivalent: 'x'

Examples:

#\A
#\\
#\'
-- TRANSPILES INTO:
'A'
'\\'
'\''

Notes:

This character literal syntax is borrowed from Lisps like Common Lisp and Racket. Axel can't use Haskell's character literal syntax because the quote character (') is reserved for Axel's metaprogramming facilities (for example, 'a' is interpreted in Axel as a quotation of a symbol named a').

Integer Literals

Notes:

This is the same as in Haskell.

Float Literals

Notes:

This is the same as in Haskell.

"..." (String Literals)

Syntax:

"some
multiline
  string \""

Haskell equivalent: "some\nmultiline\n string \""

Notes:

The syntax is identical for both single- and multiline strings.

When a string spans multiple lines, anything after each newline will be considered part of the string. That is, even if a string starts somewhere in the middle of a line, any future lines will be treated as starting from the first column.

unit (Unit Syntax)

Syntax: unit or Unit

Haskell equivalent: ()

Examples:

(:: main [] (IO Unit))
(= main (pure unit))
-- TRANSPILES INTO:
main :: IO ()
main = pure ()

Notes:

Since ( and ) are reserved characters in Axel, () is not a valid identifier name. Thus, the synonyms Unit and unit are provided for use in Axel. Although it is customary to use Unit when you would otherwise use () as a type, and unit when you would otherwise use () as a value, they both transpile to the same Haskell token and thus are interchangeable.

list, [] (List Syntax)

Syntax: (list listItem1 listItem2 ... listItemN) or [listItem1 listItem2 ... listItemN]

Haskell equivalent: [listItem1, listItem2, ..., listItemN]

Examples:

[(putStr "Hello, ") (putStr "world!")]
(length (list))
-- TRANSPILES INTO:
[putStr "Hello, ", putStr "world!"]
length []

Notes:

The use of the [...] literal syntax is preferred (the list special form is provided for use in macros).

List (List type syntax)

Syntax: List

Haskell equivalent: []

Examples:

(:: foo [] (List Int))
-- TRANSPILES INTO:
foo :: [Int]

Notes:

List is provided as the type-level equivalent of Haskell's [] (since [ and ] are reserved characters in Axel).

Statement Special Forms

= (Function Definition)

Syntax:

(= functionHeadSpecifier
  functionBody
  optionalWhereBinding1
  optionalWhereBinding2
  ...
  optionalWhereBindingN)

Haskell equivalent:

functionHeadSpecifier = functionBody
  where
    optionalWhereBinding1
    optionalWhereBinding2
    ...
    optionalWhereBindingN

Examples:

(= (foo message) (go message)
  (:: go [] (-> String (IO Unit)))
  (= go putStrLn))
-- TRANSPILES INTO:
foo message = go message
  where
    go :: String -> IO ()
    go = putStrLn

Notes:

If your function has 1) an explicit type signature and 2) multiple cases, def can be useful to reduce redundancy.

| (Function Definition Guard/Multi-Way If) [Prelude Macro]

Syntax: (| (condition1 body1) (condition2 body2) ... (conditionN bodyN))

Haskell equivalent:

| condition1 = body1
| condition2 = body2
| condition3 = body3

Examples:

(= (foo x)
  (| ({x > 0} 2)
     ({x < 0} zero)
     (otherwise 1))
  (= zero 0))
-- TRANSPILES INTO:
foo x
  | x > 0 = 2
  | x < 0 = zero
  | otherwise = 1
  where zero = 0

Notes: This is the Axel equivalent of Haskell's guards in function definitions, but it also works as a multi-way if-expression (in Haskell, this requires the MultiWayIf syntax extension; however, in Axel, it's just a macro like any other).

:: (Type Signature)

Syntax: (:: functionName functionConstraints functionType)

Haskell equivalent: functionName :: functionConstraints => functionType

Examples:

(:: foo [] Int)
-- TRANSPILES INTO:
foo :: () => Int

Notes:

If your function has 1) an explicit type signature and 2) multiple cases, def can be useful to reduce redundancy.

def (Multi-Case Function Definition) [Prelude Macro]

Syntax:

(def name (constraints type)
  ((clause1arg1 clause1arg2 ... clause1argN) body1 optionalWhereBindings1)
  ((clause2arg1 clause2arg2 ... clause2argN) body2 optionalWhereBindings2)
  ...
  ((clauseNarg1 clauseNarg2 ... clauseNargN) bodyN optionalWhereBindingsN))

Haskell equivalent:

name :: constraints => type
name clause1arg1 clause1arg2 ... clause1argN = body1 where optionalWhereBindings1
name clause2arg1 clause2arg2 ... clause2argN = body2 where optionalWhereBindings2
...
name clauseNarg1 clauseNarg2 ... clauseNargN = bodyN where optionalWhereBindingsN

Examples:

(def foo ([] {(Maybe Int) -> Int})
  ((Just x) x)
  (Nothing 0))
-- TRANSPILES INTO:
foo :: Maybe Int -> Int
foo (Just x) = x
foo Nothing = 0

Notes:

def helps reduce redundancy when a function is defined with multiple cases.

class (Typeclass Definition)

Syntax:

(class (constraint1 constraint2 ... constraintN) classHead
  classSigOrDef1
  classSigOrDef2
  ...
  classSigOrDefN)

Haskell equivalent:

class (constraint1, constraint2, ..., constraintN) => classHead where
  classSigOrDef1
  classSigOrDef2
  ...
  classSigOrDefN

Examples:

(class [(Eq a)] (Foo a)
  (:: foo [] {a -> {a -> Bool}})
  (= foo ==)

  (:: bar [] {b -> b})
  (= (bar x) x))
-- TRANSPILES INTO:
class (Eq a) => Foo a where
  foo :: a -> a -> Bool
  foo = (==)

  bar :: b -> b
  bar x = x

instance (Typeclass Instance)

Syntax:

(instance [constraint1 constraint2 ... constraintN] instanceHead
  instanceDef1
  instanceDef2
  ...
  instanceDefN)

Haskell equivalent:

instance (constraint1, constraint2, ..., constraintN) => instanceHead where
  instanceDef1
  instanceDef2
  ...
  instanceDefN

Examples:

(instance [(Eq a)] (Foo a)
  (= (foo _ _) True)
  (= (bar x) x))
-- TRANSPILES INTO:
instance (Eq a) => Foo a where
  foo _ _ = True
  bar x = x

data (ADT Definition)

Syntax:

(data dataType
  constructor1
  constructor2
  ...
  constructorN
  optionalConstraintList)

where optionalConstraintList either is not present or is of the form [constraint1 constraint2 ... constraintN]. If optionalConstraintList is not present, it will be inferred to be [].

Haskell equivalent:

data dataType = constructor1 | constructor2 | ... | constructorN deriving (constraint1 constraint2 ... constraintN)`

Examples:

(data Foo Foo)

(data (Maybe a)
  (Just a)
  Nothing
  [Eq Show])
-- TRANSPILES INTO:
data Foo = Foo

data Maybe a
  = Just a
  | Nothing
  deriving (Eq, Show)

recordType (Record Type Constructor)

Syntax: (recordType (field1 type1) (field2 type2) ... (fieldN typeN))

Haskell equivalent: { field1 :: type1, field2 :: type2, ..., fieldN :: typeN }

Examples:

(data Foo
  (Foo (recordType (foo Int)
                   (bar String))))
-- TRANSPILES INTO:
data Foo = Foo { foo :: Int, bar :: String }

Notes:

For recordType's value-level equivalent, see record.

newtype (Newtype Definition)

Syntax:

(newtype name wrappedType optionalConstraints)` OR `(newtype (name arg1 arg2 ... argN) wrappedType optionalConstraints)

where optionalConstraintList either is not present or is of the form [constraint1 constraint2 ... constraintN]. If optionalConstraintList is not present, it will be inferred to be [].

Haskell equivalent: newtype name = name wrappedType deriving optionalConstraints OR newtype name arg1 arg2 ... argN = name wrappedType deriving optionalConstraints

Examples:

(newtype EmailAddress String)
(newtype (Id a) a [(Eq a) (Show a)])
-- TRANSPILES INTO:
newtype EmailAddress = EmailAddress String
newtype Id a = Id a deriving (Eq a, Show a)

Notes:

Unlike in Haskell, the "right hand side" of the declaration doesn't need to contain the newtype name again (e.g. EmailAddress and Id are automatically added in the above transpilation examples).

type (Type Synonym Definition)

Syntax: (type typeHead wrappedType)

Haskell equivalent: type typeHead = wrappedType

Examples:

(type EmailAddress String)
(type (Id a) a)
-- TRANSPILES INTO:
type EmailAddress = String
type Id a = a

module (Module Declaration)

Syntax: (module moduleIdentifier)

Haskell equivalent: module moduleIdentifier where

Examples:

(module Foo.Bar)
-- TRANSPILES INTO:
module Foo.Bar where

Notes:

Explicit export lists are not yet implemented in Axel.

Unlike in Haskell, Axel requires every module to have an explicit module declaration.

pragma (Compiler Pragma)

Syntax: (pragma "some pragma")

Haskell equivalent: {-# some pragma #-}

Examples:

(pragma "OPTIONS_GHC \"-fno-warn-incomplete-patterns\"")
(pragma "LANGUAGE GADTs")
-- TRANSPILES INTO:
{-# OPTIONS_GHC "-fno-warn-incomplete-patterns" #-}
{-# LANGUAGE GADTs #-}

import (Unqualified Import)

Syntax: (import moduleName all) OR (import moduleName [importItem1 importItem2 ... importItemN]), where each importItem is either an identifier or (containerType subitem1 subitem2 ... subitemN)

Transpiled into: import moduleName OR import moduleName (importItem1, importItem2, ..., importItemN), where each importItem is either the identifier or containerType(subitem1, subitem2, ..., subitemN)

Examples:

(import Foo all)
(import Bar [foo bar (Bar baz quux)])
-- TRANSPILED INTO:
import Foo
import Bar (foo, bar, Bar(baz, quux))

Notes:

Axel doesn't currently support aliasing unqualified imports (e.g. import Foo as Bar).

importq (Qualified Imports)

Syntax: (importq moduleName alias all) OR (importq moduleName alias [importItem1 importItem2 ... importItemN]), where each importItem is either an identifier or (containerType subitem1 subitem2 ... subitemN)

Transpiled into: import qualified moduleName as alias OR import qualified moduleName as alias (importItem1, importItem2, ..., importItemN), where each importItem is either the identifier or containerType(subitem1, subitem2, ..., subitemN)

Examples:

(importq Foo Baz all)
(importq Bar Quux [foo bar (Bar baz quux)])
-- TRANSPILED INTO:
import qualified Foo as Baz
import qualified Bar as Quux (foo, bar, Bar(baz, quux))

Notes:

Axel doesn't currently have an equivalent for the Haskell construction import qualified Foo (...) (i.e. there is an implicit as Foo).

Expression Special Forms

case (Case-expressions)

Syntax:

(case expr
  (pattern1 body1)
  (pattern2 body2)
  ...
  (patternN bodyN))

Haskell equivalent:

case expr of
  pattern1 -> body1
  pattern2 -> body2
  ...
  patternN -> bodyN

Examples:

(case (Just 1)
  ((Just a) {a + 1})
  (Nothing 0))
-- TRANSPILES INTO:
case Just 1 of
  Just a -> a + 1
  Nothing -> 0

\ (Lambda)

Syntax: (\ [arg1 arg2 ... argN] body)

Haskell equivalent: \arg1 arg2 ... argN -> body

Examples:

(\ [x y] {x + y})
-- TRANSPILES INTO:
\x y -> x + y

\case (Lambda Case Expression) [Prelude Macro]

Syntax:

(\case
  (pattern1 body1)
  (pattern2 body2)
  ...
  (patternN bodyN))

Haskell equivalent:

\<autogenerated> ->
  case <autogenerated> of
    pattern1 -> body1
    pattern2 -> body2
    ...
    patternN -> bodyN

Examples:

(\case
  ((Just x) {x + 1})
  (Nothing 0))
-- TRANSPILES INTO:
\<autogenerated> -> case <autogenerated> of
  Just x -> x + 1
  Nothing -> 0

Notes:

\case is the Axel equivalent of the the LambdaCase GHC extension.

\case is a macro from the Axel Prelude. (This is a great example of macros extending the syntactic flexibility of the language! In Haskell, a compiler extension was required to implement such functionality, whereas it becomes a very simple macro in Axel.)

@ (At-pattern)

Syntax: (@ name pattern)

Haskell equivalent: name@pattern

Examples:

(case foo
  ((@ name pattern) name)
  ({name @ pattern} name))
-- TRANSPILES INTO:
case foo of
  name@pattern -> name
  name@pattern -> name

if (If-expression) [Prelude Function]

Syntax: (if condition ifTrue ifFalse)

Haskell equivalent: if condition then ifTrue else ifFalse

Examples:

(if {"yes" == "yes"}
  (putStrLn "Cool!")
  (error "Impossible!"))
-- TRANSPILES INTO:
if "yes" == "yes"
  then putStrLn "Cool!"
  else error "Impossible!"

Notes:

Because Haskell is lazy, if is actually implemented as a function (no macros required)!

let (Let-expression)

Syntax:

(let [(pattern1 value1)
      (pattern2 value2)
      ...
      (patternN valueN)]
  body)

Haskell equivalent:

let pattern1 = value1
    pattern2 = value2
    ...
    patternN = valueN
 in body

Examples:

(let [((Just x) (fromJust maybeResult))
      (y {x + 1})]
  (putStrLn (show y)))
-- TRANSPILES INTO:
let Just x = fromJust maybeResult
    y = x + 1
 in putStrLn (show y)

record (Record Constructor)

Syntax: (record (field1 value1) (field2 value2) ... (fieldN valueN))

Haskell equivalent: { field1 = value1, field2 = value2, ..., fieldN = valueN }

Examples:

(Foo (record (foo 1) (bar "test")))
-- TRANSPILES INTO:
Foo { foo = 1, bar = "test" }

Notes:

Haskell's record update syntax (e.g. quux { foo = 1 }) is not yet supported in Axel.

For record's type-level equivalent, see recordType.

do' (Do-notation) [Prelude Macro]

Syntax: (do' clause1 clause2 ... clauseN), where each clause is (<- pattern val), (let ((pattern1 val1) (pattern2 val2) ... (patternN valN))), or val

Haskell equivalent:

do clause1
   clause2
   ...
   clauseN

where each clause is either:

  • pat <- val
  • let pattern1 = val1
        pattern2 = val2
        ...
        patternN = valN
  • val

Examples:

(do' (<- input getLine)
     (let ((reversed (reverse input))
           (output (map toUpper reversed))))
     (putStrLn output))
-- TRANSPILES INTO:
do input <- getLine
   let reversed = reverse input
       output = map toUpper reversed
   putStrLn output

Notes:

do' is a macro from the Axel Prelude. (This is a great example of macros extending the syntactic flexibility of the language!)

Support for clauses of the form {pat <- val} is in progress (https://github.com/axellang/axel/issues/19).

Metaprogramming

For a tutorial on how to use the below items in real-world programs, as well as more information as to how they work, please see Introduction to Macros.

Macro Call

Notes:

Wherever a function call is allowed, a macro name may be used instead of the function. In this case, the macro will be called at compile-time, with its arguments being the Abstract Syntax Tree (AST) representations of the Axel forms that were passed to it.

When a macro is called, the collection of statements before the one that includes the macro call must remain valid on their own. (For an example, see the documentation for def and the rationale behind using it instead of :: and =).

Macros are expanded top-down.

See =macro for more information.

', quote (Quote)

Syntax: 'form OR (quote form)

Haskell equivalent: Abstract Syntax Tree (AST) representation of form.

Examples:

'1
'-123.45
'#\a
'"test"
'foo
'(1 2 3)
''a
-- TRANSPILES INTO:
AST.LiteralInt <metadata> 1
AST.LiteralFloat <metadata> (-123.45)
AST.LiteralChar <metadata> 'a'
AST.LiteralString <metadata> "test"
AST.Symbol <metadata> "foo"
AST.SExpression <metadata> [AST.LiteralInt <metadata> 1, AST.LiteralInt <metadata> 2, AST.LiteralInt <metadata> 3]
AST.SExpression <metadata> [AST.Symbol <metadata> "quote", <quoted metadata>, AST.LiteralChar 

Notes:

' behaves similarly to the quote operator in other Lisps. Quotations are evaluated like any other form (i.e. bottom-up, at evaluation time).

'form and (quote form) are equivalent.

The metadata included in the AST data structures tells Axel where the expressions in question were originally located (i.e. file name, line, and column).

Due to the inclusion of source metadata, it's not safe to use a quoted expression in a pattern-match. For example, (= (foo 'symbol) body) is virtually guaranteed to never be matched, since the Eq instance for AST.Expression will take the source position of the quoted symbol into consideration, which will not match the source position of whatever argument is passed into foo (even if the symbol in question is named "symbol").

For an alternative to ' that can be used in pattern-matching, see the documentation for syntaxQuote.

For documentation on the AST data structures themselves and how to manipulate them, see the Hackage documentation for Axel.Parse.AST (this is currently prevented by https://github.com/haskell/haddock/issues/900).

`, quasiquote (Quasiquote)

Syntax: `form OR (quasiquote form)

Haskell equivalent: AST representation of form, except after accounting for ~ and ~@

Examples:

`(foo ~bar ~@['baz1 baz2])
`~foo
-- TRANSPILES INTO:
AST.SExpression <metadata> [AST.Symbol <metadata> "foo", bar, AST.Symbol <metadata> "baz1", baz2]
foo

Notes:

` behaves similarly to the quasiquote (or "backquote") operator in other Lisps. Quasiquotations are expanded into the corresponding calls to quote by the parser. Nested backquotes are handled as in e.g. Common Lisp.

`form and (quasiquote form) are equivalent.

Quasiquotation is the same as quotation, except:

  • Whenever ~form is encountered, the value of form will be used instead of its AST representation
  • Whenever (... ~@form ...) is encountered, the elements inside form (which must be either a list or a quoted s-expression) are inserted directly into the containing form

For documentation on the AST data structures themselves and how to manipulate them, see the Hackage documentation for Axel.Parse.AST.

~, unquote (Unquote)

Syntax: ~form OR (unquote form)

Notes:

~ behaves similarly to the unquote operator in other Lisps (it is the equivalent of , in e.g. Common Lisp).

~form and (unquote form) are equivalent.

The use of ~form is only valid when inside a form passed to `. It instructs the quasiquote expander to refrain from quoting form, and instead insert its value literally into the result of the quasiquotation.

See the documentation for ` for more details.

~@, unquoteSplicing (Splice-Unquote)

Syntax: ~@form OR (unquoteSplicing form)

Notes:

~@ behaves similarly to the splice-unquote operator in other Lisps (it is the equivalent of ,@ in e.g. Common Lisp).

~@form and (unquoteSplicing form) are equivalent.

The use of (... ~@form ...) is only valid when inside an s-expression (which is part of a form passed to `), and when form evaluates to either a list or a quoted s-expression. It instructs the quasiquote expander to refrain from quoting form, and instead insert the values of form's elements literally into the result of its containing s-expression.

See the documentation for ` for more details.

AST.* (AST Autogenerated Import)

Notes:

Axel.Parse.AST is implicitly imported into every Axel file, aliased to AST. See the Hackage documentation for Axel.Parse.AST for details on how to use what's imported under the AST namespace.

importm (Macro Import)

Syntax: (importm moduleIdentifier [macro1 macro2 ... macroN])

Examples:

(importm moduleIdentifier [macroFoo macroBar])
-- TRANSPILES INTO:
<moduleIdentifier.macroFoo and moduleIdentifier.macroBar are now in-scope>

Notes:

Macros cannot be imported with the traditional import syntax, so importm is a special form provided for this purpose. All macros are (currently) exported by default from their defining modules.

Axel automatically imports all macros (and functions) from the Axel Prelude, so imports of the form (importm Axel (...)) are likely redundant.

=macro (Macro Definition)

Syntax: (=macro name [arg1 arg2 ... argN] body optionalWhereBindings)

Examples:

(=macro applyInfix [x op y]
        (pure [`(~op ~x ~y)]))
-- TRANSPILES INTO:
<A macro such that e.g. `(applyInfix x + y)` is converted into `(+ x y)`>

Notes:

Macros are currently provided a single array as their only argument.

When a macro is defined, the only statements (e.g. imports, function definitions) it knows about are those which come before the call to =macro in the file. Furthermore, the statements that come before the macro definition must not rely on anything that comes after.

Macro type signatures are autogenerated by Axel, such that all macros have the type [AST.Expression <metadata>] -> IO [AST.Expression <metadata>]. (This means that calls to IO are allowed during a macro expansion, but it's recommended to keep side-effectful actions to a minimum, as always.)

When defining a macro, try not to discard the metadata information from received expressions. This is very important for the Axel compiler to be able to point the user of your macro to the right place in case of errors.

For the =macro equivalent to def, see defmacro.

defmacro (Macro Definition) [Prelude Macro]

Syntax:

(defmacro name
  (pattern1 body1 optionalWhereBindings1)
  (pattern2 body2 optionalWhereBindings2)
  ...
  (patternN bodyN optionalWhereBindingsN))

Examples:

(defmacro def
    ({name : {typeSig : cases}}
           (pure
            (snoc (map (\ [(AST.SExpression _ {args : xs})] `(= (~name ~@args) ~@xs))
                       cases)
                  `(:: ~name ~@typeSig)))))
-- TRANSPILES INTO:
<The `def` macro described in this reference>

Notes:

Each pattern refers to an array of AST representations of the macro's arguments. defmacro behaves similarly to def, but without the type signature specification.

When a macro is defined, the only statements (e.g. imports, function definitions) it knows about are those which come before the call to defmacro in the file.

Macro type signatures are autogenerated by Axel, such that all macros have the type [AST.Expression <metadata>] -> IO [AST.Expression <metadata>]. (This means that calls to IO are allowed during a macro expansion, but it's recommended to keep side-effectful actions to a minimum, as always.)

When defining a macro, try not to discard the metadata information from received expressions. This is very important for the Axel compiler to be able to point the user of your macro to the right place in case of errors.

Use defmacro instead of =macro where e.g. you'd otherwise need to include multiple calls to =macro to pattern match on the argument array differently.

syntaxQuote (Pattern-Match Quote) [Prelude Macro]

Syntax: (syntaxQuote form)

Examples:

(syntaxQuote foo)
-- TRANSPILES INTO:
(AST.SExpression _ "foo")

Notes:

syntaxQuote is a variation of quote that is safe for use in pattern-matches. It replaces what would normally be the source metadata of the quoted form with _, such that you can say e.g. (= (foo (syntaxQuote symbol)) body) and match on (foo 'symbol) (whereas (= (foo 'symbol) body) would actually not be called in this case). See the documentation for quote for more detail.

syntaxQuote doesn't currently have special syntax like quote does.

Clone this wiki locally