Jump to content

Wikifunctions:How to create implementations

From Wikifunctions
Translate this page; This page contains changes which are not marked for translation.

This page provides a more detailed guide to creating implementations, beyond the overview at Wikifunctions:Introduction.

Types of Implementations

In Wikifunctions, an implementation can take one of two main forms: code or composition. A code implementation directly expresses the function’s logic in any of the available programming languages (currently, Python3 or JavaScript). It’s most useful when the function’s behavior can’t easily be built from existing Wikifunctions, or when precise control over data processing is needed.

A composition implementation, on the other hand, defines the function entirely by combining other existing functions, without writing code. Compositions are easier to understand, reuse, and translate into other languages automatically, but they depend on suitable building blocks already being available and often are less performant. In general, start by checking whether your goal can be achieved through composition; if it can’t, or if performance or fine-grained logic matters, choose a code implementation instead.

There's a third type of implementation: built-ins. These are run by system-built code, and are not editable. Most likely all pre-defined functions will have a built-in implementation. In such cases, when opening the implementation page, you will see a message stating "This is a built-in implementation within the core software and is not visible here."

Practice Test-Driven Development

Test-Driven Development, or TDD, is a way of creating software where you write tests before you write the actual code. Tests are just a way of understanding what your function is supposed to do by listing examples and how they should work.

In Wikifunctions, writing tests first offers significant benefits. If you are working on an implementation, you can run your code against the existing tests to check that it behaves as expected. This makes it easier to catch errors early before even publishing your implementation. Tests are also a powerful way to describe the function you need. By writing tests, you clearly communicate the expected behavior, and the community can step in and help with the implementation, or create alternative implementations.

Imagine you want to implement a Function that combines two strings with a space in between. A basic test for this might look like:

join_with_space( "Hello", "world" ) => "Hello world"

Once you have defined the basic behavior, it's important to think about edge cases—situations where the inputs are such that you might need some special behavior.

For example, what if your first string ends in a space, or your second string starts with a space? Should the function keep all existing spaces and add another one, or should it return a neatly spaced result with just a single space between the words? Depending on your requirements, you should build a test for such a case. For example, a test for this edge case could be either one of these — but not both!:

join_with_space( "Hello   ",  "  world") => "Hello world"

or ...

join_with_space( "Hello   ",  "  world") => "Hello      world"

Make sure that all possible special cases are covered by your tests, and once you have them, make sure that they are all connected to the Function by selecting them and clicking "Connect".

Code implementations

Wikifunctions code editor initialized with a function template
When adding a new code implementation, the function template is initialized with the function and argument names

When creating an implementation using code, the Wikifunctions UI will set up a code editor with the selected language activated for syntax highlighting and validation. When the function to implement and the programming language are selected, this editor will be set with the correct function template: do not remove or edit this! Keep your code inside this function signature: if you need auxiliary functions, you can simply declare them inside your top-level function declaration.

Using input types in your code

Wikifunctions types String (Z6) and Boolean (Z40) can be used as if they were native types in both JavaScript and Python3. For example, a String input can be concatenated or a Boolean be directly used in a logical expression. To use other types in your code implementations, you will have to look deeper into the type, figure out if it has a conversion to a native equivalent, and treat it accordingly.

For example, let's say we want to write some Python code that handles an input of type Gregorian calendar date (Z20420). In the page for this type, let's look at its "type converters to code" and search for a Python one. The converter Python converter from Gregorian calendar date (Z20424) will be applied to our date input before our code, so looking at "native type" we know that our input will be a Python dict and looking at the implementation we can see the keys we can expect:

return {
    'K1': year,
    'K2': month,
    'K3': day
}

When a type doesn't have a converter, you'll have to access its parts directly using its keys. For example, let's assume that your function Z30000 has one input Z30000K1 of type Monolingual text (Z11). This type has two keys: Z11K1 for the language (itself an object of type Natural language (Z60)), and Z11K2 for the string value. Similarly, the language object has a key Z60K1 for the language code. So, to get a string with the language code and the text, you can write:

function Z30000( Z30000K1 ) {
	// e.g. "en: some text"
	return Z30000K1.Z11K1.Z60K1 + ": " + Z30000K1.Z11K2;
}

For a quick reference of the available type conversions visit the default type conversion table.

Returning the right outputs

