Skip to content

Coding with Shards

In this chapter, we will be learning how to code with Shards so that you can write your very own program!

The shard

A shard in its most basic code form consists of its name surrounded by parentheses.

Some shard examples.

The above example consists of 3 different predefined shards.

Shards are named to make their purpose rather intuitive. (Msg) is the Message shard that prints a message to the console, while Math.Add is a Mathematics shard that adds numbers together.

Note

It is a good practice to name your code based on its purpose. This allows others to easily understand what your code achieves without getting too technical.

For example, the code (Msg "Hello World!") can be easily understood to be sending the message "Hello World" to the console. You do not need to delve into how (Msg) was coded to understand what it can do.

A shard can take in an input, process that input, and produce an output. Shards also have parameters that behave as user-defined settings.

For example, Math.Add has the parameter Operand which is defined by the user. It determines the value that will be added to the input. The final result is then produced as the output.

The parameters set for a shard affects it's behavior.

In code form, parameters are defined by the user within the parentheses of the shard itself, after the shard's name. The above examples will appear as 5 (Math.Add 1) and 5 (Math.Add 3) in code.

Some shards have multiple parameters. When specifying values for multiple parameters, you will have to prepend your values with the parameter they are for if some parameters are skipped.

Let us take a look at the Repeat shard which has four parameters: Action, Times, Forever, Until.

We can utilize the Repeat shard with its different parameters as shown:

1
2
(Repeat
    (-> (Msg "Hello World!"))) ;; (1)(2)
  1. When no parameters are specified, parameters are treated as implicit and are resolved in order. In this case, Action is the implicit parameter for Repeat and we set (Msg "Hello World") to it.
  2. Since the other parameters are not defined, they will assume their default values. In this case, the Repeat shard will not run at all as Times has a default value of 0.
1
2
3
(Repeat 
    :Action (-> (Msg "Hello World!")) ;; (1)
    :Times 2) ;; (2)
  1. The parameters are explicitly declared for clarity.
  2. Repeats the Action twice.
1
2
3
(Repeat ;; (1)
    (-> (Msg "Hello World!"))
    2)
  1. Both parameters can be implicit since they are resolved in order. In this case, Action is the first implicit parameter, and Times is the second implicit parameter.
1
2
3
(Repeat 
    (-> (Msg "Hello World!")) ;; (1)
    :Times 2)
  1. You can still implicitly declare the first parameter, while fully declaring the other parameters. Note that it does not work vice versa. You cannot implicitly declare parameters if a parameter before it has been explicitly declared.
1
2
3
(Repeat 
    :Action (-> (Msg "Hello World!")) 
    2) ;; (1)
  1. This will not work as you cannot implicitly declare the second parameter if the first has been fully declared.
1
2
3
4
5
(Repeat
   :Action (-> (Msg "Hello World!"))
   :Forever true ;; (1)
   :Until ( ;; some condition )
   ) ;; (2)
  1. The Times parameter is skipped and Forever is declared instead. Since we are skipping a parameter, we must fully declare the parameters that come after it.
  2. Until takes a shard that returns true or false. Repeat will loop forever until the shard specified in Until evaluates to true.

->

When using shards for a parameter (e.g., Action), you must always place -> before the first shard.

-> is a shard container used to group multiple shards together. We will see how to eliminate the use of -> later in the segment for defshards.

To find out more about the input/output/parameter of a shard, you can search for the shard in the search bar above and check out its documentation page.

Give it a try!

Type "Msg" in the search bar above and select the first result that appears. It should lead you to the page for Msg here.

From there, you can learn more about:

  • The purpose of the shard

  • Its parameters

  • The input type it can receive

  • The output it will produce

  • How to utilize the shard by looking at the examples given

Data Types

A shard can take in data, process it, and output the results.

A shard takes in an input and produces an output.

There are many different types of data in the Shards language, and each shard will have specific data types that it can work with.

