Language tutorial
Go to the indexThe language is not meant to be a replacement for HTML, it's just a tool for rendering HTML to style a blog post. There's still a lot of work to do, but it's already pretty useful, at least in my view.
The syntax of the language is based on S-expressions in lisp: to call a function f with arguments x, y, z, you write (f x y z). For example, 2 + (3 * 5) would be written as (+ 2 (* 3 5)). Apart from that, you have names (like porlist-unordered), strings ( like "hello") and integers (like 42).
The entire document is just one big expression.
Some examples of markup, just to give you an idea:
A string is a valid document:
"Hello, world"
You can concatenate things with the $ function:
($ "Hello, " "world!")
Some functions represent HTML tags:
(p ; paragraph (bf "Hello") ; boldface (it "World") ; italics (tt "Aaaaa") ; monospaces text ; heading: ((h 3) "To do:") (list-unordered "Buy milk" "Submit a pull request" "Solve fizzbuzz"))
HelloWorldAaaaa
To do:
- Buy milk
- Submit a pull request
- Solve fizzbuzz
Strong typing
Some languages are powerful not because of something you can do with them, but rather because of something you can't. Strict languages like Haskell or Rust are a good example of that.
(b (p "text"))
This results in an error: Cannot call (λ ...inline . inline) with (block) (line 71, column 3)
Why can't you do that? It seems perfectly fine, but it isn't: p is a 'block' element, while b is an 'inline' element, and you can't put a 'block' element inside an 'inline' element. You can go to the W3C validator and see for yourself that <b><p>text</p></b> is a no-no.
Inspecting types
You can inspect the type of an object by using the type function:
(list-unordered (tt "\"hello\": " (type "hello")) (tt "5: " (type 5)) (tt "p: " (type p)) (tt "$: " (type $)) (tt "h: " (type h)) (tt "nobr: " (type nobr)) (tt "horizontal-rule: " (type nobr)) )
- "hello": str
- 5: int
- p: (λ ...inline|block . block)
- $: (λ ...inline . inline)|(λ ...inline|block . inline|block)
- h: (λ int . (λ ...inline . block))
- nobr: (λ inline . inline)|(λ block . block)
- horizontal-rule: (λ . block)
Let's break down the function types:
- The lambda (λ) just means that it's a function
- (λ a . b) is a function from type 'a' to type 'b'
- (λ ...a . b) is a function that accepts any number of 'a's an returns a 'b'
- a | b means "either an 'a' or a 'b'"
- inline is an inline HTML element, which also includes strings and ints.
- block is a block HTML element
Higher-order functions
There's a type that seems strange: h: (λ int . (λ ...Inl . block))
Just like in Python or in many other computer languages, functions are first-class values: they can be created, returned, and passed as arguments to other functions. So h is a function that takes an integer and returns a new function, which in turn accepts any number of inline-ish elements and returns a block HTML element.
map is another example of a higher-order function. its type is (λ (λ inline . inline) . (λ ...inline . inline))|(λ (λ ...inline . inline) . (λ ...inline . inline))|(λ (λ str . inline) . (λ ...str . inline))|(λ (λ ...str . inline) . (λ ...str . inline))|(λ (λ block|inline . block) . (λ ...block|inline . block))|(λ (λ ...block|inline . block) . (λ ...block|inline . block)). That's a very long type, but basically, map allows you to apply a function to a list of values.
((map p) "First paragraph" "Second paragraph" "Third paragraph")
First paragraph
Second paragraph
Third paragraph
sep allows you to separate a list of values with a separator you give it.
(p ((sep ", ") "int" "str" "list"))
int, str, list
Ah, but what if you need to sep and map? In that case you can use sepmap!
(p ((sepmap ", " tt) "int" "str" "list"))
int, str, list
Documentation for all built-in functions
bf : (λ ...inline . inline)
Boldface text. Represents the <b> HTML tag.it : (λ ...inline . inline)
Italic text. Represents the <i> HTML tag.tt : (λ ...inline . inline)
Monospace text. Represents the <tt> HTML tag.mono : (λ ...inline . inline)
Like tt, but prevents text inside from line-wrapping.e : (λ str . inline)
Creates an HTML entity. (e "mdash") renders as —.$ : (λ ...inline . inline)|(λ ...inline|block . inline|block)
Concatenate multiple elements.h : (λ int . (λ ...inline . block))
Heading. Represents the <h1>..<h6> HTML tags.style : (λ str . (λ ...inline . inline))
Applies CSS styling to an inline element. Use sparingly.list-unordered : (λ ...inline . block)
Represents the <ul> HTML tag.list-ordered : (λ ...inline . block)
Represents the <ol> HTML tag.p : (λ ...inline|block . block)
Represents the <p> HTML tag.a : (λ str inline . inline)
Represents the <a> HTML tag.horizontal-rule : (λ . block)
Represents the <hr/> HTML tag.-- : (λ . inline)
The "em dash" (—).pre : (λ ...inline . block)
Represents the <pre> HTML tag. Make sure to use a multiline ("""...""") string here.map : (λ (λ inline . inline) . (λ ...inline . inline))|(λ (λ ...inline . inline) . (λ ...inline . inline))|(λ (λ str . inline) . (λ ...str . inline))|(λ (λ ...str . inline) . (λ ...str . inline))|(λ (λ block|inline . block) . (λ ...block|inline . block))|(λ (λ ...block|inline . block) . (λ ...block|inline . block))
Map a function onto a list of values. The overloads are messy and will soon be refactored.sepmap : (λ inline (λ inline . inline) . (λ ...inline . inline))|(λ inline (λ ...inline . inline) . (λ ...inline . inline))|(λ str (λ str . inline) . (λ ...str . inline))|(λ str (λ ...str . inline) . (λ ...str . inline))
Combination of sep and map.sep : (λ inline . (λ ...inline . inline))
Concatenate inline elements, separating them with a given string.nobr : (λ inline . inline)|(λ block . block)
Replaces spaces in text with so that it's not subject to text wrapping. Does not represent the nobr HTML TAG.type : (λ any . inline)
Renders the type of a value as a string.doc : (λ any . inline)
Render the documentation for a function.+ : (λ ...&[name]|&[(name str)]|inline|block . block)
Part of the 'fnl.x' module. Shortcut for b &divb : (λ str|&[name] ...&[str]|&[name]|&[(name str)]|inline|block . block)
Part of the 'fnl.x' module. Creates a block element. See the 'Quoted expressions' tutorial for more info.i : (λ str|&[name] ...&[name]|&[(name str)]|inline . inline)
Part of the 'fnl.x' module. Creates an inline element. See the 'Quoted expressions' tutorial for more info.var : (λ &[name] . any)
Part of the 'bindings' module. (var &x) is used to look up the binding x defined in a 'let' or 'for' clause.let : (λ ...&[(name any)] . (λ &[any] . any))|(λ &[name] any &[any] . any)
Part of the 'bindings' module. Defines a scope in which certain bindings refer to certain expressions. Examples: (let &answer 42 &(bf (var &answer))), ((let &(answer 42) &(pi 3)) &(bf (var &answer) "..." (var &pi)))foreach : (λ &[name] &[any] &[any] . any)
Part of the 'bindings' module. For each element of a 'list', render some expression while binding the element to a specific name. (foreach &i &(1 2 3 4) &($ (bf (var &i)) " ") will render boldface numbers 1, 2, 3 and 4 separated by spaces.unquote : (λ &[any] . any)
Part of the 'bindings' module. Extract and evaluate the expression from under the &extract-name : (λ &[name] . str)
Part of the 'bindings' module. Convert a quoted name to a stringdocumented-names : (λ . &[any])
Part of the 'bindings' module. Get a list of all the documented names as a quoted s-expression: &(&bf &it &tt ...)debug : (λ any . str)
Part of the 'bindings' module. Render an expresion for debugging purposesSource:
($docs $filename $source "Language tutorial"
(p
"The language is not meant to be a replacement for HTML,
it's just a tool for rendering HTML to style a blog post. There's still a
lot of work to do, but it's already pretty useful, at least in my view.")
(horizontal-rule)
(p
"The syntax of the language is based on S-expressions in lisp: to call
a function " (mono "f") " with arguments " ((sepmap ", " mono) "x" "y" "z")
", you write " (mono "(f x y z)") ". For example, " (mono "2 + (3 * 5)")
" would be written as " (mono "(+ 2 (* 3 5))") ". Apart from that, you have
names (like " (mono "p") "or" (mono "list-unordered") "), strings ( like "
(mono "\"hello\"") ") and integers (like " (mono "42") ").")
(p
"The entire document is just one big expression.")
(p
"Some examples of markup, just to give you an idea:")
(horizontal-rule)
((h 3)
"A string is a valid document:")
(p (mono "\"Hello, world\""))
($box
"Hello, world")
(horizontal-rule)
((h 3)
"You can concatenate things with the " (mono "$") " function:")
(p (mono "($ \"Hello, \" \"world!\")"))
($box
($ "Hello, " "world!"))
(horizontal-rule)
((h 3)
"Some functions represent HTML tags:")
(pre ($fnl """
(p ; paragraph
(bf "Hello") ; boldface
(it "World") ; italics
(tt "Aaaaa") ; monospaces text
; heading:
((h 3) "To do:")
(list-unordered
"Buy milk"
"Submit a pull request"
"Solve fizzbuzz"))
"""))
($box
(p
(bf "Hello")
(it "World")
(tt "Aaaaa")
((h 3) "To do:")
(list-unordered
"Buy milk"
"Submit a pull request"
"Solve fizzbuzz")))
(horizontal-rule)
((h 3)
"Strong typing")
(p
"Some languages are powerful not because of something you can do with them,
but rather because of something you " (it "can't") ". Strict languages like
Haskell or Rust are a good example of that.")
(p (pre ($fnl """
(b (p "text"))
""")))
(p
"This results in an error: "
(bf (mono "Cannot call (λ ...inline . inline) with (block) (line 71, column 3)")))
(p
"Why can't you do that? It seems perfectly fine, but it isn't: "
(tt "p") " is a 'block' element, while " (tt "b") " is an 'inline' element,
and you can't put a 'block' element inside an 'inline' element.
You can go to " (a "https://validator.w3.org/" "the W3C validator")
" and see for yourself that " (mono "<b><p>text</p></b>") " is a no-no.")
(horizontal-rule)
((h 3)
"Inspecting types")
(p
"You can inspect the type of an object by using the " (tt "type")
" function:")
(pre ($fnl
"""
(list-unordered
(tt "\"hello\": " (type "hello"))
(tt "5: " (type 5))
(tt "p: " (type p))
(tt "$: " (type $))
(tt "h: " (type h))
(tt "nobr: " (type nobr))
(tt "horizontal-rule: " (type nobr))
)
"""
))
($box
(list-unordered
(tt "\"hello\": " (type "hello"))
(tt "5: " (type 5))
(tt "p: " (type p))
(tt "$: " (type $))
(tt "h: " (type h))
(tt "nobr: " (type nobr))
(tt "horizontal-rule: " (type horizontal-rule))))
(p
"Let's break down the function types:"
(list-unordered
"The lambda (λ) just means that it's a function"
($ (tt "(λ a . b)") " is a function from type 'a' to type 'b'")
($ (tt "(λ ...a . b)") " is a function that accepts any number of 'a's an returns a 'b'")
($ (tt "a | b") " means \"either an 'a' or a 'b'\"")
($ (tt "inline") " is an inline HTML element, which also includes strings and ints.")
($ (tt "block") " is a block HTML element")
)
)
(horizontal-rule)
((h 3)
"Higher-order functions")
(p
"There's a type that seems strange: " (mono "h: (λ int . (λ ...Inl . block))"))
(p
"Just like in Python or in many other computer languages, functions are
first-class values: they can be created, returned, and passed as arguments
to other functions. So " (tt "h") " is a function that takes an integer
and returns a new function, which in turn accepts any number of inline-ish
elements and returns a block HTML element.")
(p
(tt "map") " is another example of a higher-order function. its type
is " (mono (type map)) ". That's a very long type, but basically, "
(tt "map") " allows you to apply a function to a list of values.")
(pre ($fnl """
((map p) "First paragraph" "Second paragraph" "Third paragraph")
"""))
((map p) "First paragraph" "Second paragraph" "Third paragraph")
(p
(tt "sep") " allows you to separate a list of values with a separator
you give it.")
(pre ($fnl """
(p
((sep ", ") "int" "str" "list"))
"""))
($box
(p
((sep ", ") "int" "str" "list")))
(p
"Ah, but what if you need to " (mono "sep") (it " and ") (mono "map") "?
In that case you can use " (mono "sepmap") "!")
(pre ($fnl """
(p
((sepmap ", " tt) "int" "str" "list"))
"""))
($box
(p
((sepmap ", " tt) "int" "str" "list")))
(horizontal-rule)
((h 2)
"Documentation for all built-in functions")
(foreach &name (documented-names)
&($
((h 3) (tt (bf (extract-name (var &name))) " : " (type (unquote (var &name)))))
(doc (unquote (var &name)))
))
(horizontal-rule)
((h 2) "Source:")
(pre ($fnl $source))
)