Similarly to the way you treat inputs, outputs need to match the type specified in the function definition. Some can be treated as simply as native types, so any string output will be converted into a Wikifunctions String (Z6) object and any native boolean returned by your implementation will be converted into a Wikifunctions Boolean (Z40) object.

Those types that have "type converters from code" will apply the appropriate function to the output, in order to build the required type. Following the example of Gregorian calendar date (Z20420), the Python converter to Gregorian calendar date (Z20443) will transform the output from your implementation–a dict with K1, K2 and K3 keys–into its correct ZObject representation.

For types that don't have converters from code, you can carefully build and return the output objects. For example, imagine that our function Z30000 takes two string inputs: language code and text, and should return a Monolingual text (Z11) object. You can do this in Python by using the ZObject constructor:

def Z30000(Z30000K1, Z30000K2):
    lang_object = ZObject(
        {"Z1K1": "Z9", "Z9K1": "Z60"},
        Z60K1 = Z30000K1
    )
    return ZObject(
        {"Z1K1": "Z9", "Z9K1": "Z11"},
        Z11K1 = lang_object,
        Z11K2 = Z30000K2
    )

Or in JavaScript:

function Z30000( Z30000K1, Z30000K2 ) {
  const langObject = new ZObject( 
    new Map( [ [ "Z60K1", Z30000K1 ] ] ),
    { "Z1K1": "Z9", "Z9K1": "Z60" }
  );

  const monolingualKeys = new Map( [
    [ "Z11K1", langObject ],
    [ "Z11K2", Z30000K2 ]
  ] );
  return new ZObject(
    monolingualKeys,
    { "Z1K1": "Z9", "Z9K1": "Z11" }
  );
}

You can learn more about the ZObject class and other useful constructors in Wikifunctions:Execution_targets#Custom_and_non-built-in_type_conversion

Code in Python

In this section, we give a concrete example on how to create an Implementation in the form of Code in Python. Say we want to create an implementation for a Function which combines two input strings by putting a space between them, returning the result of that. Let’s assume the ZID of that function is Z30000.

The implementation is defined using the ZID of the function. So are the arguments of the function. So in the case of the function Z30000 with its two arguments, the function template should look like this:

def Z30000(Z30000K1, Z30000K2):

As we described before, we know that the arguments (Z30000K1 and Z30000K2) are Strings, so we can treat them as simply as we use native strings in Python. Also, the function should return a String. For our example, we simply return the two arguments concatenated with a space in between:

def Z30000(Z30000K1, Z30000K2):
    return Z30000K1 + " " + Z30000K2

Since this is Python, do not forget to indent this line.

If you have followed our advice to practice TDD, you will already have a set of tests that you know should work. If that's the case, while building your implementation you can click on the circular arrow in the Tests panel, and see if your implementation passes all of them.

Remember that the runtime has no state. Don’t assume values for global or static variables. If you have further questions about what you can and cannot do in Python, ask on Wikifunctions:Python implementations.

Code in JavaScript

In this section, we give a concrete example on how to create an Implementation in the form of Code in JavaScript. Say we want to create an implementation for a Function which combines two input strings by putting a space between them, returning the result of that. Let’s assume the ZID of that function is Z30000.

The implementation is defined using the ZID of the function. So are the arguments of the function. So in the case of the function Z30000 with its two arguments, the function template should look like this:

function Z30000( Z30000K1, Z30000K2 ) {

}

We know that the arguments (Z30000K1 and Z30000K2) are Strings, so we can treat them as native strings in JavaScript. Also, the function should return a String. To complete our function, you can then add a line concatenating the input strings, like this:

function Z30000( Z30000K1, Z30000K2 ) {
  return Z30000K1 + " " + Z30000K2;
}

If you have tests as recommended in the TDD section above, click on the circular arrow in the Tests panel and see if your implementation passes all of them.

Remember that the runtime has no state. Don’t assume values for global or static variables. If you have further questions about what you can and cannot do in JavaScript, ask on Wikifunctions:JavaScript implementations.

Compositions

In this section, we give a concrete example on how to create an Implementation in the form of a composition. Say we want to create an implementation for a Function which combines two input strings by putting a space between them, returning the result of that. Let’s assume the ZID of that function is Z30000.

It is usually a good idea to first think about how to combine existing functions in order to create the desired output. Sometimes this might be trivial, and you can just go ahead with composing functions, but in many cases it is worthwhile to note the desired composition down.