For example, the Math.Add shard can only work with numeric data types, while the Log shard that prints information to the console can work with Any data type.

Here are some of the data types found in Shards:

Data Type Description Example
Any Any data type.
None No data type.
Bool Evaluates to either true or false. true, false
Float A numerical value with a decimal point. 2.53, -9.124
Int A numerical value with no decimals. Read as "integer". 2, -9
Sequence A collection of values. [2.5, -9.1, 9.7]
String Characters enclosed by double quotes. "A string!"
Wire A sequence of shards.

Note

A shard can have multiple data types as its input and output. For example, the shard Math.Add can have its input and output as an Int, or it can have its input and output as a Float.

For the full list of data types and more in-depth reading, check out the Types documentation page here.

Variables

To better work with data across your code, we can assign them to data containers known as variables.

Imagine a scenario where you have a float 3.141592653589793 that you need to reuse in code multiple times. Instead of typing out the entire float each time, you could assign it to a variable called .pi-value and simply use that variable whenever it is needed.

1
3.141592653589793 (Math.Add 3.141592653589793) (Math.Multiply 3.141592653589793) (Math.Subtract 3.141592653589793)
1
2
3.141592653589793 = .pi-value ;; (1)
.pi-value (Math.Add .pi-value) (Math.Multiply .pi-value) (Math.Subtract .pi-value)
  1. 3.141592653589793 is assigned to the variable .pi-value. We'll learn more about assigning variables in a bit!

Variable names always start with a . period.

Some example of variable names:

  • .x

  • .number-of-apples

  • .is-verified

How you assign data to variables depends on the variable type. The main differences between variables are as follows:

  • Constant vs Mutable

    • Constant: The variable's value cannot be changed once defined.

    • Mutable: The variable's value can be changed.

  • Local vs Global

    • Local: The variable is only known within the Wire it originated from.

    • Global: The variable is known throughout the entire Mesh.

Local vs Global

We will learn more about this later in the section about Scope in Shards.

Here are the variable types and the symbols used to create and assign to them:

Variable Type Shard Alias Description
Local, Constant Ref = Creates a local constant variable.
Local, Mutable Set >= Creates a local mutable variable.
Global, Mutable Set >== Creates a global mutable variable.
Mutable Update > Updates a mutable variable.

In summary:

  • Use = to create constant variables.

  • Otherwise, use >= to create local variables, or >== to make them global.

  • Use > to update variable values.

When defining variables in your program, you can use Setup to ensure that variables defined within it will only ever be defined once within a program.

1
2
3
(Setup
 10 >= .timer
 100 >= max-points) ;; (1)
  1. Code within a Setup will only be run once. As such, you can prevent variables defined in a loop from being reset each time.

Setup is an alias of the shard Once, with its Every parameter set to 1 to ensure that code defined in its Action parameter will only be run once.

Grouping shards

defshards allows you to group multiple shards to form a new shard, thereby eliminating the use of ->. It is useful for organizing your code and improving readability.

defshards has a syntax as such:

1
2
3
(defshards shard-name []
    ;; your shards here
)

The square brackets [] are where you can define parameters. For example:

1
2
3
(defshards send-message [message]
    (Msg "Message Incoming...")
    (Msg message))

When used in code:

1
(send-message "Hello World!")
1
2
Message Incoming...
Hello World!

Let us now take a look at how we can utilize defshards in a code snippet that counts from 1 to 5 multiple times.

1
2
3
4
5
6
7
8
(Repeat
 :Action
 (-> (Msg "1")
     (Msg "2")
     (Msg "3")
     (Msg "4")
     (Msg "5"))
 :Times 5)

We can replace the use of -> above with defshards to make the count from 1 to 5 code reusable and factor it out under a new shard called msg-one-to-five.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(defshards msg-one-to-five []
  (Msg "1")
  (Msg "2")
  (Msg "3")
  (Msg "4")
  (Msg "5"))

