Stacks overview
Stacks are a new feature for building impact services in Turn. Stacks are heavily inspired by the original ideas in Hypercard and Hypertalk.
In Turn, stacks are containers that hold one or more cards and the relationships between cards. Cards perform the various steps that make up your impact service using functions and expressions.
The available functions and expressions are described below with examples and common errors.
Stacks coding language
Impact services built using stacks are described using a custom coding language. We sometimes refer to this as the Stacks DSL, which stands for Stacks Domain Specific Language.
Let's look at the code language structure and syntax.
Stack code block
Think of a stack as a container that holds multiple cards.
Example
The following example creates an empty stack for illustrative purposes.
stack StackName do
...
end
Key things to note
- Your stack must have a name.
- The
do
command initiates the stack. - The
end
command concludes the stack. - All logic that the stack should execute will be added between the do and the end commands.
You can omit the stack do ... end
which will have the system implicitly assume a single stack describes all the cards defined.
Card code block
Think of a card as performing one or more steps on your impact service. Cards make use of various functions and expressions to describe these steps.
Example
The following example creates an empty card for illustrative purposes.
card CardName do
...
end
Key things to note
- The code structure to create a card is similar to the code structure to create a stack.
- Your card code block will always be defined within a stack code block.
- Your card must have a name.
- The
do
command initiates the card. - The
end
command concludes the card. - All logic of the card will be added between the do and the end commands.
- Logic will comprise functions, expressions, etc. See the examples below.
Using comments
Any line of text in your stack that is prefixed with a # sign will become a comment. This means it will not be an executable line of code but will be ignored when the stack runs.
card MyCard do
# This is a comment
end
Documenting your Stacks
Code comments are great to document individual lines within a Card but sometimes these can be too limiting. When one wants to describe larger overarching concerns like providing more detail behind specific design decisions, linking to external documentation or research, or to describe key implementation details, Markdown documentation blocks are what you'd want to use.
Markdown is a simple text based format used to provide extra markup to plain text. There's a great Markdown tutorial available at https://www.markdowntutorial.com that covers the details and there's also the Markdown Cheat Sheet if you're looking for a quick reference guide.
The idea behind combining cards, to describe the logic, and Markdown, to describe the thought process and background information, is that these stacks will become self documenting over time. In fact, your whole impact service is itself described as one large Markdown text file.
Between your cards you can add as many Markdown blocks as needed to describe the logic of your impact service.
Your first stack
Now that we know a stack contains one or more cards, and each card contains logic that describe your impact service, let's put it together. The example shows you how stack blocks and card blocks fit together to create a very simple stack.
Example
The following example creates a simple stack that sends a single text message to a user, saying "Hello World!". Note how the card block sits within the stack block.
stack MyStack do
card MyCard do
text("Hello world!")
end
end
Relating cards
Cards describe the steps of your impact service. Each card describes a specific point in a series of interactions.
The first card is always what the stack starts with. There after, the order in which cards appear in your stack code is not important. What is important is to describe the relationships between cards, as that determines how users will navigate between them.
There are two main ways to describe what happens after each card, i.e. the then
or the when
condition.
The then
condition
The then
relationship is used to connect one card with another card.
Syntax
card FirstCard, then: NextCard do
...
end
Example
This example shows how to use the then:
keyword to indicate when a card should immediately be followed by another card. In this case two text messages will be sent immediately after each other, first "this is the first card"
and then "this is the second card"
.
card One, then: Two do
text("this is the first card")
end
card Two do
text("this is the second card")
end
Key things to note
- Without
then:
only the first text message will be sent.
The when
condition
The when clause is used to add conditions that control the execution order of a card.
Syntax
card CardName when some_condition do
...
end
Note that with the then
syntax we used a comma and a colon (CardName, then:
).
However when
should occur immediately after the cardName
without a comma or colon.
Example
This following example elaborates a bit on the previous example by introducing conditions between cards.
You'll notice that card Two
proceeds to card Three
with the then:
keyword. One important difference here is that we're using the ask()
function which waits for user input before proceeding.
Card Three
is defined multiple times, two of which have conditions that guard the card. Depending on the user's response the system will automatically select the card Three
for which the condition resolves to true
. The last card Three
has no condition which means it will always resolve to true
and functions as a default fallback.
card One, then: Two do
text("this is the first card")
end
card Two, then: Three do
text("this is the second card")
age = ask("What is your age?")
end
card Three when age > 18 do
text("Hello boomer")
end
card Three when age > 16 and age <= 18 do
text("Hey there!")
end
card Three do
text("This service is too cool for you")
end
Variables
As in many programming languages, variables can be used to store values. All variables declared in a stack are available to all the cards in the stack, and not just in the card where the variable was created.
The values assigned to variables are only available for the duration of the stack and will get lost once the user finishes interacting with the stack. if you need to save data for later retrieval (for example to retrieve it from some other stacks in the future), please read about contact profiles.
card CardOne, then CardTwo do
first = 2
end
card CardTwo, then CardThree do
second = 3
result = first + second
end
card CardThree do
# This sends the message "The result is: 5"
text("The result is: @result")
end
Content Tables
A common use-case when writing Stacks is to store some content in a way that makes it easy to interact with from a card.
An example is a 10 question survey. The logic described in the cards doesn't need to change if the content was updated to have fewer or more questions. We simply just want to go through all available questions one by one until all have been answered.
Content Tables are great for these kinds of use cases. They have a unique name and function much like a spreadsheet, allowing one to specify columns with names and rows for each.
From within a card, the content table's rows can be read one by one using Expressions.
Tables have a list attribute called rows
which gives access to all the rows in the table. Each individual row's values can be addressed using the column's name in lowercase.
card TextCard do
row = table_0.rows[0]
text("""
The value of the first rows is: @row.column_1, @row.column_2, and @row.column_3
""")
end
Parameters
Often, as one writes stacks, there will be common bits of text and values that are re-used between cards. Think of things like the welcome text on a button that takes one back to the main menu for example.
It's often desirable to present these common elements in an easily editable interface outside of code objects in a Stack.
Parameters are great for this as they allow one to assign a name to a commonly reused element and provide a default value for it. When writing cards, one can then reference to these values by name using Expressions
Parameters are similar to Content Tables except that they are optimised for reading individual values from rather than combined rows.
The values in parameters are stored in an attribute called items
from where the individual entries can be read by name in lowercase.
card TextCard do
text("""
@parameters_0.items.welcome_text
@parameters_0.items.intro
""")
end
Activating a stack
Once you have a stack ready and tested on the phone preview, you want to test it on your phone and/or make it live for users. To do that you need to set up an automation on Turn, the trigger can be whatever you need and the action should be Start a stack
. In the dropdown select the stack of interest.
Use multi-line text
Multi-line text is wrapped by triple quotes ("""
) and allows you to easily represent text spanning over multiple lines. It also allows you to use quotes ("
) inside your message without having to escape them.
Common errors
- All the lines including the triple quotes, must be indented at the same level, per the example below.
- Don't put any text in the same line as the triple quotes. For example, don't do
ask("""Welcome
, onlyask("""
and the text on the next line.
card QuestionCard do
ask("""
Welcome!
Is "Jane" your name?
""")
end
Formatting text
WhatsApp allows you to format your text into bold, italics, strike-through and monospace.
Format | Syntax | Example |
---|---|---|
Italics | place an underscore () on both sides of the text | _your text_ |
Bold | place a star (*) on both sides of the text | *your text* |
Strikethrough | place a tilde (~) on both sides of the text | ~your text~ |
Monospace | place three backticks (```) on both sides of the text | ```your text``` |