For example, for the given Function, we could use the existing Function join two strings (Z10000). That Function takes two strings and makes one string out of them. But we need to add a space in between. So we first concatenate a space to the end of the first string, and then concatenate the second string to the result of that first concatenation. So our composition could be noted down like this:

join_strings( join_strings( Z30000K1, " " ), Z30000K2 )

There are multiple ways to write a composition: notice that the the same thing could be accomplished with:

join_strings( Z30000K1, join_strings( " ", Z30000K2 ) )

An alternative implementation could also use the existing Function join two strings with separator (Z15175), so your composition could be:

join_with_separator( Z30000K1, Z30000K2, " " )

As you can see, there are multiple ways to accomplish this, so take your time and explore the existing functions available in the Wikifunctions:Catalogue.

Let's try to create the second example, join_strings(Z30000K1, join_strings(" ", Z30000K2)).

In order to create this:

  1. Start adding an implementation by clicking on the "+" button in the "Implementations" table from your Function page.
  2. Make sure that the option "composition" under "Implementation" is selected.
  3. Click on the "›" icon or click the "Select Function" link to expand the function details.
  4. Under "function" search for the name of the outermost function you want to use, which in this example is "join two strings". Then, select the wanted function from the dropdown list.
  5. You will see two new fields, one for each of the arguments needed for the selected function.
  6. For the first argument, we want to select the first input to our function:
    1. Next to "first string", click on the "…" button and select "Argument reference".
    2. Under "key id" you can now select the first string of your function from the dropdown, which contains all available inputs.
  7. For the second argument, we want to use the result of another call to "join two strings":
    1. Next to "second string", click on the "…" button and select "Function call".
    2. Under "function", search and select the function you want to call, which is again "join two strings".
    3. For this inner function call, configure the "first string" by simply typing a white space in the text field, and configure the "second string" as you did for step 6: change it using the "…" button to "Argument reference", and then select the second argument in the dropdown.

Your composition is now complete!

You can expand and collapse the details using the chevron buttons to see how it looks. If you already have Tests as recommended in the TDD section above, click on the circular arrow in the Tests panel and see if your implementation passes the tests. Once your composition passes the tests, go ahead and publish it.

A Wikifunctions composition implementation in its expanded view
A Wikifunctions composition implementation in its expanded view
A Wikifunctions composition implementation in its collapsed view
A Wikifunctions composition implementation in its collapsed view


Wikifunctions error handling

As a Wikifunctions function creator, you might want to warn the user calling your function when something goes wrong in the code, or when their inputs do not fit a certain criteria. For example, if your Function expects a string input to contain a name, you might want to check that the name is not empty and, if it is, show an error message that the person using your Function can understand.

For this purpose, you can use Wikifunctions.Error() from your code implementations or the function Throw Error (Z851) from your compositions.

In this section you will learn:

  • How to throw errors from both code and composition implementations,
  • how to test error cases with your tests, and
  • how to catch and handle errors thrown by functions that you are using in your compositions.

Throwing errors from code implementations

When writing code, both from JavaScript and Python, you can use Wikifunctions.Error() to end the execution with a wanted error.

Wikifunctions.Error() has two parameters:

  1. Error type: a string containing the ZID of the error type you want to return.
  2. Error arguments: a list of string arguments to build the error.

Let's go one by one:

Error type

An error type is an object that describes a kind of error that could happen in the system. There are a number of built-in error types but you can also build other error types that are more suited to your use case. When using errors, the best idea is to browse through the available error types, which can be explored in this list, by selecting Error type (Z50) in the type dropdown.

If there are no errors that fit your case, create a new one by going to the create page, and selecting "Error type" in the type selector. Then, set the necessary fields:

Error type creation in Wikifunctions
To create new Error type (Z50), edit the label and add the necessary keys.
  • Label – This is the main title of the error, which should contain a short but descriptive message. Set the error label on the right-side box titled "About".
  • Keys – Keys contain additional information about what caused the error. For example, if an input had the wrong format, you might want to inform the user of what was the input content when raising the error. To use them in your implementations, keys should be strings. To create a key, click on the "[+]" icon below "keys", select "String" under "value type", and add a label that describes it appropriately, for example "current input". Once you have all the necessary keys, you can Publish your error type and keep the ID of the newly created object.