(Repeat
 :Action (msg-one-to-five)
 :Times 5)

Note

The parameter will still require a -> if it contains multiple shards.

1
2
3
4
5
(Repeat
 :Action
 (-> (msg-one-to-five)
     (Msg "6"))
 :Times 5)

The Wire

A Wire is made up of a sequence of shards, queued for execution from left to right, top to bottom.

A Wire is made up of a sequence of shards.

To create a Wire, we use defwire.

1
2
3
(defwire wire-name 
  ;; shards here
)

Note

The syntax for defwire is different from defshards as you cannot define parameters. Square brackets [] are not used. Instead, defwire inherits variables from the parent wire unless the variables are pure.

Note

Unlike defshards which group shards up for organization, defwire groups shards up to fulfill a purpose. As Wires are created with a purpose in mind, they should be appropriately named to reflect it.

A Wire's lifetime ends once the final shard within it has been executed. To keep a Wire alive even after it has reached its end, we can set it to be loopable. This is called a Looped Wire.

A Looped Wire is kept alive even after the final shard is executed.

A Looped Wire will continue running until its exit conditions have been met.

Note

You will learn more about the entering and exiting of Looped Wires in the next chapter!

To create a Looped Wire, we use defloop.

1
2
3
(defloop loop-name 
  ;; shards here
)

The Mesh

Wires are queued for execution within a Mesh, from left to right, top to bottom.

Wires are queued for execution within a Mesh.

To queue a Wire on a Mesh, we use schedule.

1
(schedule mesh-name wire-name)

Note

We will learn more about controlling the flow of Shards with Wires and Meshes in the following chapter.

Running Shards

To get Shards running, a specific hierarchy and sequence must be followed. Your shards are first queued into Wires, which are then queued onto a Mesh.

The hierarchy of a Shards program.

When the Mesh is run, the Wires are executed in sequence and your program is started. This is done using the aptly named command run.

1
(run mesh-name)

run can take in two optional values:

  • The interval between each iteration of the Mesh.

  • The maximum number of iterations, which is typically used for debugging purposes.

Note

If your program has animations, we recommend that you set the first value to (/ 1.0 60.0) which emulates 60 frames per second (60 FPS).

1
(run mesh-name (/ 1.0 60.0))

Let us now take a look at what a basic Shards program will look like!

Writing a sample program

Do you recall the hungry-cat loop from the previous chapter? Let us try to implement a simpler modified version of it using the concepts learned in this chapter.

The modified overview of the hungry-cat loop.

In this example, the "cat" starts off with 0 hunger. At the end of each loop, we increase the hunger by 1. Once the value of hunger is greater than 0, the cat starts to make cat noises.

defshards and defwire

Let us first define the make-cat-noises Wire.

1
2
3
4
5
(defwire make-cat-noises
  (Msg "Meow") (Msg "Meow") (Msg "Meow")
  (Msg "Mew") (Msg "Mew") (Msg "Mew")
  (Msg "Meow") (Msg "Meow") (Msg "Meow")
  (Msg "Mew") (Msg "Mew") (Msg "Mew"))

We can employ the Repeat shard we saw earlier to make our code more efficient.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defwire make-cat-noises
  (Repeat
   :Action (-> (Msg "Meow"))
   :Times 3)
  (Repeat
   :Action (-> (Msg "Mew"))
   :Times 3)
  (Repeat
   :Action (-> (Msg "Meow"))
   :Times 3)
  (Repeat
   :Action (-> (Msg "Mew"))
   :Times 3))

Going a step further, we can better organize our code by creating new shards with defshards. Look at how much neater it is now!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(defshards meows []
  (Repeat
   :Action (-> (Msg "Meow"))
   :Times 3))

(defshards mews []
  (Repeat
   :Action (-> (Msg "Mew"))
   :Times 3))

(defwire make-cat-noises
  (meows) (mews) (meows) (mews))

