Merge branch 'master' into gh-pages
This commit is contained in:
commit
8246a2fc7f
24
gh.css
24
gh.css
|
@ -4,16 +4,28 @@
|
|||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/*** racket.css ***/
|
||||
/**********************************************************************/
|
||||
/* racket.css */
|
||||
|
||||
/* Monospace: */
|
||||
.RktIn, .RktRdr, .RktPn, .RktMeta,
|
||||
.RktMod, .RktKw, .RktVar, .RktSym,
|
||||
.RktRes, .RktOut, .RktCmt, .RktVal, .hspace {
|
||||
font-family: monospace;
|
||||
font-size: 95%;
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.RktValLink, .RktModLink {
|
||||
color: #2626c0; /* NOT bright blue */
|
||||
}
|
||||
|
||||
.RktErr {
|
||||
color: #c02626; /* NOT bright red */
|
||||
font-family: monospace;
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
/* Serif: */
|
||||
.inheritedlbl {
|
||||
font-family: 'Fenix',serif;
|
||||
|
@ -24,14 +36,8 @@
|
|||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.RktBlk {
|
||||
background-color: #f8faf8;
|
||||
white-space: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
/*** scribble.css ***/
|
||||
/**********************************************************************/
|
||||
/* scribble.css */
|
||||
|
||||
/* Monospace: */
|
||||
.maincolumn, .refpara, .refelem, .tocset, .stt, .refparaleft, .refelemleft {
|
||||
|
|
106
index.html
106
index.html
File diff suppressed because one or more lines are too long
296
main.rkt
296
main.rkt
|
@ -14,6 +14,11 @@
|
|||
[sandbox-error-output 'string])
|
||||
(make-evaluator 'racket)))
|
||||
|
||||
@(define typed/evaluator
|
||||
(parameterize ([sandbox-output 'string]
|
||||
[sandbox-error-output 'string])
|
||||
(make-evaluator 'typed/racket)))
|
||||
|
||||
@(define-syntax-rule (i body ...)
|
||||
(interaction #:eval evaluator body ...))
|
||||
|
||||
|
@ -79,7 +84,7 @@ understand the answers and solutions.
|
|||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{The plan of attack}
|
||||
@section{Our plan of attack}
|
||||
|
||||
The macro system you will mostly want to use for production-quality
|
||||
macros is called @racket[syntax-parse]. And don't worry, we'll get to
|
||||
|
@ -89,10 +94,10 @@ But if we start there, you're likely to feel overwhelmed by concepts
|
|||
and terminology, and get very confused. I did.
|
||||
|
||||
1. Instead let's start with the basics: A syntax object and a function
|
||||
to change it (a "transformer"). We'll work at that level for awhile to
|
||||
to change it---a "transformer". We'll work at that level for awhile to
|
||||
get comfortable and to de-mythologize this whole macro business.
|
||||
|
||||
2. Next, we'll realize that some pattern-matching would make life
|
||||
2. Soon we'll realize that pattern-matching would make life
|
||||
easier. We'll learn about @racket[syntax-case] and its shorthand
|
||||
cousin, @racket[define-syntax-rule]. We'll discover we can get
|
||||
confused if we want to munge pattern variables before sticking them
|
||||
|
@ -117,7 +122,7 @@ enhancements is @racket[syntax-parse].
|
|||
@; ----------------------------------------------------------------------------
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Transformers}
|
||||
@section{Transform!}
|
||||
|
||||
@verbatim[#:indent 2]{
|
||||
YOU ARE INSIDE A ROOM.
|
||||
|
@ -227,9 +232,9 @@ which is used to evaluate and run our program.
|
|||
|
||||
@subsection{What's the input?}
|
||||
|
||||
Our examples so far ignored the input syntax, and output a fixed
|
||||
syntax. But instead of throwing away the input, usually we want to
|
||||
transform the input.
|
||||
Our examples so far have ignored the input syntax and output some
|
||||
fixed syntax. But typically we will want to transform in the input
|
||||
syntax into somehing else.
|
||||
|
||||
Let's start by looking closely at what the input actually @italic{is}:
|
||||
|
||||
|
@ -244,7 +249,7 @@ The @racket[(print stx)] shows what our transformer is given: a syntax
|
|||
object.
|
||||
|
||||
A syntax object consists of several things. The first part is the
|
||||
s-expression representing the code, such as @racket['(+ 1 2)].
|
||||
S-expression representing the code, such as @racket['(+ 1 2)].
|
||||
|
||||
Racket syntax is also decorated with some interesting information such
|
||||
as the source file, line number, and column. Finally, it has
|
||||
|
@ -260,7 +265,7 @@ stx
|
|||
]
|
||||
|
||||
Now let's use functions that access the syntax object. The source
|
||||
information functions:
|
||||
information functions are:
|
||||
|
||||
@margin-note{@racket[(syntax-source stx)] is returning @racket['eval],
|
||||
only becaue of how I'm generating this documentation, using an
|
||||
|
@ -274,7 +279,7 @@ somthing like "my-file.rkt".}
|
|||
]
|
||||
|
||||
More interesting is the syntax "stuff" itself. @racket[syntax->datum]
|
||||
converts it completely into an s-expression:
|
||||
converts it completely into an S-expression:
|
||||
|
||||
@i[
|
||||
(syntax->datum stx)
|
||||
|
@ -297,9 +302,8 @@ In most cases, @racket[syntax->list] gives the same result as
|
|||
(syntax->list stx)
|
||||
]
|
||||
|
||||
When would @racket[syntax-e] and @racket[syntax->list] differ? Let's
|
||||
not get side-tracked now.
|
||||
|
||||
(When would @racket[syntax-e] and @racket[syntax->list] differ? Let's
|
||||
not get side-tracked now.)
|
||||
|
||||
When we want to transform syntax, we'll generally take the pieces we
|
||||
were given, maybe rearrange their order, perhaps change some of the
|
||||
|
@ -356,9 +360,9 @@ compiler, and @italic{that} syntax is evaluated:
|
|||
|
||||
@subsection{Compile time vs. run time}
|
||||
|
||||
@codeblock[#:indent 10]{
|
||||
@codeblock0{
|
||||
(define-syntax (foo stx)
|
||||
(make-pipe) ;This is not run time.
|
||||
(make-pipe) ;Ce n'est pas le temps d'exécution
|
||||
#'(void))
|
||||
}
|
||||
|
||||
|
@ -520,6 +524,8 @@ So let's try that:
|
|||
(our-if-using-match-v2 #t "true" "false")
|
||||
]
|
||||
|
||||
Joy.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@subsection{@racket[begin-for-syntax]}
|
||||
|
@ -569,7 +575,7 @@ syntax without evaluating them. We can implement forms like
|
|||
|
||||
@item{More good news is that there isn't some special, weird language
|
||||
for writing syntax transformers. We can write these transformer
|
||||
functions using the Racket language we already know and lovex.}
|
||||
functions using the Racket language we already know and love.}
|
||||
|
||||
@item{The semi-bad news is that the familiarity can make it easy to forget
|
||||
that we're not working at run time. Sometimes that's important to
|
||||
|
@ -671,7 +677,7 @@ working.
|
|||
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@subsection{"A pattern variable can't be used outside of a template"}
|
||||
@subsection{Pattern variable vs. template---fight!}
|
||||
|
||||
Let's say we want to define a function with a hyphenated name, a-b,
|
||||
but we supply the a and b parts separately. The Racket @racket[struct]
|
||||
|
@ -754,20 +760,21 @@ Well that explains it. Instead, we wanted to expand to:
|
|||
Our template is using the symbol @racket[name] but we wanted its
|
||||
value, such as @racket[foo-bar] in this use of our macro.
|
||||
|
||||
Can we think of something we already know that behaves like
|
||||
this---where using a variable in the template yields its value? Sure
|
||||
we do: Pattern variables. Our pattern doesn't include @racket[name]
|
||||
because we don't expect it in the original syntax---indeed the whole
|
||||
point of this macro is to create it. So @racket[name] can't be in the
|
||||
main pattern. Fine---let's make an @italic{additional} pattern. We can
|
||||
do that using an additional, nested @racket[syntax-case]:
|
||||
Is there anything we already know that behaves like this---where using
|
||||
a variable in the template yields its value? Yes: Pattern
|
||||
variables. Our pattern doesn't include @racket[name] because we don't
|
||||
expect it in the original syntax---indeed the whole point of this
|
||||
macro is to create it. So @racket[name] can't be in the main
|
||||
pattern. Fine---let's make an @italic{additional} pattern. We can do
|
||||
that using an additional, nested @racket[syntax-case]:
|
||||
|
||||
@i[
|
||||
(define-syntax (hyphen-define/wrong1.2 stx)
|
||||
(syntax-case stx ()
|
||||
[(_ a b (args ...) body0 body ...)
|
||||
(syntax-case (datum->syntax stx
|
||||
(string->symbol (format "~a-~a" #'a #'b))) ()
|
||||
(string->symbol (format "~a-~a" #'a #'b)))
|
||||
()
|
||||
[name #'(define (name args ...)
|
||||
body0 body ...)])]))
|
||||
]
|
||||
|
@ -793,23 +800,19 @@ Let's try to use our new version:
|
|||
Hmm. @racket[foo-bar] is @italic{still} not defined. Back to the Macro
|
||||
Stepper. It says now we're expanding to:
|
||||
|
||||
@racketblock[
|
||||
(define (|#<syntax:11:24foo>-#<syntax:11:28 bar>|) #t)
|
||||
]
|
||||
@racketblock[(define (|#<syntax:11:24foo>-#<syntax:11:28 bar>|) #t)]
|
||||
|
||||
Oh right: @racket[#'a] and @racket[#'b] are syntax objects. Therefore
|
||||
|
||||
@racketblock[(string->symbol (format "~a-~a" #'a #'b))]
|
||||
|
||||
is something like
|
||||
is the printed form of both syntax objects, joined by a hyphen:
|
||||
|
||||
@racketblock[|#<syntax:11:24foo>-#<syntax:11:28 bar>|]
|
||||
|
||||
---the printed form of both syntax objects, joined by a hyphen.
|
||||
|
||||
Instead we want the datum in the syntax objects (such as the symbols
|
||||
@racket[foo] and @racket[bar]). Let's use @racket[syntax->datum] to
|
||||
get it:
|
||||
Instead we want the datum in the syntax objects, such as the symbols
|
||||
@racket[foo] and @racket[bar]. Which we get using
|
||||
@racket[syntax->datum]:
|
||||
|
||||
@i[
|
||||
(define-syntax (hyphen-define/ok1 stx)
|
||||
|
@ -818,7 +821,8 @@ get it:
|
|||
(syntax-case (datum->syntax stx
|
||||
(string->symbol (format "~a-~a"
|
||||
(syntax->datum #'a)
|
||||
(syntax->datum #'b)))) ()
|
||||
(syntax->datum #'b))))
|
||||
()
|
||||
[name #'(define (name args ...)
|
||||
body0 body ...)])]))
|
||||
(hyphen-define/ok1 foo bar () #t)
|
||||
|
@ -829,7 +833,7 @@ And now it works!
|
|||
|
||||
Now for two shortcuts.
|
||||
|
||||
Instead of an additional, nested @racket[syntax-case] we could use
|
||||
Instead of an additional, nested @racket[syntax-case], we could use
|
||||
@racket[with-syntax]@margin-note*{Another name for
|
||||
@racket[with-syntax] could be, "define pattern variable".}. This
|
||||
rearranges the @racket[syntax-case] to look more like a @racket[let]
|
||||
|
@ -901,7 +905,7 @@ To review:
|
|||
|
||||
@item{You can't use a pattern variable outside of a template. But
|
||||
you can use @racket[syntax] or @tt{#'} on a pattern variable to make
|
||||
an ad hoc "fun size" template.}
|
||||
an ad hoc, "fun size" template.}
|
||||
|
||||
@item{If you want to munge pattern variables for use in the
|
||||
template, @racket[with-syntax] is your friend, because it lets you
|
||||
|
@ -1047,7 +1051,7 @@ Later, we'll see how @racket[syntax-parse] makes it even easier to
|
|||
check usage and provide helpful messages about mistakes.
|
||||
|
||||
|
||||
@subsection{Using dot notation for nested hash lookups}
|
||||
@subsection[#:tag "hash.refs"]{Using dot notation for nested hash lookups}
|
||||
|
||||
The previous two examples used a macro to define functions whose names
|
||||
were made by joining identifiers provided to the macro. This example
|
||||
|
@ -1057,9 +1061,15 @@ pieces.
|
|||
If you write programs for web services you deal with JSON, which is
|
||||
represented in Racket by a @racket[jsexpr?]. JSON often has
|
||||
dictionaries that contain other dictionaries. In a @racket[jsexpr?]
|
||||
these are represented by nested @racket[hasheq] tables.
|
||||
these are represented by nested @racket[hasheq] tables:
|
||||
|
||||
JavaScript you can use dot notation:
|
||||
@#reader scribble/comment-reader
|
||||
(i
|
||||
; Nested `hasheq's typical of a jsexpr:
|
||||
(define js (hasheq 'a (hasheq 'b (hasheq 'c "value"))))
|
||||
)
|
||||
|
||||
In JavaScript you can use dot notation:
|
||||
|
||||
@codeblock{
|
||||
foo = js.a.b.c;
|
||||
|
@ -1067,13 +1077,7 @@ foo = js.a.b.c;
|
|||
|
||||
In Racket it's not so convenient:
|
||||
|
||||
@#reader scribble/comment-reader
|
||||
(i
|
||||
; Nested hasheqs typical of a jsexpr:
|
||||
(define js (hasheq 'a (hasheq 'b (hasheq 'c "value"))))
|
||||
; Typical annoying code to get something:
|
||||
(hash-ref (hash-ref (hash-ref js 'a) 'b) 'c)
|
||||
)
|
||||
@racketblock[(hash-ref (hash-ref (hash-ref js 'a) 'b) 'c)]
|
||||
|
||||
We can write a helper function to make this a bit cleaner:
|
||||
|
||||
|
@ -1092,7 +1096,7 @@ We can write a helper function to make this a bit cleaner:
|
|||
(hash-refs js '(a b c))
|
||||
)
|
||||
|
||||
That's not bad. Can we go even further and use a dot notation somewhat
|
||||
That's better. Can we go even further and use a dot notation somewhat
|
||||
like JavaScript?
|
||||
|
||||
@#reader scribble/comment-reader
|
||||
|
@ -1101,12 +1105,43 @@ like JavaScript?
|
|||
(require (for-syntax racket/syntax))
|
||||
(define-syntax (hash.refs stx)
|
||||
(syntax-case stx ()
|
||||
;; If the optional `default' is missing, assume it's #f.
|
||||
[(_ chain)
|
||||
#'(hash.refs chain #f)]
|
||||
[(_ chain default)
|
||||
(let ([xs (map (lambda (x)
|
||||
(datum->syntax stx (string->symbol x)))
|
||||
(regexp-split #rx"\\."
|
||||
(symbol->string (syntax->datum #'chain))))])
|
||||
(with-syntax ([h (car xs)]
|
||||
[ks (cdr xs)])
|
||||
#'(hash-refs h 'ks default)))]))
|
||||
;; Gives us "sugar" to say this:
|
||||
(hash.refs js.a.b.c)
|
||||
;; Try finding a key that doesn't exist:
|
||||
(hash.refs js.blah)
|
||||
;; Try finding a key that doesn't exist, specifying the default:
|
||||
(hash.refs js.blah 'did-not-exist)
|
||||
)
|
||||
|
||||
It works!
|
||||
|
||||
We've started to appreciate that our macros should give helpful
|
||||
messages when used in error. Let's try to do that here.
|
||||
|
||||
@#reader scribble/comment-reader
|
||||
(i
|
||||
(require (for-syntax racket/syntax))
|
||||
(define-syntax (hash.refs stx)
|
||||
(syntax-case stx ()
|
||||
;; Check for no args at all
|
||||
[(_)
|
||||
(raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])"
|
||||
stx #'chain)]
|
||||
[(_ chain)
|
||||
#'(hash.refs chain #f)]
|
||||
[(_ chain default)
|
||||
;; Check that chain is a symbol, not e.g. a number or string
|
||||
(unless (symbol? (syntax-e #'chain))
|
||||
(raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])"
|
||||
stx #'chain))
|
||||
|
@ -1114,30 +1149,25 @@ like JavaScript?
|
|||
(datum->syntax stx (string->symbol x)))
|
||||
(regexp-split #rx"\\."
|
||||
(symbol->string (syntax->datum #'chain))))])
|
||||
;; Check that we have at least hash.key
|
||||
(unless (and (>= (length xs) 2)
|
||||
(not (eq? (syntax-e (cadr xs)) '||)))
|
||||
(raise-syntax-error #f "Expected hash.key" stx #'chain))
|
||||
(with-syntax ([h (car xs)]
|
||||
[ks (cdr xs)])
|
||||
#'(hash-refs h 'ks default)))]))
|
||||
;; Gives us "sugar" to say this:
|
||||
(hash.refs js.a.b.c)
|
||||
)
|
||||
|
||||
It works!
|
||||
|
||||
We've started to appreciate that our macros should give helpful
|
||||
messages when used in error. We tried to do that here. Let's
|
||||
deliberately elicit various errors. Are the messages helpful?
|
||||
|
||||
@i[
|
||||
;; See if we catch each of the misuses
|
||||
(hash.refs)
|
||||
(hash.refs 0)
|
||||
(hash.refs js)
|
||||
(hash.refs js.)
|
||||
]
|
||||
)
|
||||
|
||||
Not too bad.
|
||||
Not too bad. Of course, the version with error-checking is quite a bit
|
||||
longer. Error-checking code generally tends to obscure the logic, and
|
||||
does here. Fortuantely we'll soon see how @racket[syntax-parse] can
|
||||
help mitigate that, in much the same way as contracts in normal
|
||||
Racket or types in Typed Racket.
|
||||
|
||||
Maybe we're not convinced that writing @racket[(hash.refs js.a.b.c)]
|
||||
is really clearer than @racket[(hash-refs js '(a b c))]. Maybe we
|
||||
|
@ -1203,7 +1233,7 @@ conflict with a variable named @racket[x] in an outer scope:
|
|||
(printf "The outer `x' is ~s\n" x))
|
||||
]
|
||||
|
||||
When your macros also respect lexical scoping, it's easy to write
|
||||
When our macros also respect lexical scoping, it's easier to write
|
||||
reliable macros that behave predictably.
|
||||
|
||||
So that's wonderful default behavior. But @italic{sometimes} we want
|
||||
|
@ -1261,18 +1291,6 @@ But we can still define @racket[it] as a normal variable:
|
|||
it
|
||||
]
|
||||
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Robust macros: syntax-parse}
|
||||
|
||||
TO-DO.
|
||||
|
||||
TO-DO.
|
||||
|
||||
TO-DO.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
|
@ -1345,12 +1363,140 @@ The splicing variation is more convenient than the usual way:
|
|||
x))))
|
||||
)
|
||||
|
||||
When there are many body forms---and you are generating them in a
|
||||
When there are many body forms---and we're generating them in a
|
||||
macro---the splicing variations can be much easier.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{Robust macros: syntax-parse}
|
||||
|
||||
Functions can be used in error. So can macros.
|
||||
|
||||
@subsection{Error-handling strategies for functions}
|
||||
|
||||
With plain old functions, we have several choices.
|
||||
|
||||
1. Don't check at all.
|
||||
|
||||
@#reader scribble/comment-reader
|
||||
(i
|
||||
(define (misuse s)
|
||||
(string-append s " snazzy suffix"))
|
||||
;; User of the function:
|
||||
(misuse 0)
|
||||
;; I guess I goofed, but -- what is this "string-append" of which you
|
||||
;; speak??
|
||||
)
|
||||
|
||||
The problem is that the resulting error message will be confusing. Our
|
||||
user thinks they're calling @racket[misuse], but is getting an error
|
||||
message from @racket[string-append]. In this simple example they
|
||||
could probably guess what's happening, but in most cases they won't.
|
||||
|
||||
2. Write some error handling code.
|
||||
|
||||
@#reader scribble/comment-reader
|
||||
(i
|
||||
(define (misuse s)
|
||||
(unless (string? s)
|
||||
(error 'misuse "expected a string, but got ~a" s))
|
||||
(string-append s " snazzy suffix"))
|
||||
;; User of the function:
|
||||
(misuse 0)
|
||||
;; I goofed, and understand why! It's a shame the writer of the
|
||||
;; function had to work so hard to tell me.
|
||||
)
|
||||
|
||||
Unfortunately the error code tends to overwhelm and/or obscure our
|
||||
function definition. Also, the error message is good but not
|
||||
great. Improving it would require even more error code.
|
||||
|
||||
3. Use a contract.
|
||||
|
||||
@#reader scribble/comment-reader
|
||||
(i
|
||||
(define/contract (misuse s)
|
||||
(string? . -> . string?)
|
||||
(string-append s " snazzy suffix"))
|
||||
;; User of the function:
|
||||
(misuse 0)
|
||||
;; I goofed, and understand why! I hear the writer of the function is
|
||||
;; happier.
|
||||
)
|
||||
|
||||
This is the best of both worlds.
|
||||
|
||||
The contract is a simple and concise. Even better, it's
|
||||
declarative. We say what we want, without needing to spell out what to
|
||||
do.
|
||||
|
||||
On the other hand the user of our function gets a very detailed error
|
||||
message. Plus, the message is in a standard, familiar format.
|
||||
|
||||
4. Use Typed Racket.
|
||||
|
||||
@codeblock{#lang typed/racket}
|
||||
@interaction[#:eval typed/evaluator
|
||||
(: misuse (String -> String))
|
||||
(define (misuse s)
|
||||
(string-append s " snazzy suffix"))
|
||||
;; User of the function:
|
||||
(misuse 0)
|
||||
;; I goofed, and understand why! I hear the writer of the function is
|
||||
;; a cheerful type.
|
||||
]
|
||||
|
||||
With respect to error handling, Typed Racket has the same benefits as
|
||||
contracts. Good.
|
||||
|
||||
@subsection{Error-handling strategies for macros}
|
||||
|
||||
For macros, we have similar choices.
|
||||
|
||||
1. Ignore the possibility of misuse. This choice is even worse for
|
||||
macros. The default error messages are even less likely to make sense,
|
||||
much less help our user know what to do.
|
||||
|
||||
2. Write error-handling code. We saw how much this complicated our
|
||||
macros in our example of @secref["hash.refs"]. And while we're still
|
||||
learning how to write macros, we especially don't want more cognitive
|
||||
load and obfuscation.
|
||||
|
||||
3. Use @racket[syntax/parse]. For macros, this is the equivalent of
|
||||
using contracts or types for functions. We can declare that input
|
||||
pattern elements must be certain kinds of things, such as an
|
||||
identifier. Instead of "types", the things are referred to as "syntax
|
||||
classes". There are predefined syntax classes, plus we can define our own.
|
||||
|
||||
@subsection{Using @racket[syntax/parse]}
|
||||
|
||||
November 1, 2012: So here's the deal. After writing everything up to
|
||||
this point, I sat down to re-read the documentation for
|
||||
@racket[syntax/parse]. It was...very understandable. I didn't feel
|
||||
confused.
|
||||
|
||||
@codeblock{
|
||||
<span style='accent: "Kenau-Reeves"'>
|
||||
Whoa.
|
||||
</span>
|
||||
}
|
||||
|
||||
Why? The documentation is written very well. Also, everything up to
|
||||
this point prepared me to appreciate what @racket[syntax/parse] does,
|
||||
and why. That leaves the "how" of using it, which seems pretty
|
||||
straightforward, so far.
|
||||
|
||||
This might well be a temporary state of me "not knowing what I don't
|
||||
know". As I dig in and use it more, maybe I'll discover something
|
||||
confusing or tricky. If/when I do, I'll come back here and update
|
||||
this.
|
||||
|
||||
But for now I'll focus on improving the previous parts.
|
||||
|
||||
@; ----------------------------------------------------------------------------
|
||||
@; ----------------------------------------------------------------------------
|
||||
|
||||
@section{References and Acknowledgments}
|
||||
|
||||
Eli Barzliay's blog post,
|
||||
|
|
Loading…
Reference in New Issue
Block a user