Error arguments

Error arguments correspond to the keys of the Error type, and they should provide additional information to understand the error. Error arguments should always be Strings.


Throwing errors from code implementations

Say that you want an error to be thrown when either your first or your second input are empty. You have already created the error type, and the assigned ZID was Z30005. Your error type has two string keys:

  • Input key: contains the key of the failing input
  • Input value: contains the current value of the failing input

To raise an error in your JavaScript implementation you should do something like this:

function Z30000( Z30000K1, Z30000K2 ) {
  if ( trim( Z30000K1 ) === '' ) {
    // If the first input is empty, raise error Z30001
    // with two string args: [ first input key, first input value ]
    Wikifunctions.Error( 'Z30005', [ "Z30000K1", Z30000K1 ] )
  }
  if ( trim( Z30000K2 ) === '' ) {
    // If the second input is empty, raise error Z30001 with
    // with two string args: [ second input key, second input value ]
    Wikifunctions.Error( 'Z30005', [ "Z30000K2", Z30000K2 ] )
  }
  return Z30000K1 + " " + Z30000K2;
}

To raise an error in your Python implementation you should do something like this:

def Z30000(Z30000K1, Z30000K2):
    if Z30000K1.strip() == "":
        # If the first input is empty, raise error Z30001
        # with two string args: [ first input key, first input value ]
        Wikifunctions.Error("Z30005", ["Z30000K1", Z30000K1])

    if Z30000K2.strip() == "":
        # If the second input is empty, raise error Z30001
        # with two string args: [ first input key, first input value ]
        Wikifunctions.Error("Z30005", ["Z30000K2", Z30000K2])

    return Z30000K1 + " " + Z30000K2

Remember that calling Wikifunctions.Error() ends the execution!

Throwing errors from compositions

If you are creating a composition implementation, you can throw an error by calling the special function Throw Error (Z851).

Throw Error function (Z851)

The Throw Error (Z851) function is similar to the Wikifunctions.Error() method and takes two arguments:

  • Error type – A reference to the Error type you want to raise
  • Error parameters – A list with the arguments to create your Error, which should also be Strings.

Using Throw Error

Say you want to modify your composition so that it first checks that the arguments are not empty, and raises an error when it finds an empty one. Then, if none of them are empty, it runs the original composition, which is:

join_with_separator( Z30000K1, Z30000K2, " " )

To do this, you need to create conditional paths, for which you can use the If (Z802) function. Think of it like this:

  • If the first argument is empty, throw an error for the first argument
  • Else, proceed to check the second argument
  • If the second argument is empty, throw an error for the second argument
  • Else, proceed with the success path and return the joint strings with the space separator

Or in functional pseudo code:

if (
    is_empty_string( Z30000K1 ),
    throw_error( Z30005, [ "Z30000K1", Z30000K1 ] ),
    if(
        is_empty_string( Z30000K2 ),
        throw_error( Z30005, [ "Z30000K2", Z30000K2 ] ),
        join_with_separator( Z30000K1, Z30000K2, " " )
    )
)


Let's create this step by step:

  1. Start adding an implementation by clicking on the "+” button in the "Implementations" table from your Function page.
  2. Make sure that the option "composition" under "Implementation" is selected.
  3. Click on the "›” icon or click the "Select Function" link to expand the function details.
  4. Under "function" search and select the outermost function you want to use, which in this case is "if".
  5. Check the validity of the first argument:
    1. Next to "condition", click on the "…" button and select "function call"
    2. Under "function", search and select the function "is empty string"
    3. Next to "input", click on the "…" button and select "argument reference"
    4. Under "key id", select the first input (e.g. "fist string")
    5. You can now collapse the "condition" function call if you want, by clicking on the down-chevron next to "condition". You should see something like "f(x) is empty string( → first string )"
  6. Throw error if the condition is true:
    1. Next to "then", click on the "…" button and select "function call"
    2. Under "function", search and select the function "Throw Error"
    3. Under "error type", search and select your error type (you can directly input the error ID if you know it, e.g. Z30005)
    4. Under "error parameters", add two items by clicking on the "+" button
    5. For "Item 1" (offending argument key), set its type to "String" and write the argument identifier (e.g. "first input", or "Z30000K1")
    6. For "Item 2" (offending argument value), click on the "…" next to "Item 2" and select "argument reference"
    7. Under "key id", select the first input (e.g. "first string")
    8. You can now collapse the "then" function call if you want, by clicking on the down-chevron next to "then". You should see something like "f(x) Throw Error( Input was the wrong format, Typed list( Object, "first input", → first string ) )"
  7. Continue to check the second argument if the first was good:
    1. Next to "else", click on the "…" button and select "function call"
    2. Under "function", search and select the function "if"
    3. Set up "condition" to check the validity of the second argument by following the same process as described in step 5.
    4. Set up "then" to throw an error for the second argument by following the same process as described in step 6.
  8. Finally, set up the success path:
    1. Next to the nested "else", click on the "…" button and select "function call"
    2. Under "function", search and select the successful function, which in this case can be "join strings with separator"
    3. Next to "first string", click on the "…" button and set it to "argument reference", then select "first string"
    4. Next to "second string", click on the "…" button and set it to "argument reference", then select "second string"
    5. Under "separator" add a space into the text field