The Loop

With the make-cat-noises Wire done, let us now look at creating the full hungry-cat program loop.

1
(defloop hungry-cat)

We want to first create a variable to track the cat's hunger level. Create the .hunger variable and assign the value of 0 to it. Remember to create the variable within Setup to prevent it from being reassigned at each iteration of the loop.

1
2
3
(defloop hungry-cat
  (Setup
   0 >= .hunger)) ;; (1)
  1. Code within a Setup will only be run once in a program.

Next, use the Math.Inc shard to increase the value of .hunger every time the Wire loops.

1
2
3
4
(defloop hungry-cat
  (Setup
   0 >= .hunger)
  (Math.Inc .hunger))

Conditionals

A conditional can be used to check if .hunger is greater than 0. When the cat's hunger level has risen above 0, we want the cat to start making cat noises. Some conditional shards that you can use are:

When allows you to specify what happens if a condition is met. The syntax reads as such: When a condition is met, Then a specified action happens.

If is similar to When, but it has an additional parameter Else that allows it to have a syntax that reads as such: If a condition is met, Then a specified action occurs, Else another action is executed instead.

For this example, using When would suffice as we only need make-cat-noises to run When hunger IsMore than 0.

1
2
3
4
5
6
7
(defloop hungry-cat
  (Setup
   0 >= .hunger)
  (When
   :Predicate (IsMore 0) ;; (1)
   :Action (-> (Detach make-cat-noises))) ;; (2)
  (Math.Inc .hunger))
  1. IsMore compares the input to its parameter and outputs true if the input has a greater value. In this case, it is comparing the value of .hunger to 0.
  2. Detach is used to schedule a Wire on the Mesh. You will learn more about using Detach in the following chapter!

Debugging

What if you wanted to check the value of .hunger in each loop iteration? We can employ a shard that is useful when you wish to debug your code - the Log shard.

Debugging

Debugging is the process of attempting to find the cause of an error or undesirable behavior in your program. When attempting to debug your code, functions or tools that allow you to check the value of variables at various points in your code can be useful in helping you narrow down where the errors could be originating from.

Log is useful as it can be placed at any point of your code to check the value passing through it. In this example, we will use Log to verify the value of .hunger before the conditional check with When occurs. Upon running the code, you will see that when the value of .hunger becomes 1, the cat starts to make noises.

Readying the Mesh

Before our program can run, do not forget to:

  • Define the Mesh.

  • schedule the Wire on the Mesh.

  • run the Mesh.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(defmesh main)

(defshards meows []
  (Repeat
   :Action (-> (Msg "Meow"))
   :Times 3))

(defshards mews []
  (Repeat
   :Action (-> (Msg "Mew"))
   :Times 3))

(defwire make-cat-noises
  (meows) (mews) (meows) (mews))

(defloop hungry-cat
  (Setup
   0 >= .hunger)
  .hunger (Log "Hunger Level")
  (When
   :Predicate (IsMore 0)
   :Action (-> (Detach make-cat-noises)))
  (Math.Inc .hunger))

(schedule main hungry-cat)
(run main 1 3) ;; (1)
  1. We set the Mesh to only run 3 iterations. This means that the hungry-cat loop will only occur 3 times.
[hungry-cat] Hunger Level: 0
[hungry-cat] Hunger Level: 1
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Mew
[make-cat-noises] Mew
[make-cat-noises] Mew
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Mew
[make-cat-noises] Mew
[make-cat-noises] Mew
[hungry-cat] Hunger Level: 2
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Mew
[make-cat-noises] Mew
[make-cat-noises] Mew
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Meow
[make-cat-noises] Mew
[make-cat-noises] Mew
[make-cat-noises] Mew

Congratulations! You have now learned the fundamentals of writing a Shards program.

We will next look at how you can manipulate the flow of Shards to give you better control of how your program utilizes different Wires.