This tutorial follows after Tutorial: First evaluations
The need for custom definitions
- Libraries
- are collections of definitions that work together
- in Synthesis lab have the form of Logseq pages
- educate Synthesis in both:
- understanding expressions
- performing evaluations
- can cover a variety of user needs, by accepting user-provided input
- However, there will always be scenarios that need more definitions:
- Static needs
- Completeness
- Add a secondary or rare but missing case.
- Very few libraries can be considered complete.
- Add a secondary or rare but missing case.
- Convenience
- Express something in a way that is more fitting to a particular workflow.
- This is usually about wrapping existing functionality within phrases that are either:
- more concise or easier to use
- more natural or easier to explain
- could be closer to a different language
- This is usually about wrapping existing functionality within phrases that are either:
- Express something in a way that is more fitting to a particular workflow.
- Integration
- Achieve something by exploiting two or more libraries that ignore each-other.
- Refactor libraries to reuse their functionality in a reduced way that is easier to maintain.
- Completeness
- Dynamic needs
- Real-time context configuration
- Easily and accurately adapt, without needing specialized help or interfaces.
- e.g. to temporarily exclude or switch some functionality
- Easily and accurately adapt, without needing specialized help or interfaces.
- Modernization
- Extend the functionality to cover newly emerged needs.
- Experimentation, whether for:
- exploring
- learning
- Real-time context configuration
- Static needs
- The very last bullet is a good enough scenario on its own.
Mini (but detailed) tutorial on conditionals
- Most of the details are intuitively known.
- Conditional expressions are about either:
- doing something only if some condition is met
- picking among available values, based on whether some condition is or isn’t met
- main syntaxes
- if condition then is
- if condition then is else isn’t
- explanation
- syntactical parts
- plain words:
if
,then
,else
- arbitrary expressions:
condition
,is
,isn't
- If composite, they may need to be put inside parentheses.
- plain words:
- Synthesis will do the following steps:
- evaluate the
condition
-expression - check whether the result of that evaluation is
- meaning to check it for its equality to values no and void
- This includes any nully javascript values.
- meaning to check it for its equality to values no and void
- if the result of that evaluation is:
- different than both, then evaluate the
is
-expression- i.e. $it =
{{eval is-expression}}
- i.e. $it =
- equal to either, then if the part
else isn't
:- exists, then evaluate the
isn't
-expression- i.e. $it =
{{eval isn't-expression}}
- i.e. $it =
- doesn’t exist, then return no
- i.e. $it = no
- exists, then evaluate the
- different than both, then evaluate the
- evaluate the
- syntactical parts
- contrived examples
{{eval if yes then ok}}
=> ok- explanation
- here the condition-expression is
yes
and the is-expression isok
- firstly the condition-expression is evaluated
{{eval yes}}
=> yes
- yes is neither no nor void, so the is-expression is evaluated
{{eval ok}}
=> ok
- here the condition-expression is
- most condition-expressions behave the same
{{eval if something then ok}}
=> ok
- explanation
{{eval if no then problem}}
=> no- explanation
- here the condition-expression is
no
and the is-expression isproblem
- firstly the condition-expression is evaluated
{{eval no}}
=> no
- no is one of no and void, so the is-expression is ignored
- here the condition-expression is
void
behaves the same{{eval if void then problem}}
=> no
- explanation
{{eval let condition no; if that condition then ok else problem}}
=> problem- Same as the previous example, but with part
else isn't
. - additional explanation
- the is-expression is ignored, but the isn’t-expression is evaluated
{{eval problem}}
=> problem
- the is-expression is ignored, but the isn’t-expression is evaluated
- Same as the previous example, but with part
- a few relational expressions
{{eval 1 < 2}}
=> yes{{eval 1 > 2}}
=> no{{eval 1 >< 2}}
=> yes><
means not equal
{{eval a= 1; b= 2; ?a = ?b}}
=> no- Space before
=
makes a difference between assignment and equality.
- Space before
- a more realistic example
{{eval a= 1; b= 2; let comp if (?a = ?b) then equal else unequal; Text("a is " ?comp " to b"}}
=> a is unequal to b
How to add a definition
- The last examples used an expression
?a = ?b
to mean “that a is equal to that b”. - What if we wanted to use directly the expression
that a is equal to that b
? - Let’s try it:
{{eval a= 1; b= 2; that a is equal to that b}}
=> $ERR{matching phrase not found} - Synthesis doesn’t know such a phrase, so it’s time for some (trivial) education.
- To teach a single phrase means to provide a definition for it, meaning these two things:
- the syntax of the phrase, for future expressions to be matched against
- It cannot contain nested phrases.
- an expression to be evaluated in place of each matched expression
- It can contain any structure of nested sub-expressions.
- the syntax of the phrase, for future expressions to be matched against
- To teach a single phrase means to provide a definition for it, meaning these two things:
- As usually, Synthesis supports multiple ways. Below are a few of them.
- First way
some num is equal to some other-num: that num = that other-num
- Each definition should be:
- in plain text
- in a Logseq block alone
- without definitions in its ancestor-blocks
- otherwise it becomes an extension of them (see lower)
- The colon
:
indicates a definition with:- a phrase to be matched on the left part
- The word
some
introduces parameters of given types.- Here both parameters are of type
num
, i.e. numbers. - We prefixed the second parameter, in order to differentiate it.
- Here both parameters are of type
- The word
- an expression to be evaluated on the right part
- The parameters are used with
that
(orthose
or?
).- Just like variables, but they have priority over any same-named variable.
- The parameters are used with
- a phrase to be matched on the left part
- Each definition should be:
- Now try again or revisit the previous effort:
{{eval a= 1; b= 2; that a is equal to that b}}
=> no
- Second way
some first-num is equal to some second-num: that first = that second
- Prefixed parameters can be used without their type-part (here without
-num
).
- Prefixed parameters can be used without their type-part (here without
- Try again or revisit the previous effort:
{{eval a= 1; b= 2; that a is equal to that b}}
=> [yes, no]- The result is a list of two values.
- One of them happens to be correct, but it doesn’t matter, as we should not reach this point, namely having more than one definition for the same phrase.
- Rather than failing or silently ignoring some, Synthesis lab attempts to evaluate both.
- This can be useful in some scenarios, but currently it gets complicated.
- The result is a list of two values.
- Exclude the first definition by either:
- invalidating it (e.g. by removing or replacing the colon
:
)- Invalid blocks (e.g. common notes) are ignored.
- omiting it, by placing it under an ancestor-block
$omit
$omit
is a special Synthesis word that instructs the parser to skip some blocks.
- deleting its block
- Don’t use undo, as it doesn’t currently update the modified timestamp.
- invalidating it (e.g. by removing or replacing the colon
- Then try again:
{{eval a= 1; b= 2; that a is equal to that b}}
=> no
- Third way
some num-1 is equal to some num-2: ?num-1 = ?num-2
- Numbered suffixes don’t change the type (here remains
num
). - Parameters can also be read with
?
instead ofthat
/those
.
- Numbered suffixes don’t change the type (here remains
- First way
Factorials
- Let’s now teach to Synthesis something less trivial.
- In Mathematics, the factorial of a positive integer number is an operation generally defined as a product:
- directly:
n! = n * (n-1) * (n-2) ... * 3 * 2 * 1
- recursively:
n! = n * (n-1)!
- directly:
- The tricky part is when
n
is not positive or not an integer or not a number. For the needs of this tutorial, we are going to look at only two of the possible ways.- First way
- definition
factorial of some num: if (?num <= 1) then 1 else (int= round ?num; ?int * (factorial of (?int - 1
- How many parentheses should we close at the end?
- It simply doesn’t matter, Synthesis can figure it out.
some num
ignores anything that is not a numberif (?num <= 1) then 1
returns 1 for all numbers below 1int= round ?num
turns numbers into integers
- How many parentheses should we close at the end?
- examples
{{eval factorial of 3}}
=> 6- Because 3 * 2 = 6
{{eval factorial of three}}
=> void- Because
three
is not a number in the current context.
- Because
{{eval factorial of 0}}
=> 1- Because 0 < 1
{{eval factorial of -3}}
=> 1- Because -3 < 1
{{eval factorial of 4.5}}
=> 120- Because (round 4.5) = 5
{{eval factorial of 6}}
=> $ERR{max length exceeded}- Some evaluations may go on forever, so Synthesis uses some limits.
- Clicking on the plus button reveals that the initial error was not the length, but the depth.
- Synthesis attempts to tolerate the errors, until only errors are left.
- The reasoning is beyond this tutorial’s scope.
- One error led to the other, finishing with a violation of the length limit.
- Synthesis attempts to tolerate the errors, until only errors are left.
- We can increase the limit with special Synthesis variable
$eval-max-depth
{{eval let $eval-max-depth 20; factorial of 6}}
=> 720
- definition
- Second way
- definitions
factorial of 0: 1
- explicit definition for the specific case of 0
factorial of 1: 1
- explicit definition for the specific case of 1
factorial of some num: let int round Math.abs(that num); that int times (the factorial of (that int minus 1
Math.abs(that num)
turns negative numbers into positive numbers
- equivalent alternative syntax
factorial of:
0: 1
1: 1
some num: let int round Math.abs(that num); that int times (the factorial of (that int minus 1
- Definitions of identical beginning are made siblings under a common parent block, which holds that beginning.
- The colon
:
at the end of the parent block is necessary.
- The colon
- examples
- To avoid surprises, before continuing exclude (delete/invalidate/omit) all definitions of factorial except of the last group.
{{eval factorial of 0}}
=> 1- By definition.
{{eval factorial of 3}}
=> 6- Because 3 * 2 = 6
{{eval factorial of -3}}
=> 6- Because -3 got turned into 3.
{{eval factorial of 6}}
=> 720- This way needs less depth.
{{eval factorial of 9}}
=> +- But eventually reaches the limit, which we can increase:
{{eval $eval-max-depth= 20; factorial of 9}}
=> 362880- However, having to increase the limit before each calculation is annoying.
- Globally increasing the limit is possible, but decreases the safety in other scenarios.
- Ideally, the limit should be increased by the exact calculation that actually needs to go beyond.
- Do it by simply moving
$eval-max-depth= 20;
right aftersome num:
- Just use a value that covers your needs.
- Do it by simply moving
- But eventually reaches the limit, which we can increase:
- definitions
- First way
- Defining specific cases is very useful, but so is defining fall-back cases, like this:
- definitions
factorial of some text: Error("positive number expected")
- Function
Error
returns a Synthesis error, i.e. an $ERR- Custom errors are helpful, although neutral values are often preferable.
- Function
factorial of some value: Error("unexpected value")
value
is an umbrella-like type that covers any and all values- That is if there is no definition that covers their actual type.
- Because after its actual type, everything passed is a value.
- examples
{{eval factorial of "text"}}
=> $ERR{positive number expected}{{eval factorial of Error()}}
=> $ERR{unexpected value}
- definitions
Scope of definitions
- Definitions like the above are available:
- everywhere in the page they are themselves
- Normally you would define factorial inside page
Synthesis/Number
.
- Normally you would define factorial inside page
- in every page that either
$include
s or$delegate
s that page (see lower) - in every expression that its context has:
- the name of either:
- that page
- any page that
$include
s or$delegate
s that page
- as the value of an assignment to special Synthesis variable
$page
, i.e. either of:let $page
$page=
- the name of either:
- everywhere in the page they are themselves
- That approach:
- takes care of managing the multitude of possible phrase-syntaxes
- enables proper scaling in environments of thousands of definitions
- but doesn’t address the need for internal locally-private definitions
- This is addressed:
- by other programming languages: through code-blocks and special keywords
- by Synthesis lab: through exploiting Logseq’s outliner (see below)
- This is addressed:
Local definitions
- They are blocks that hold expressions available only to the outlined context formed by:
- their direct parent-block
- the descendant-blocks of their direct parent-block
- They don’t have own parameters, but they share those of the one that uses them.
- Consider an earlier example:
{{eval a= 1; b= 2; that a is equal to that b}}
=> no - It can be written in separate blocks like this:
this a: 1
this b: 2
{{eval this a is equal to this b}}
=> no- Notice that
this
(orthese
) has replacedthat
(orthose
).
- Notice that
{{eval !a = !b}}
=> no- shorter form
- No space after
!
- No space after
- shorter form
{{eval let comp if (this a is equal to this b) then equal else unequal; Text("a is " that comp " to b")}}
=> a is unequal to b- Again an earlier example, but this time utilizing local definitions.
- By now you should be expecting alternative ways.
- Remember that definitions consist of:
- left part: a phrase
- separator: a colon
:
followed by space - right part: an expression
- When the name in the left part is composite (i.e. with dashes
-
), it doesn’t needthis
/these
.- The name should be without spaces, otherwise it stops being local.
- Add a definition-block with left part
first-num
and right part{{cell 1}}
- Add a definition-block with left part
second-num
and right part{{cell 2}}
- Should look like this:
- Use the new blocks like this:
{{cell comp= if (first-num is equal to second-num) then equal else unequal; Text("first is " ?comp " to second")}}
- Hover on the cells to see their expressions.
- Click on the expressions and modify their values to make them equal.
- e.g. change first-num’s cell-value from
1
to2
- e.g. change first-num’s cell-value from
- Press
Enter
- The last cell should get auto-updated to first is equal to second
- Remember that definitions consist of:
- But what if we wanted to reuse e.g.
this a
?this a: 9
{{eval this a}}
=> [1, 9]- Again we got a list of values, because of the same-phrase definition.
- Although local definitions are private to a page, they are shared by that page’s blocks.
- However, not every block has access to every local definition. Each block has access only to:
- the direct child-blocks of itself
- the direct child-blocks of its ancestors
- This way we can place local definitions to the exact context that we want them to be available in (see next point).
- Having learned how to break parts out of a block, let’s turn the last cell-block into pieces.
- definitions
this equality: if a-is-b then equal else unequal
a-is-b: this a is equal to this b
this a: 8
this b: 8
- All these definitions except of the first one:
- are placed as direct children of the block that uses them
- are invisible to other blocks in the page
- Therefore they don’t conflict with same-phrase definitions.
- In the context of
a-is-b
, the value ofthis a
has nothing to do with values 1 and 9 above.
- usage
{{eval Text("a is " !equality " to b")}}
=> a is equal to b- Synthesis did the following high-level steps:
- begin evaluating the macro
- begin evaluating the definition of
this equality
- begin evaluating the definition of
a-is-b
{{eval this a}}
=> 8{{eval this b}}
=> 8
{{eval 8 is equal to 8}}
=> yes
- begin evaluating the definition of
{{eval if yes then equal else unequal}}
=> equal
- begin evaluating the definition of
{{eval Text("a is " equal " to b")}}
=> a is equal to b
- begin evaluating the macro
- Synthesis did the following high-level steps:
- definitions
Date and Time
Time to put together everything that we have learned so far.
- Create a Logseq page named
Synthesis/Time
- Synthesis has already a sense of time as a type, but doesn’t know what to do with it.
- Let’s begin educating it by wrapping some Javascript inside Synthesis definitions. Paste the following markdown inside the new page:
- - **today**: now - **now**: JS.new(self.Date) - **date** from some num: JS.new(self.Date, ?num); if JS.isNaN(it) then Error( "invalid date" ) else it - - **date** of: - some time: Date.prototype.getDate.$call(that time) - today: date of (now) - **day** of: - some time: Date.prototype.getDay.$call(that time) plus 1 - today: day of (now) - **hours** of: - some time: Date.prototype.getHours.$call(that time) - now: hours of (now) - **minutes** of: - some time: Date.prototype.getMinutes.$call(that time) - now: minutes of (now) - **month** of: - some time: Date.prototype.getMonth.$call(that time) plus 1 - today: month of (now) - **seconds** of: - some time: Date.prototype.getSeconds.$call(that time) - now: seconds of (now) - **year** of: - some time: Date.prototype.getFullYear.$call(that time) - today: year of (now) -
- These are definitions that as usually have:
- right parts: We are going to ignore them in this tutorial.
- Synthesis doesn’t understand them either, it just calls them.
- left parts: We are going to use these ones.
- Notice how they are grouped by their common beginnings.
- Grouping is optional, but helpful.
- Notice how they are grouped by their common beginnings.
- some bold words: Also optional, helping with reading.
- right parts: We are going to ignore them in this tutorial.
- These are definitions that as usually have:
- We now have a tiny library for dates and times, but it always returns numbers.
- Numbers are fine for time, but not for weekdays.
- Let’s turn some numbers into weekdays. Paste the following markdown:
- the **weekday** for number: - some value: the weekday for number (num from that value) - 1: Sunday - 2: Monday - 3: Tuesday - 4: Wednesday - 5: Thursday - 6: Friday - 7: Saturday
- Tidy and easy to read.
- Although the order doesn’t matter.
- There is also a fall-back definition with
some value
.- It converts values to numbers and then to weekdays.
- Tidy and easy to read.
- We could use all of them directly. Here are just two ways:
- First way
{{eval Text(the year of today; slash; month of today; "/"; the date of (now); " - "; hours of (today); colon; the minutes of now; ":"; seconds of now; comma; " "; the weekday for number the day of today)}}
=> 2024/4/11 - 10:45:29, Thursday
- Second way
{{eval Text((the year of today) slash (month of today) slash (the date of (now)) space dash space (hours of (today)) colon (the minutes of now) colon (seconds of now) comma space (the weekday for number (the day of today)))}}
=> 2024/4/11 - 10:46:38, Thursday
- First way
- Yes, I don’t like these ways either.
- These are so-called one-liners, good for one-time usage.
- They do work, but working with them is a pain.
- After all, an one-liner shouldn’t occupy more than one line.
- These are so-called one-liners, good for one-time usage.
- Let’s take advantage of what we have learned, to start unleashing Synthesis’ potential. Paste the following markdown:
- date and time: Text(date " - " time comma space weekday) - **text** from: - date: Text(year slash month "/" monthday) - year: the year of today - month: month of today - monthday: the date of (now) - time: Text( these hours, colon, minutes-of-now, ":" , these seconds ) - these hours: hours of (today) - minutes-of-now: the minutes of now - these seconds: seconds of now - weekday: the weekday for number this day - this day: the day of today
- This mixes many different styles on purpose (see lower).
- It can be rewritten in countless ways.
- Use it like this:
{{eval date and time}}
=> 2024/4/11 - 10:57:26, Thursday- To prevent the timestamp from changing every time you visit the page, replace
eval
withevalonce
- To prevent the timestamp from changing every time you visit the page, replace
- Now go ahead and change the definitions yourself, so as:
- the code to match a style of your choice
- the output to match your preferred date and time format
- Maybe turn months from numbers to words.
How to use definitions in other pages
When you are ready with your definition of phrase date and time
, you may want to:
- use it in another page like this:
{{eval let $page synthesis/time; date and time}}
{{eval $page= synthesis/time; date and time}}
- use it multiple times within a specific page:
- add to that page a line
$delegate Synthesis/Time
- then use it like if it was in the same page:
{{eval date and time}}
- add to that page a line
- make it available to every other page like this:
- Go to page
Synthesis/Base
and either:- add a line
$include Synthesis/Time
- append
, Synthesis/Time
to the existing$include
line
- add a line
- Move your custom definitions inside page
Synthesis/Custom
- Go to page