The resulting function call should look like this:

Wikifunctions composition using Throw Error function
Wikifunctions composition using Throw Error function
Wikifunctions composition using Throw Error function, in its collapsed view
Wikifunctions composition using Throw Error function, in its collapsed view


Handling errors in compositions

While it is useful to allow functions to raise expected errors, it is crucial to be able to handle errors in your compositions in order to safely use these functions.

Say you want to create a new function "full name of a person" – we will refer to it with the ZID Z30010. This function should return the full name of a person given a first and last names, but if any of the two fields are empty, it should return the text "Unknown" instead.

You could create a composition using our example function "Join two strings with a space" or Z30000 to generate the name. If any of the inputs are empty, you know that Z30000 will throw an error of type Z30005. Using the function Try-Catch Function (Z850) as a wrapper, you can attempt the join operation and intercept a possible error to simply return "Unknown" whenever it occurs.

Try-Catch function (Z850)

The Try-Catch Function (Z850) function is built-in with ZID Z850 and takes three arguments:

  • Function call – The initial function call to run. If nothing goes wrong, the response will be the result of this function call.
  • Error type – An error type to catch in case it is thrown during the execution of the first function call.
  • Error handler – A function call that will be run in case the error specified above is caught during the execution of the initial function call. If that happens, the result of the error handler will be the value returned.

Using Try-Catch

Once we have all the functions for our composition, we can craft it like this:

try_catch(
    join_with_space( Z30010K1, Z30010K2 ),
    Z30005,
    echo( "Unknown" )
)

This means that:

  • The "Join two strings with a space" function will be tried with the two inputs and, if everything goes well, the output of this function will be returned
  • If any of the inputs are blank, "Join two strings with a space" with raise an error of type Z30005
  • Our Try-Catch Function (Z850) will then detect this error and run the Echo (Z801) function to return "Unknown"

Let's create this step by step:

  1. Start adding an implementation by clicking on the "+" button in the "Implementations" table from your Function page.
  2. Make sure that the option "composition" under "Implementation" is selected.
  3. Click on the "›” icon or click the "Select Function" link to expand the function details.
  4. Under "function" search and select the outermost function you want to use, which in this case is "Try-Catch".
  5. Set the main function call:
    1. Under the field where the "Try-Catch" function is selected, next to the "function call" key, click on the "…" button and select "function call"
    2. Under "function" search and select the function "join two strings with space"
    3. Next to "first string", click on the "…" button and select "argument reference", then select the "first name" argument from the dropdown.
    4. Next to "second string", click on the "…" button and select "argument reference", then select the "second name" argument from the dropdown.
  6. Select the error to catch:
    1. Under "error type", search and select "bad string input" – you can search directly by its ZID Z30005
  7. Set the error handler function call:
    1. Next to "error handler", click on the "…" button and select "function call"
    2. Under "function", search and select the function "echo"
    3. Under "input" and "type", search and select the type "String"
    4. Now, under "input", type "Unknown" in the text field

The resulting function should look like this:

Wikifunctions composition using Try-catch function, in expanded mode
Wikifunctions composition using Try-catch function, in expanded mode
Wikifunctions composition using Try-catch function, in collapsed mode
Wikifunctions composition using Try-catch function, in collapsed mode


Testing your failure use cases

