fizz is an experimental language and runtime environment for the exploration of cognitive architectures and combined Machine Learning (ML) and Machine Reasoning (MR) solutions. It is based primarily on symbolic programming and fuzzy formal logic, and it features a distributed, concurrent, asynchronous and responsive inference engine.
- 1 About this document
- 2 Concepts & Syntax
- 3 Terms
- 4 Console
- 5 Primitives
- 6 Elementals
- 7 Advanced topics
1 About this document
This document is a user manual for fizz and assumes some basic familiarity with logic programming. It is divided into the following parts:
|Concepts & Syntax||introduces the concepts and the syntax used to describe and manipulate knowledge|
|Console||introduces the usage of the builtin console|
|Terms||introduces the various types that can be manipulated|
|Primitives||lists and describes all the primitives functions|
|Elementals||lists and describes all the supported Classes of Elementals|
|Advanced topics||describes more advanced topics including the Services|
|Release notes||contains pertinent information for each subsequent releases|
All code elements are presented in a distinct font like print("hello, world!"). Note that any tabulation shown in a listing is only present to enhance the readability of the code. Tabulations are not part of the language syntax. Primitives syntax is often a combination of code element and italic font. The part in italic is always the input to the primitive. Primitives inputs use special symbols :
|symbol?||indicates that the input is optional|
||indicates that the input can be either a symbol or a number|
|symbol+||indicates that the primitive can take on several symbols as input,|
|but at least one is required|
|symbol*||indicates that the primitive can take on several symbols as input,|
|but one is optional|
Many thanks to Joshua Nozzi (@JoshuaNozzi) and Keith Kolmos (@KeithMKolmos) for reviewing this document and providing many insightful corrections and suggestions.
2 Concepts & Syntax
If you are familiar with PROLOG, you will find that fizz takes some of its fundamental elements and syntax from it. There are five main concepts in fizz , which we will be discussing in this section:
|Knowledge||is a collection of related statements and/or prototypes.|
|Statement||is a collection of terms with an assigned truth value (think fact).|
|Predicate||is a labeled collection of terms with an assigned truth value range (or variable).|
|Prototype||is a chained collection of predicates that can be evaluated (think rule).|
|Elemental||is a runtime object which hold knowledge and can answer to query.|
|Service||is a runtime object which provide a unique service within the runtime.|
One of the main differences between PROLOG and fizz is how inference is done not by a single entity having access to all facts and rules, but by the cooperation of a collection of objects each having access only to what they must know (knowledges). Elemental objects in fizz are very much independent actors, which must exchange messages (mostly by a queries and replies mechanism) in order to execute any inferences. While this is far from being the most efficient method (and performance in some aspect is much worse for some types of inferences) it allows for instance a statement that is broadcasted to trigger the execution of any prototype that references it (via a predicate). It also supports inferences to be distributed among many cores and/or many hosts11not yet fully implemented.
A Knowledge groups a series of related Statements and Prototypes under the same logical concept (often refered in this document as ”label”). For example, if we wanted to create a list of the three basic colors we would define it as follows:
Knowledge definition always starts with a label that identifies the concept, followed by a frame (optional) and a series of Statements
and/or Prototypes within curly brackets. The frame (see Section 3.3 for details on that term) specified after the symbol
is known as the properties of the knowledge.
When a knowledge is used to define only statements, it is said to be factual knowledge. If it contains only prototypes, it is called a procedural knowledge.
A Statement, as we have seen in the example above, is a comma-separated list of terms within parantheses and terminated by a semicolon. We will look into all the supported terms in more details in Section 3, but so far we have used symbols and numbers. Each time a statement is defined, it can be assigned a truth value (indicating the relation of the statement to truth). Let’s look at an example where each statement is assigned a value to represent the likelihood of a given weather occurence in a particular city:
It’s so unlikely that you will see snow in Honolulu, that we here state that such statement is false.
A truth value is always a number between 0 (false) and 1 (true). When no truth value is assigned, the default value for a statement is 1. It is always defined last, prefixed with a :=. As part of a statement definition, we could also join a collection of properties that apply to the statement in the form of a frame object which is inserted right after the closing parenthesis. Here’s a version of the above knowledge where each statement have been timestamped (see section 5.2 for how):
Without getting ahead of ourselves (next section), a statement’s properties can be queried the same way as its terms:
A Predicate, while being syntactically similar to a Statement, represents not a fact but a question to be figured out. In the following example we will write a predicate which formulates the query: ”tell me where it is very likely to rain”:
The <0.7|1.0> at the end of the predicate is a truth value range. In this case, it indicates that we will only accept the statements where
truth values are between 0.7 and 1.0. Beside a range, a predicate will also accept a number or an unbound variable. The
latter will allow the truth value of each statements received for the predicate to be used in the following predicates.
Because a predicate is querying a particular knowledge, its label must be indicated. Here, we’re using the weather knowledge we defined earlier. The @ prefix indicates to the runtime that the predicate is referencing a knowledge and not a primitive. Primitives are built-in functions, such as lst.length, which can be used to get the number of elements in a list term. See Section 5 for all the supported primitives. If we wanted to use a primitive we would have omitted the @ like in this example:
There is however a situation when a prefix (other than !) can be used with a primitive. Using & will cause the primitive to be
executed on the runtime environement threads pool and not within the elemental. We will often reference this as ”offloading”.
A secondary meaning of the @ prefix is to indicate that the predicate should be considered a trigger. As stated in section 2), when a statement is broadcasted in the runtime environment, the predicate will set up the prototype to which it belongs for evaluation. For performance reasons, it is often best to indicate when a given predicate is not a trigger. For these situations, the @ prefix can be replaced by #. If we look back at our earlier example, any new weather statement will activate the prototype in which we used that predicate, we can change it as follow:
Lastly, if a caret (
^) is added right after the terms of the predicate, it will indicate that once the predicate as succeded, the solver should not
consider any other alternative based on any of the predicates that came before (this is similar to the cut operator in PROLOG). When the
predicate is part of series of prototypes, the other prototypes may still be considered depending on what type of predicates came before
the cut. To illustrate a cut let’s consider the following example which defines str.default as a knowledge which given a term
will either ”return” that term when it is a valid string or a second term if it is not:
If we now query this knowledge with a symbol as first term, we would expect the second term to be unified with the third term:
As we have started each prototypes with a call to the console.puts primitive, we can observe how the second and third prototypes were indeed not called. Have we had omitted the cut from the two first prototypes, we would have seen this:
Because each of the prototypes is composed of primitives only, they will be considered sequentially by the solver. In fact, the solver will always
considere prototypes sequentially but if a predicate is not a primitive, the following prototype will be considered while the solver
waits for answers to the query it put out for the predicate.
As we would expect, if the cutting predicate is not reached by the solver the cut will have no effect as we see in the following example:
As you probably noticed in the past examples, we have used as one of the terms :x and :length. These are variables and they can stand for any other type of terms (except variables themselves) during the inference process. See Section 3.6 for more details on variables.
A Prototype defines the relationship between a collection of statements, which may produce a new statement if the logical inference reaches a conclusion. For example, we could create a new logical concept that would contain a prototype based on the weather example we wrote earlier. We will call it surely_raining:
A prototype is composed of an entrypoint: a comma-separated list of terms within parentheses followed by a :- and a comma-separated collection of predicates terminated by a semi-colon. The entrypoint specifies what a predicate referencing this knowledge would be like and it is also used during inference to check if the prototype should be used. In this case, it would have a single term that will be unified with the local variable :x. If we wanted to check if it is surely raining in Paris, we would write:
If a caret (
^) is insterted between the entrypoint and the :-, it will indicates that during inferences when the prototype’s
entrypoint unifies with a statement or a query, no other prototypes should be considered, even if, in the end, the inference fails. This allows
for cases where a single prototype among many must be used.
In some instances, it’s often desired to take the negation of a predicate. This can be done by prefixing the predicate with a ! like this:
Since 3.14 is a number, the call to the primitive is.string will return a truth value of 0 since that primitive checks if its argument is a string. Negating this will result in the predicate returning 1 as its truth value.
When a prototype contains more than a single predicate, the truth value of the statements matching each predicate will be used to compute the truth value of the predicate as a fuzzy logical and. For example, to answer the question ”Where are we the most likely to see a rainbow?” we would write a new knowledge as follows:
With the weather knowledge we have, we would get the answer honolulu with a truth value of 0.1.
Before moving on to the next concept, lets backtrack to the following example:
The prototype could have been written using a constrained wildcard variable:
Using a variable would have allow us to take in the actual truth value of all the statements satisfying the predicate and use them in whichever way necessary.
Elementals in fizz are the main components of the runtime environment (also called substrate). In most cases, when a knowledge is
loaded a new elemental object is created to handle it, however a single elemental can manage multiple knowledges. There are several types of
elementals in fizz . See Section 6 for more details. Each elemental presents on the substrate is assigned an unique
identifier (GUID), unless one is provided.
Elementals objects can have properties associated with them. In most cases, such data allow for customization or optimization of the objects. This is done with a frame (which is a supported term, see Section 3.3) in between the knowledge’s body and its label, as seen in the following example:
In the example we request a specific class of elemental object to be instantiated using the class label and specify a min and max value. While these two properties are specific to MRKCRandomizer, class is a reserved label. There’s a few other reserved labels:
|alias||a symbol by which the elemental will also be known locally|
|class||a symbol indicating the class of the elemental object|
|guid||a string containing the GUID to be used by the elemental object|
|spawn||assigned to the symbol no will not cause the knowledge to|
|instantiate a new elemental|
If there is no existing matching elemental for a knowledge (that is, no elemental objects with the same name and capable of accepting the knowledge), a new one will be instantiated even if spawn is set to no.
Services are a special case of elemental objects which exist on the substrate as a singleton. Each of these objects provides services to all other elementals. The services are provided via the classic query/reply pattern shared by all elementals. See Section 7 for more details.
There are eight categories of terms in fizz . In this section we will introduce each one of them and see how they are each different from the other. They all have one thing in common, however: their immutability. While this may be common with atoms, it is less common with more complex data such as lists (at least in non-functional languages).
There are five different kinds of atoms in fizz :
They are the most basic data that can be handled.
A number in fizz represents a 64-bit numerical value. It can be an integer (signed or unsigned) or a floating point value, depending on how it is written and eventually postfixed. For example, if we consider the following statement:
The first term will be understood as a signed integer, the second term will be floating point, while the third term will be unsigned. The last term, by the addition of the postfix f, will be promoted from signed integer to floating point. Numbers expressed in scientific notation, such as 3e-2 will also be understood as floating point values. For two numbers to be successfully unified, their difference must be smaller than the epsilon value specified in the runtime environment configuration (see Section 4.2).
Strings in fizz are no different from other languages: a series of characters between double quotes. For example:
The common escape sequence using a backslash (for example "\n") is supported with the following characters:
|a||alert (bell) character|
Two strings will only unify if their content and length perfectly match. Note that at this time, Unicode isn’t supported.
Symbols in fizz are fundamental. Just like strings, they can contain characters as well as numbers but they are not started and terminated by double quotes. As such, they cannot contain spaces, nor start with a number. They are often used as identifiers. Here are a few example of valid symbols:
Two symbols will only unify if they perfectly match.
Binary terms are a way for fizz to handle elementals specific binary data. Such terms uses base64 to encode binary contents into a string, and they are specified in fizz code using a single quoted functor as in the following example:
Two binaries will only unify if there’s a perfect match of the decoded binary data. When a knowledge containing such term is parsed, the parsing will fail if the binary data fails to be decoded.
Guid terms are a way to represent globally unique identifier. Such terms are specified in fizz code using a single quoted functor as in the following example:
Lists are common and widely used. They allow the grouping a collection of terms into a single object. The syntax for a list is a comma-separated collection of terms (including lists) in between square brackets. For example, we could have written the color example from earlier where each colors RGB values are expressed as lists:
There’s a special kind of list that can be used to split the content of the list (head and rest). Used with recursion, it makes it possible to iterate over all the terms in a list possible. Consider the following knowledge:
The above example sets up lst.print with a prototype, which will print the head of the list and then recursively call itself with the rest of the list. The knowledge also constains a statement for when the list is empty. While it is not mandatory, it will cause a call to lst.print to always succeed.
In fizz , a frame is the equivalent of a dictionary in other languages. It stores key/value pairs. This is done by having a comma-separated collection of key/value pairs within curly braces. Here is an example:
While the value associated with a key can be any valid term (including a Frame), the key (also called label) can only be a valid symbol. Unlike with lists, unification of two frames will only be done over the labels that both terms have in common.
A Functor in fizz is akin to a structure, although it really is more of a named list (since a C-like structure will have fields). Here’s an example where the likelihood of a given weather is given as a functor:
When it comes to unifying functors. The label of each functor will be unified as well as each of the terms, therefore arity (the number of terms) of each functors also need to be the same.
Range terms are a way to express a range of numerical values between minimum and maximum values. The syntax of a range is something that we have already encountered in Section 2.3 when expressing the acceptable truth value range for a predicate. Here’s an example where we look at the manufacturer-reported range of some electrical cars:
A range will unify with a fellow range but also with a number as long as it is within the range. If we were to query the above knowledge for a car with a range of at least 300 miles, we would do so like this: @car.range(:x,300) and get the variable :x bound to the value tesla(model_s).
Variables in fizz , like in any logic programming language, are placeholders for any terms. As we have seen in several examples, the syntax for defining a
variable is a symbol prefixed with a colon. Often when unification is happening, it is handy to indicate that we do not care about a given term.
For such situations, we use the wildcard variable, which is a single underscore. If we take the car.range knowledge we defined above, we may want
to list all the tesla cars, but without caring about the range of each model. We would express this in a predicate as follows: @car.range(tesla(:m),_),
and the :m variable will be bound to the values model_s and model_x.
Because inferences in fizz are distributed (within a single substrate or accross multiple networked substrates), the number of replies to a query need to be minimized whenever possible. As such, variables support constraints specifications. Let’s look at an example where we are querying the gameboy.color knowledge we defined earlier:
If we were only interested in the colors where the red component is within 0.1 and 0.4, we could modify our query to use primitives to put constraints on the value bound to the :r variables:
We now have two matching colors instead of four. However, we did that by filtering the answers we got to our query on the gameboy.color knowledge. By specifying constraints directly on the variable within the predicate, we could have only received the two matching statements:
Constraints are specified after a variable with a question mark followed by list or a variable which will be bound at runtime to a
list. Each of the element in the list (which can be a functor, range or symbol) is a constraint that any value bound to the
variable must satisfy. In the above example, we indicated that the value for :r must be greater than 0.1 and less than 0.4.
Constraints support multiple functors as listed in this table:
|gte||greater than or equal|
|lte||lesser than or equal|
|lst.member||value is present in a list|
|lst.except||value is not present in a list|
|lst.incl||value is a list that include the items in a list|
|lst.excl||value is a list that exclude the items in a list|
|is.atom||value is an atom term|
|is.binary||value is a binary term|
|is.string||value is a string term|
|is.symbol||value is a symbol term|
|is.number||value is a number term|
|is.guid||value is a guid term|
|is.list||value is a list term|
|is.range||value is a range term|
|is.frame||value is a frame term|
|is.func||value is a functor term|
|is.unbound||no value is bound yet|
|is.even||value is an even number|
|is.odd||value is an odd number|
|str.find||value is a string which contains a specified substring|
Most functors requiere a single term except the is.* ones which can be given as a symbol, and aeq which expects two. Constraints can be use on any variables, including in a prototype’s entrypoint as shown here:
Constants in fizz are a special kind of variable whose content is static. Aside from the constants defined by the runtime environment, new ones can be defined via command line arguments. Constants do not support constraints, and are prefixed with a dollar sign. The following table lists all the constants provided by the runtime environment:
|$true||the boolean value for true|
|$false||the boolean value for false|
|$cores||the number of CPU cores enabled for fizz|
Volatiles in fizz are a special kind of constant whose content is most likely to change in between unifications. They can be used to add, for example, a time stamp to a statement being asserted (added to a knowledge) like in this example:
The syntax for volatiles is similar to constants, but with a percent instead of the dollar sign. The following table lists all the volatiles currently supported:
|%now||current time (UTC) in seconds since (Unix) Epoch|
|%today||date and time as a string|
|%rnd||a randomly generated number between 0 and 1|
|%sym||a randomly generated symbol|
|%gui||a randomly generated GUID as a string|
Because of their values are always changing, volatiles will always unify with anything. They should really not be used in a statement.
Because of its asynchronous and concurrent nature, fizz provides a console with a slightly unusual mode of operation. The default state of the console is to display any outputs coming from the runtime or from the queries entered by the user. Here’s the console when the program is started:
To switch to input, for example to enter a query or any of the supported console’s command, press the ESC key or one of the arrow keys. When the console is waiting for user input, it will display a ?-. If Ctrl-C is pressed, the console will exit the input state. The up and down arrow keys also serve to cycle thru the history. While, the console is in such mode, any output coming from the runtime will be buffered until the mode is exited. Press the enter key to exit the input mode. If a query or command was entered, it will be executed (in most case asynchronously) and any result will be printed:
Each solution to a query will be presented as a statement where each variable becomes one of the statement’s terms (in the order they
appears in the predicates). The truth value will be printed after, followed by the elapsed time (in seconds) since the query was sent. The last number is a
sequential number for the reply. It is worth noting that in fizz a query will not be stopped at the first answer.
When invoking the executable, the arguments of the command line can be any numbers of strings specifying the path and name of files to be loaded by the runtime, as seen in the above example. If the path leads to a folder, it will be assumed that it is a previously frozen runtime enviroment to kindle. The command line option -l can be used to switch the console logging on. This option will expect as argument the path and name of the log file to be created. For example:
The command line option -q can be used to specify a query to be executed right after the executable enter its Read–Eval–Print Loop (REPL). Be aware, thought that loading files in fizz is done asynchronously. Therefore a query using any yet-to-be loaded knowledges will fail. For example:
Any key pressed while outside of the console input state will cause a console.keypress statement to be broadcasted in the substrate. Any elemental can make use of it (via an activable predicate) and execute inferences based on the key that was pressed. The sole term of that statement is the ASCII code of the key. As an example, here’s a knowledge which display an hint to the user each time it press a key:
Lastly, pressing Ctrl-C outside of the input state, will cause the executable to terminate.
4.2 Adjusting the runtime
Severals parameters of the runtime environment can be adjusted by creating (or modifying) a JSON file. In order for the executable to use that file when it starts, the file must have the same name as the executable and have the exension .json. Here’s an example of a file that adjusts all the possible parameters:
It contains two sections: the runtime and the substrate. The former adjusts the threading and multi-cores models of the runtime while the later
adjusts the common behavior of all elemental objects will use.
Let’s look at the key/value pairs in the scheduler section:
|threads||represents the number of threads to be used. This number will not change at any|
|point in time|
|affinity||if set to true, each thread will be assigned to a given core of the host|
|spinning||the amount of time (in ms) that each thread will spin for when waiting to something|
|to do before going to sleep. Increasing that value may lower the latency at the cost|
|of a higher CPU load|
The offloader section is responsible for tuning the part of the runtime that handles offloaded processing using a dynamically resizable thread pool. The execution of any primitives flagged as offloaded will be executed on the pool instead of being executed within the elemental object calling it. The key/value pairs meanings is as follows:
|minpool||the minimum number of threads in the pool at any given time.|
|maxpool||the maximum number of threads in the pool at any given time.|
|timeout||the maximum amount of time a non-busy thread will wait before it exits the pool.|
|affinity||if set to true, each thread will be assigned to a given core of the host.|
The httpclient section is responsible for tuning the built-in HTTP client used by the elemental class FZZCHttpPuller. If this section is not present in the configuration file, the HTTP client will not be available. The key/value pairs meanings is as follows:
|dnstimeout||timeout (in seconds) when performing a DNS lookup.|
|maxconnect||maximum number of concurrent connection to any host.|
|maxrequest||maximum number of concurrent request for the same host (0 for no limit).|
|maxresolve||maximum number of concurrent Domain-name resolution (0 for default).|
|maxcontent||maximum size of the content to store in RAM, before storing it into a|
The livereload section deals with the automatic live code reload built in fizz . If this section is not present in the configuration file, this functionality will not be available. The command line option -n can be used to force this functionality to be disabled even if it is enabled in the configuration JSON file. The key/value pairs meanings is as follows:
|enabled||true to enable functionality, false to disable.|
|interval||interval of time (in ms) in between checks of the loaded scripts file’s timestamp.|
Because the substrate section of the JSON file deals with the configuration of each elemental, the format that is expected is a little different. The meaning of each value is:
|ttl||this is the time to live for anything posted on the substrate (in seconds).|
|grace||this is the grace period for any query (in seconds).|
|sspr||the maximum number of statements to be included in a single query reply. If|
|there are more statements to be sent, more replies will be sent.|
|pulse||the frequency (in miliseconds) at which each elementals gets to perform cleanups and other|
|cyclic tasks. The lower the value, the more CPU will be used.|
|epsilon||the upper bound on the relative error due to rounding in floating point arithmetic to be used when|
|lettered||default elemental class to be used when creating elemental to handle asserted|
|bundle.len||the maximum number of statement that can be bundled into a single knowledge before it is asserted|
|in the substrate.|
|bundle.tmo||the timeout value (seconds) before bundled statements are to be asserted if no other statements|
|is added to the bundle.|
Lastly, there are two command line options of interest: -s and -c. The formost can be used to specify an alternate settings JSON file as show here:
The latter allows constants to be defined as shown in this example:
The expected syntax for each defined constants is label=value. The value can be any term while the label is expected to be a symbol. Multiple -c options can be given.
Commands differs from queries by starting with a slash. Otherwise, their syntax is similar to a predicate (minus the truth value range). For example:
Will load the contents of the manual.fizz file into the runtime.
Close the console and terminate the executable.
Creates one (or more if a fourth terms is provided) elemental object which label will be the first term. The second term is the name of the /em class on which the elemental should be based. The third term contains the properties of the object. For example, to create ten elementals labeled product each with a statements limit of 1000, we would type:
Print to the console the number of cores the host computer has. This can be handy when you do not know that answer and want to adjust the configuration of the runtime.
The delete command allows for elementals to be removed from the substrate. The command will accept any numbers of symbols or string as its terms. The only supported strings are GUID while the symbols can be either an alias or a knowledge’s label. When the later is used, all elementals objects with this label will be removed:
If any of the terms doesn’t resolve into an actual elemental, the command will still complete successfully.
This command exports statements into a file storing tabular data (numbers and strings) in a plain text format, using the character from the third
term as delimiter for the generated lines. The first term indicates the path and filename of the file to be created, while the second term is
the predicate to be queried for. The list provided as fourth term contains the index of each columns (starting from
0) to be included in each lines. If provided, the fifth term is a frame which can specify a timeout value (in seconds) after which the command shall
complete (with the label tmo); and if the truth value of each statements is to be added as a column (with the label truth). When no
timeout is provided, the default is half a second.
As an example, let’s consider the following two knowledges:
To export all statements with a third term greater than 2005, we would use the command as follow:
Which will generate a products.csv file containing:
By using an intermediate knowledge instead of directly querying the knowledge that interests us, we could have further filter and/or modify the statements generated. Here’s a simple example which add a GUID to each of the lines that will be stored in the CSV file:
The export.csv command will then be:
And it the CSV file contents will be:
This command exports statements into a JSON file. The first term indicates the path and filename of the file to be created, while the second term is
the predicate to be queried for. If provided, the third term is a frame which can specify a timeout value (in seconds) after which the command shall
complete (with the label tmo). When no timeout is provided, the default is half a second. Note that only string, number, list and frame
can be exported to JSON.
As an example, let’s consider the following gameboy.color knowledge:
If we wanted to export the colors for which the red value if in between 0.1 and 0.4, we would do:
And the generated JSON file will contain:
Since there was more than one matching statement, the generated JSON object will contain an array with all the frames that were in the statements. The key for that array will be the label of the functor used to query the substrate. If the array only contains a single frame, the frame only will be exported as we can see in the generated file:
When the statements to be exported do not contains a single term, all the exportable terms will be exported within a JSON array. For example, if we consider the following knowledge:
and export it as follow:
The JSON file will then contains:
This command freezes the runtime enviroment to a binary format that can be kindled at a later point. The only accepted term is the path of the folder in which the saving to be done. Please note that any on-going query is not preserved.
Clear the console’s history.
Change the length of the console’s history. The default is 100.
Imports data from a file storing tabular data (numbers and strings) in a plain text format (using any characters from the third term as delimiter
and generates statements from each line. The first term indicates the path and filename of the file to be imported, while the second term is the
label to be used for the statements that will be generated. The list provided as fourth term contains the number of each columns (starting from
0) to be extracted from each line of the file and put in the statement. If provided, the fifth term is the number of lines from the file to skip and if there is a fifth term it will
be the number of lines to be processed.
If we wanted to import a CSV file such as this:
We would do as follows:
Since we wanted all the columns to be used, we simply provide an empty list as the fourth term. Also, if a column is detected as holding a numerical value, it will be automatically converted as a number. If we had wanted to convert the last column into a symbol (instead of the string we are getting), we would have had to use an intermediary elemental object which would have made the conversion. Something such as this:
It simply states that each time an input statement is broadcasted in the substrate (which is what import does), the last term will be converted to a symbol after having its case changed to lowercase. Finally, a new iris statement is asserted. Running it we now get:
Imports data from a JSON file. The first term indicates the path and filename of the file to be imported, while the second term is the
label to be used for the statement that will be generated. If provided, the third term is a list of options to be used for the processing of
the JSON objects contained in the file: stringify will keep all strings as string terms, symbolize will force all strings to be
converted as symbols. The default behavior is to convert the strings that can be considered symbol as such.
As example, let’s look at importing the foreign exchange rates from such a site as fixer.io22http://api.fixer.io/latest?base=USD. For the sake of simplicity, the JSON file below was abbreviated:
When we import the file, it will generate a statement containing a single frame. To further process the frame to fit your need, you will need to use some supporting knowledge, so that the right statements can be generated. In the sample etc/samples/fixer.fizz you will find such support code that will process the JSON data from above:
The code in fixer.fizz splits the work over two elementals: process and process.rates:
The first one, activated when an input statement is published on the substrate, fetchs from the frame it contains the value for the base and rates labels and pass them to the second elemental:
Since the rates are contained in a single frame, the elemental, concurrently fetchs all the label/value pairs from it, checking that they both match the expected type, then a new conversion statement is asserted.
Imports data from a file storing data in plain text and generates a single statements from each line. The first term indicates the path and filename of the file to be imported, while the second term is the label to be used for the statements that will be generated. If provided, the third term is the number of lines from the file to skip and if there is a fourth term it will be the number of lines to be processed. Each of the statement will have two terms: the first being a sequential number (starting at 0) and the second a string containing the whole line:
This command loads a runtime enviroment from a previously saved binary format. The only accepted term is the path of the folder in which the saving was done. Using kindle and freeze are more efficient than load and save since it use a direct binary format instead of an intermediary text format that would need to be parsed. However, it is not possible to edit the knowledge with a text editor.
Check if an elemental object is present on the runtime using its alias (when the argument is a symbol) or its GUID (when the argument is a string or a guid). In the following example, we modify the car.range knowledge to specify an alias for the elemental object that will get created:
We can then use that alias with the /knows command:
This command generates a list of all the elemental objects presents on the substrate. Each of the output lines, will contains, in order, the GUID, the class, label and, if available, the alias of each elementals:
The load command allows knowledge to be loaded from (properly formatted) text files. All terms in the predicate are expected to be strings.
If any of the files to be loaded have already been loaded, they will each be unloaded before being re-loaded. See the command unload (Section 4.3) to manually unload the knowledge from a given set of files.
The reload command allows knowledge to be re-loaded from (properly formatted) text files. All terms in the predicate are expected to be strings.
The poke command allows the properties of an elemental object to be written. For example, in the case of the rand elemental as defined in Section 2.5, we can change the value of its min properties as follows:
In this example, as in the one for the /peek command, we have used the label of the elemental to identify it. If there are more than one elemental responding to the same label, they will all receive and process the poke. In such situation, we should have use the GUID of the elemental to only target a single one.
The save command allows knowledge to be saved to a (properly formatted) text file, allowing it to be re-loaded at a later time. The command supports saving all knowledges or a selection based on their labels. To save all existing knowledges currently in the runtime environment, you only need to provide the name of the text file to be created:
If we wanted to save only the weather knowledges, we would do:
All terms except the first one are expected to be symbols.
The scan command will keep printing statistics on the runtime environment until none of the statistics changes in the substrate:
The breakdown of the statistic is identical to the stats command with the addition of qps and rps which are respectively queries per seconds and replies per seconds.
Instructs the runtime to start or stop printing any events (queries, replies, …) related to any of the knowledge labels provided as arguments. Spying is a handy way to see what is happening within the runtime and can be extremly useful to debug. In the following example, we spy on the gameboy.color knowledge then submit a query:
Output from spying will always be prefixed with spy. The first letter after the colon indicates the type of the observed event:
|T||a query is being scrapped.|
Print to the console some basic statistic about what is happening in the runtime:
The breakdown of the statistic is the following:
|e||current number of elemental objects in the substrate.|
|k||total number of knowledges on the substrate.|
|s||total number of statements on the substrate.|
|p||total number of prototypes on the substrate.|
|u||up time (in seconds) of the runtime.|
|t||elapsed time (in miliseconds) it took for the statistics to be collected.|
|q||total number of queries posted on the substrate.|
|r||total number of replies (in statements) posted on the substrate.|
|z||total number of statement posted (without query) on the substrate.|
Sends a message (in the form of a functor or a symbol) to an elemental object identified by its label, alias or GUID, the first argument. Not all elemental object can handle message. If the object is identified by its label, all objects with the same label will receive the message.
The unload command allows knowledge loaded from a file to be unloaded. All terms in the predicate are expected to be strings.
The wipe command will cause the runtime enviroment to be cleared of all existing elementals objects. The state of the runtime will be similar to the state at of the runtime when the executable is started.
The peek command allows the properties of an elemental object to be read. For example, if we have a rand elemental as defined in Section 2.5, we can read the value of its min properties as follows:
This Section details the primitives provided by the runtime. For each one, expected (and optional) arguments are described and for most a use case examples is given. All primitives are grouped under related categories.
This section contains all the primitives that deal with basic arithmetic.
This primitive will unify or bind the sum of its two first terms with the third. For example:
If the third term is a number or a variable bound to a number, one of the first terms can be an unbound variable. In that case the primitive will find the right value to make the addition valid as seen in the example below:
This primitive will unify or bind the division of the first term by the second with the third. For example:
If the third term is a number or a variable bound to a number, one of the first terms can be an unbound variable. In that case the primitive will find the right value to make the division valid as seen in the following example:
This primitive will unify or bind the integer division of the first term by the second with the third. For example:
If the third term is a number or a variable bound to a number, one of the first terms can be an unbound variable. In that case the primitive will find any values that will make the division valid as seen in the following example:
This primitive will unify or bind the inverse value of the first term with the second. For example:
This primitive will unify or bind results from performing an integer division between the first two terms with the third. For example:
The primitive doesn’t support the first or second term as unbound variables.
This primitive will unify or bind the multiplication of the first two terms with the third. For example:
If the third term is a number or a variable bound to a number, one of the first terms can be an unbound variable. In that case the primitive will find the right value to make the multiplication valid as seen in the following example:
This primitive will unify its third term with a value representing the similarity between the first two terms. For example:
This primitive will unify or bind the second term subtracted from the first one with the third. For example:
If the third term is a number or a variable bound to a number, one of the first terms can be an unbound variable. In that case the primitive will find the right value to make the subtraction valid as seen in the following example:
This primitive will unify or bind the sum of all terms with the last term. For example:
Countrary to the primitive add, this primitive does not support having any term unbound but the last one.
Under this grouping are all the primitives that provide very basic - and in most cases essentials - capabilities to the runtime.
The assert primitive allows for a statement to be added to an existing knowledge. If no elemental object capable of handling it exists, the runtime will instantiate one. The following example shows how a new statement is added at runtime to the weather knowledge:
The optional third term to the primitive is a frame which (as we have seen in section 2.2) provides the properties of the statement. Here’s how we could timestamp each statement when asserting them:
When a statement is asserted, it will be broadcasted in the substrate. See primitive repeal for the inverse function.
The primitive break will prematurely end an ongoing inference when its term unify to the boolean value true. The call will always evaluate to a truth value of 1.0.
See the sample leibniz.fizz for an example of its use.
The primitive break will prematurely end an ongoing inference when its term unify to the boolean value false. The call will always evaluate to a truth value of 1.0.
Like the assert primitive, bundle allows for a statement to be added to an existing knowledge. It however provides a way for the statements provided during consecutive (or concurrent) calls to be grouped into a single knowledge. Once a specified number of statements have been reached, or if the time elapsed since the last addition of a statement reaches a timeout value, the knowledge will be asserted into the substrate. In the following example, we define a procedural knowledge which when triggered (by any line.f statement) will assert a frag statement bundled within knowledges of 1024 statements in size:
If the last term isn’t given, the default value specified in the runtime settings (bundle.len) will be used.
The change primitive combines a repeal followed by an assert. In the following example, we use it to replace an earlier version of the statement with one with the current time:
Both terms are expected to be lists, describing the statement to be repealed and the statement to be asserted (as per the primitives repeal and assert).
This primitive will trigger the background execution of a console’s command. It can be used, for instance by an elemental to trigger the frequent saving of all (or selected) knowledge during the execution. Here’s an example:
This primitive will read a line from the console. Since the user will be prompted to enter a string as a synchronous operation, calling this primitive will only work when offloaded. For example:
This primitive will output the concatenation of the terms in the console. For example:
This primitive will broadcast statements into the runtime environment built from its terms. A functor (or a symbol plus a list) followed by an optional truth value and an optional frame is requiered for the primitive to create a statement. Multiple statements can be broadcasted if they are enclosed in lists. For example:
If multiple statements have the same label, they will be grouped according to the runtime environment’s sspr value and broadcasted together.
The define primitive allows for a prototype to be added to the knowledge contained on the substrate. If no elemental object capable of handling it exists, the runtime will instantiate one. The following example defines two prototypes which together print the content of a list given as input:
This would have had the same result as defining the lst.print knowledge as:
The first term is the label of the prototype, followed by a list containing the entrypoint. The third term is a list of options (
for example the symbol cut to turns the prototype into a cut one). The last term is a list containing the definitons of all the
predicates that makes up the prototype. Each of the predicate is it-self defined within a list. As shown in the above example, this list
is expected to have two elements. The first one is a list of options (symbols such as negate, primitive, cut, offload, trigger. The list can also
contain a range term and a frame term. The second term can either be a functor or a list containing the label of the
predicate and a list of the predicate’s terms.
See the primitive revoke for the inverse effect in Section 5.2.
Calling this primitive with no term will cause the on-going inference to fail by resolving to a truth value of 0. When used with a single term it will either test of a value is false or bind a variable to the value false.
The forget primitive will cause all elemental objects with the label given in its terms to be removed from the substrate.
The fuzz primitive will resolve with a truth value during inference the value passed as term:
The primitive hush will husher the ongoing inference. No statement will be published and no query will be answered. This is useful mainly in situations where a prototype is activated by a trigger predicate and the goal of it is to
This primitive will unify and/or substitute its sole term with the current host time (UTC, expressed in seconds since Unix epoch).
The peek primitive allows for a property of the calling elemental object to be read and unified and/or substitued with the second term. If the label provided as the first term is not a known property, the call will evaluate to a truth value of 0.0. For example, the following knowledge will multiply a value by a factor read from its properties:
Using the console command /poke we can modify the value of the knowledge property on the fly as shown here:
Accessing properties during inferences can allow for an easier reuse of knowledge. Please note that this primitive will not work when offloaded.
The poke primitive allows for a property of the calling elemental object to be written with the second term as value. If the label provided as the first term is not a known property or if it is a reserved label (like class guid label alias), the call will evaluate to a truth value of 0.0. Changing the value of a property during inference supports allow for the elemental to save states. The following example uses two properties to cycle through a list of words to only return a different word at each inference:
Just like with the peek primitive, offloading the execution of the primitive will not work.
The repeal primitive allows for a statement to be removed from any existing knowledge. If the functor or the terms list contains unbound variables, any matching statements will be removed.
Note that the elemental object that was storing the statement will not be detached from the substrate even if it doesn’t hold any more knowledge.
The revoke primitive allows for a prototype to be removed from the knowledge contained on the substrate. It is the reverse action of the primitive define (see Section 5.2). Using the example from that primitive we can remove both prototypes as follow:
Note that the elemental object that was storing the prototype will not be detached from the substrate even if it doesn’t hold any more knowledge.
The set primitive primary use is to assign a value to a variable, but it can also be used to unify terms or variables. When used in the former case, the order in the terms doesn’t matter as shown in the example below:
The set.if primitive functions as the primitive set but only if its third term is a number which boolean value is true. If it’s false, it will evaluate to a truth value of 1.0 and the variable will not be bound. For example:
The set.if primitive functions as the primitive set but only if its third term is a number which boolean value is false. If it’s true, it will evaluate to a truth value of 1.0 and the variable will not be bound. For example:
This primitive will unify and/or substitute its last term with the date/time (UTC, expressed in seconds since Unix epoch) build from the other terms. The first time is expected to be the calendar year, followed by the month and the day. Following optional terms are, in order: hours, minutes, seconds and miliseconds. For example:
This primitive will unify and/or substitute its terms in between a date/time (UTC, expressed in seconds since Unix epoch) and a string representation of that date. The first term is expected to be either a number or a variable and the second either a string or a variable. For example:
Calling this primitive will cause the inference to continue. This is sort of a no-op with limited use, except to turn a statement into a prototype. When it is used with a single term it will either test if a value is true or bind a variable to the value true.
The whisper primitive allows for a statement to be added to an existing knowledge. If no elemental object capable of handling it exists, the runtime will instantiate one. The following example shows how a new statement is added at runtime to the weather knowledge:
Unlike with assert, when a statement is whispered, it will not be broadcasted in the substrate. See primitive repeal for the inverse function.
All primitives related to comparing two terms are grouped in this category.
This primitive will evaluate to a truth value of 1.0 if its two first terms are almost equal numbers, and 0.0 if they do not. The third term is the maximum allowed difference between the two numbers to be estimated to be the same. For example:
This primitive will evaluate to a truth value of 1.0 if its two terms do not unify, and 0.0 if they do.
This primitive will evaluate to a truth value of 1.0 if its two terms do unify, and 0.0 if they don’t.
This primitive will unify or bind the comparison (lesser, greater or equal) between the first two terms with the third. For example: