Tutorial: Basic definitions - Conditionals, Date, Time, Factorial

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.
      • 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
      • 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.
    • 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
      • Modernization
        • Extend the functionality to cover newly emerged needs.
      • Experimentation, whether for:
        • exploring
        • learning
  • 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.
    • 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.
      • if the result of that evaluation is:
        • different than both, then evaluate the is-expression
          • i.e. $it = {{eval is-expression}}
        • equal to either, then if the part else isn't:
          • exists, then evaluate the isn't-expression
            • i.e. $it = {{eval isn't-expression}}
          • doesn’t exist, then return no
            • i.e. $it = no
  • contrived examples
    • {{eval if yes then ok}} => ok
      • explanation
        • here the condition-expression is yes and the is-expression is ok
        • firstly the condition-expression is evaluated
          • {{eval yes}} => yes
        • yes is neither no nor void, so the is-expression is evaluated
          • {{eval ok}} => ok
      • most condition-expressions behave the same
        • {{eval if something then ok}} => ok
    • {{eval if no then problem}} => no
      • explanation
        • here the condition-expression is no and the is-expression is problem
        • firstly the condition-expression is evaluated
          • {{eval no}} => no
        • no is one of no and void, so the is-expression is ignored
      • void behaves the same
        • {{eval if void then problem}} => no
    • {{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
  • 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.
  • 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.
  • 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.
          • an expression to be evaluated on the right part
            • The parameters are used with that (or those or ?).
              • Just like variables, but they have priority over any same-named variable.
      • 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).
      • 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.
      • 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.
      • 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 of that / those .

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)!
  • 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 number
          • if (?num <= 1) then 1 returns 1 for all numbers below 1
          • int= round ?num turns numbers into integers
      • 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.
        • {{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.
          • We can increase the limit with special Synthesis variable $eval-max-depth
            • {{eval let $eval-max-depth 20; factorial of 6}} => 720
    • 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.
      • 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 after some num:
                • Just use a value that covers your needs.
  • 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.
      • 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}

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.
    • in every page that either $includes or $delegates that page (see lower)
    • in every expression that its context has:
      • the name of either:
        • that page
        • any page that $includes or $delegates that page
      • as the value of an assignment to special Synthesis variable $page, i.e. either of:
        • let $page
        • $page=
  • 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)

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 (or these) has replaced that (or those).
    • {{eval !a = !b}} => no
      • shorter form
        • No space after !
    • {{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 need this/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:image
    • 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")}} image
    • 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 to 2
    • Press Enter
      • The last cell should get auto-updated to first is equal to second
  • 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 of this 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
            • {{eval if yes then equal else unequal}} => equal
          • {{eval Text("a is " equal " to b")}} => a is equal to b

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.
      • some bold words: Also optional, helping with reading.
  • 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.
  • 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
  • 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.
  • 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 with evalonce
  • 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}}
  • 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
    • Move your custom definitions inside page Synthesis/Custom
1 Like