As we introduced in the above section about practicing TDD, it's important to test not only the most common paths, but also the possible edge cases. It is equally important to test failure behavior. In this section we will give you the tools necessary to test that your function fails with a given set of inputs, and that the error thrown is the expected one.

To do that, you will need other two system functions:

Is error type (Z852)

The Is error type (Z852) function is built-in with ZID Z852 and takes two arguments:

  • Error – An Error (Z5) object, which can be thrown by a failing call. An error instance has a type and a value.
  • Error type – A reference to an error type (Z50) which will be compared to the type of the Error passed as a first argument. If that's the case, the function will return true.

Get error thrown by function call (Z853)

The Get error thrown by function call (Z853) function is built-in with ZID Z853 and takes one argument:

  • Function call – A function call which can either return a successful value of any type, or throw an error.

This function returns a Typed pair (Z882) with a Boolean (Z40) in first place and an Object (Z1) in the second:

  • If the Function call runs successfully and hence does not throw any error, the returned pair will contain False in the first place, and void in the second.
  • If the Function call throws an error, the returned pair will contain True in the first place, and the caught error in the second.

To use this function in your compositions, you might need to use the additional functions:

Testing errors

Say our target function, "Join two strings with a space" or Z30000, throws an error of type Z30005 and we want to test this behavior. To do that we need to:

Tests have two fields:

  • Call – A function call that will produce an output with the inputs you want to test. This call will return a value.
  • Result validation – A function call that will validate that the value returned by the call above is the expected one. The first argument of this function call will be automatically assigned to take the return value of the first call.

For step-by-step instructions on how to create tests, see Wikifunctions:Introduction#Create_tests.

In our example, we can structure it like this — but this is not the only way!

The call, which will return an Error (Z5) object:

error = get_second_element_of_typed_list(
    get_error_thrown_by(
        join_strings_by_whitespace( "", "not empty" )
    )
)

And the result validation, which will take the Error (Z5) object as its first argument:

is_error_type( error, Z30005 )

Let's do this step by step:

  1. Start adding a test by clicking on the "+" button in the "Tests" table from your Function page. The "call" and "result validation" fields already have some functions pre-set, but to test the errors we will need to change them.
  2. First, let's set the call:
    1. Under "call", click the "›" chevron button to expand the function call.
    2. Under "function", edit the field and search and select the function "Get second element of a typed pair"
    3. Once selected, next to "Typed pair", click on the "…" button and select "function call"
    4. Under "function", search and select the function "Get error thrown by function call"
    5. Next to "function call", click on the "…" button and select "function call"
    6. Under "function", search and select the function "Join two strings with a space" (or by ZID Z30000)
    7. Now set the inputs so that they cause a failure: at least one of them should be empty.
    8. You can now collapse your "call" by clicking again on the down-chevron button if you want!
  3. Next, let's set the result validation:
    1. Under "result validation", click the "›" chevron button to expand the function call.
    2. Under "function", edit the field and search and select the function "Is error type"
    3. Now you will only be asked to configure the second argument. Under "error type", search and select your Z30005 error type.
  4. And you are done! You can now click the circular arrow in the "Implementations" box and you should see green checks.

If everything went right, you can now save your test. It should look like this:

Testing a failure using Get error and Is error type functions, in expanded mode
Testing a failure using Get error and Is error type functions, in expanded mode
Testing a failure using Get error and Is error type functions, in collapsed mode
Testing a failure using Get error and Is error type functions, in collapsed mode

Debugging your implementations

Sometimes things can go wrong. A function might behave differently than expected or a value might not look quite right. Sometimes implementations are really complex and, because they are run in a sandboxed environment, it can seem really difficult to debug. To help understand what's going on inside a function call, Wikifunctions includes a small set of debugging tools.

These tools don't change the result of a function, but will add extra information to the returned metadata, which you can observe after running the function. You can use Wikifunctions.Debug() from your code implementations or the function Add debug log to function call (Z854) from your compositions.

In this section you will learn:

  • How to add debug messages from both code and composition implementations,
  • how to inspect the metadata manually or via the UI to inspect these logs.

Debugging your code implementations

When writing code, both from JavaScript and Python3, you can use Wikifunctions.Debug() to add a message to your debugging log and continue the execution.

Wikifunctions.Debug() has one parameter, which is generally expected to be a string. However, any other object passed as parameter will get directly converted into a string.

