Skip to main content

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.

markdown-block

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
info

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.

info

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.

content-table

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

content-table-example

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

parameters

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.

Creating an automation for a stack

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, only ask(""" 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.

FormatSyntaxExample
Italicsplace an underscore () on both sides of the text_your text_
Boldplace a star (*) on both sides of the text*your text*
Strikethroughplace a tilde (~) on both sides of the text~your text~
Monospaceplace three backticks (```) on both sides of the text```your text```