Quoted expressions
Go to the indexStrict evaluation
Suppose that you want to render some content conditionally. For example, you might want to add notes that are only visible to the editors; or you might want to temporarily disable some part of the tree. It's not very hard to implement, right?
extensions = {} is_debug = True @fn(extensions, 'if-debug') def if_debug(): def _if_debug(subtree): if is_debug: return subtree else: return e.BlockConcat(()) yield ('(λ inline|block . block)', _if_debug)
Let's see if it works.
>>> is_debug = True >>> fnl.html('(if-debug (bf "hi"))', extensions) '<b>hi</b>' >>> is_debug = False >>> fnl.html('(if-debug (bf "hi"))', extensions) ''
It works, but there are some issues. The subtree is evaluated even if it doesn't get rendered. It might waste some resources if it's a really large tree (or if it requies reading from a file, which is probably not a good idea, but you might want to read a config, for instance). More importantly, under certain conditions, it might throw an error because something about this tree requires is_debug to be true.
FNL is a strict language. It means that function arguments are fully evaluated first, and only then are passed to the function. So, alas, we're out of luck.
Quoted expression to the rescue
...unless we use quoted expressions! A quoted expression represents an expression that doesn't get immediately evaluated. You can create a quoted expression by prefixing it with &, for example, &1024 is a deferred integer, and &(hello "world") is a deferred call.
extensions = {} is_debug = True @fn(extensions, 'if-debug') def if_debug(): def _if_debug(quoted): if is_debug: return quoted.subexpression else: return e.BlockConcat(()) yield ('(λ &[any] . block)', _if_debug)
&[any] is a type of a quoted expession. The type of an expesssion isn't known until it's fully evaluated, so we will use anyas the type parameter. Other examples of quoted types:
- &[str] — quoted string (like &"hi")
- &[int] — quoted integer (like &10)
- &[name] — quoted name (like &a-name)
- &[name[a|b]] — bounded quoted name (only &a or &b )
- &[(name int)] — s-expression (like &(version 7))
Examples of using quoted expressions in fnl.x
fnl.x is a standard extension that helps you construct arbitrary HTML.
import fnl source = ... print(fnl.html(source, fnl.x)) # or: print(fnl.html(source, {**your, **extensions, **fnl.x})) # or, in 3.9+: print(fnl.html(source, your | extensions | fnl.x))
fnl.x exports three function: b, i, +. b creates a block element, i creates an inline element, and + is a shortcut for the div element.
($ (b"!DOCTYPE" &html &.) (b&html &(lang "en") (b&head (b&meta &(charset "UTF-8") &.) (b&meta &(name "viewport") &(content "width=device-width, initial-scale=1.0") &.) (b&title "My blog") (b&link &(rel "stylesheet") &(href "style.css") &/)) (b&body (b&main &#app (+ &.greeting ((h 1) "Hello, world!") (+ &.content (i&a &(href "pay.html") "Give me your money")))))))
This is going to render the following HTML code:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My blog</title> <link rel="stylesheet" href="style.css" /> </head> <body> <main id="app"> <div class="greeting"> <h1>Hello, world!</h1> <div class="content"> <a href="pay.html">Give me your money</a> </div> </div> </main> </body> </html>
As you can see, there aren't that many rules:
- You can use plain old FNL functions (like h)
- (b&tag ...) creates a block element
- (b"tag!?!&" ...) creates a block element with a name which is not a valid identifier
- (i&tag ...) creates an inline element
- &.class adds a class
- &#id adds an ID
- &html adds an attribute without an argument
- &(attr "value") adds an attribute
- &. makes the tag closed without making a <tag></tag> pair
- &/ makes the tag closed without making a <tag></tag> pair and adds a slash ( / ) at the end.
fnlx takes advantage of quoted expressions: an expression like (lang "en") or #id would fail at runtime.
With this tool, you can write pretty much any HTML you want. If you're feeling adventurous, you can even write markup for Vue.js like that.
Source:
($docs $filename $source "Quoted expressions"
((h 2)
"Strict evaluation")
(p
"Suppose that you want to render some content conditionally. For example,
you might want to add notes that are only visible to the editors; or
you might want to temporarily disable some part of the tree. It's not very
hard to implement, right?")
(pre """
extensions = {}
is_debug = True
@fn(extensions, 'if-debug')
def if_debug():
def _if_debug(subtree):
if is_debug:
return subtree
else:
return e.BlockConcat(())
yield ('(λ inline|block . block)', _if_debug)
""")
(p
"Let's see if it works.")
(pre """
>>> is_debug = True
>>> fnl.html('(if-debug (bf "hi"))', extensions)
'<b>hi</b>'
>>> is_debug = False
>>> fnl.html('(if-debug (bf "hi"))', extensions)
''
""")
(p
"It works, but there are some issues. The subtree is evaluated
even if it doesn't get rendered. It might waste some resources if it's
a really large tree (or if it requies reading from a file, which is probably
not a good idea, but you might want to read a config, for instance). More
importantly, under certain conditions, it might throw an error because
something about this tree requires " (tt "is_debug") " to be true.")
(p
"FNL is a " (it "strict") " language. It means that function arguments are
fully evaluated first, and only then are passed to the function. So, alas,
we're out of luck.")
((h 2)
"Quoted expression to the rescue")
(p
"...unless we use quoted expressions! A quoted expression represents an expression
that doesn't get immediately evaluated. You can create a quoted expression
by prefixing it with " (tt "&") ", for example, " (mono "&1024") " is a
deferred integer, and " (mono "&(hello \"world\")") " is a deferred call.")
(pre """
extensions = {}
is_debug = True
@fn(extensions, 'if-debug')
def if_debug():
def _if_debug(quoted):
if is_debug:
return quoted.subexpression
else:
return e.BlockConcat(())
yield ('(λ &[any] . block)', _if_debug)
""")
(p
(mono "&[any]") " is a type of a quoted expession. The type of an expesssion
isn't known until it's fully evaluated, so we will use " (tt "any") "as the
type parameter. Other examples of quoted types: "
(list-unordered
($ (tt "&[str] ") (--) " quoted string (like " (tt "&\"hi\"") ")" )
($ (tt "&[int] ") (--) " quoted integer (like " (tt "&10") ")" )
($ (tt "&[name] ") (--) " quoted name (like " (tt "&a-name") ")" )
($ (tt "&[name[a|b]] ") (--) " bounded quoted name (only " (tt "&a") " or " (tt "&b") " )" )
($ (tt "&[(name int)] ") (--) " s-expression (like " (tt "&(version 7)") ")" )))
((h 2)
"Examples of using quoted expressions in " (tt "fnl.x"))
(p
(tt "fnl.x") " is a standard extension that helps you construct arbitrary HTML.")
(pre """
import fnl
source = ...
print(fnl.html(source, fnl.x))
# or:
print(fnl.html(source, {**your, **extensions, **fnl.x}))
# or, in 3.9+:
print(fnl.html(source, your | extensions | fnl.x))
""")
(p
(tt "fnl.x") " exports three function: " ((sepmap ", " tt) "b" "i" "+") ". "
(tt "b") " creates a block element, " (tt "i") " creates an inline element,
and " (tt "+") " is a shortcut for the " (tt "div") " element.")
(pre ($fnl """
($
(b"!DOCTYPE" &html &.)
(b&html &(lang "en")
(b&head
(b&meta &(charset "UTF-8") &.)
(b&meta &(name "viewport") &(content "width=device-width, initial-scale=1.0") &.)
(b&title "My blog")
(b&link &(rel "stylesheet") &(href "style.css") &/))
(b&body
(b&main &#app
(+ &.greeting
((h 1) "Hello, world!")
(+ &.content
(i&a &(href "pay.html") "Give me your money")))))))
"""))
(p "This is going to render the following HTML code:")
(pre """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My blog</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main id="app">
<div class="greeting">
<h1>Hello, world!</h1>
<div class="content">
<a href="pay.html">Give me your money</a>
</div>
</div>
</main>
</body>
</html>""")
(p
"As you can see, there aren't that many rules:"
(list-unordered
($ "You can use plain old FNL functions (like " (tt "h") ")")
($ (tt "(b&tag ...)") " creates a block element")
($ (tt "(b\"tag!?!&\" ...)") " creates a block element with a name which is
not a valid identifier")
($ (tt "(i&tag ...)") " creates an inline element")
($ (tt "&.class") " adds a class")
($ (tt "&#id") " adds an ID")
($ (tt "&html") " adds an attribute without an argument")
($ (tt "&(attr \"value\")") " adds an attribute")
($ (tt "&.") " makes the tag closed without making a " (tt "<tag></tag>")
" pair")
($ (tt "&/") " makes the tag closed without making a " (tt "<tag></tag>")
" pair and adds a slash ( / ) at the end.")))
(p
(tt "fnlx") " takes advantage of quoted expressions: an expression like "
(mono "(lang \"en\")") " or " (tt "#id") " would fail at runtime.")
(p
"With this tool, you can write pretty much any HTML you want. If you're
feeling adventurous, you can even write markup for Vue.js like that.")
(horizontal-rule)
((h 2) "Source:")
(pre ($fnl $source))
)