If we want to add debug logs to our code implementations for our "Join two strings with a space" example function, we can do, in JavaScript:

function Z30000( Z30000K1, Z30000K2 ) {
  if ( trim( Z30000K1 ) === '' ) {
    Wikifunctions.Debug( `first input is empty "${ Z30000K1 }"` );
    Wikifunctions.Error( 'Z30005', [ "Z30000K1", Z30000K1 ] )
  }
  if ( trim( Z30000K2 ) === '' ) {
    Wikifunctions.Debug( `second input is empty "${ Z30000K2 }"` );
    Wikifunctions.Error( 'Z30005', [ "Z30000K2", Z30000K2 ] )
  }

  Wikifunctions.Debug( "both inputs are fine" );
  return Z30000K1 + " " + Z30000K2;
}

And the same in your Python3 implementation will be:

def Z30000(Z30000K1, Z30000K2):
    if Z30000K1.strip() == "":
        Wikifunctions.Debug( 'first input is empty "'+Z30000K1+'"' )
        Wikifunctions.Error("Z30005", ["Z30000K1", Z30000K1])

    if Z30000K2.strip() == "":
        Wikifunctions.Debug( 'second input is empty "'+Z30000K2+'"' )
        Wikifunctions.Error("Z30005", ["Z30000K2", Z30000K2])

    Wikifunctions.Debug( 'both inputs are fine' )
    return Z30000K1 + " " + Z30000K2

This way, no matter what happens, we will be adding one message to your debugging logs:

  • If the function call fails when checking on the first or second input, the result of the execution will be a failure, but it will contain one informative log about what branch caused the failure.
  • If the function call succeeds—because both inputs are valid—the function call will return the joined string, but the returned metadata will also contain a debug log.

Debugging your compositions

To support debugging compositions, Wikifunctions provides the built-in function Add debug log to function call (Z854). This function allows you to attach debug messages to the execution of any function call that generates the final output. The debug logs collected during the evaluation are returned as part of the execution metadata, under the executorDebugLogs key.

Add debug log to function call (Z854)

The Add debug log to function call (Z854) function is built-in with ZID Z854 and takes two arguments:

  • Function call – Any composition you want to execute.
  • Debug log – A string message that will be recorded if the given function call is evaluated.

When the wrapped function call runs, the provided message is added to the execution logs. If that function call is not executed, or its execution is not part of the final output, its debug log will not appear in the final result metadata.

You will benefit from this built-in if you want to observe:

  • Which branch of a conditional was executed, or
  • the order of evaluation in a composition.

Using Add debug log to function call

Say that you want to add debug logs to our example function "Join two strings with a space" or Z30000, so that different messages are added to the different possible outcomes. You could use our debugging builtin like this:

if (
    is_empty_string( Z30000K1 ),
    add_debug_log( throw_error( Z30005, ... ), 'failed on first input' )
    if(
        is_empty_string( Z30000K2 ),
        add_debug_log( throw_error( Z30005, ... ), 'failed on second input' ),
        add_debug_log(
            join_with_separator( Z30000K1, Z30000K2, " " ),
            'both inputs are ok!'
        )
    )
)

This way,

  • when called with an empty first input, the function will return a failed response, and the metadata will contain an executorDebugLogs entry with the "failed on first input" log.
  • When called with an empty second input, the call will also fail, but the logged message will be "failed on second input"
  • When called with valid inputs, the function will return a successful response, and additionally the metadata will contain "both inputs are ok" in its executorDebugLogs key.

This is the way this composition would look:

Composition implementation using Add debug logs to log all possible execution paths
Composition implementation using Add debug logs to log all possible execution paths


Accessing execution debug logs

Whether your execution logs are added via Wikifunctions.Debug() or via the Add debug log to function call (Z854) built-in function, they are returned as part of the response metadata. To see the logs, once you run your function, click on the "Details" button that appears at the bottom of the "Result" section.

The error and debug information will appear at the top of the "Details" dialog.

This is what you would see when running the composition from our previous example with valid and invalid inputs:

Function metadata dialog for a successful execution with debug logs
Function metadata dialog for a failed execution with debug logs
Function metadata dialog for a successful execution: shows no errors, but displays the successful execution log: "both inputs are ok!" Function metadata dialog for a failed execution: shows the error information, and the additional execution log for the first failure branch: "failed on first input"