An educational tool for the semantics of first-order logic
tarski.mp4
Attempting to recreate Barwise and Etchemendy's Tarski's world in Doodle's Reactor using Scala 3. (I might switch to ScalaFX later.)
They use 3D objects (cube, tetrahedron, dodecahedron) but I'm going with 2D as in Epp's book. The image above is taken from there.
Thanks a lot to Noel Welsh for his awesome Doodle library and all the help on Discord.
Thanks to Jon Barwise (1942-2000) and John Etchemendy for their awesome idea and book on Tarski's world.
See my adventures in bad design on my Github Pages
This is a Scala-cli project.
With Scala 3.5.0 and above, you can simply run scala compile . and scala test ..
// main
// |
// view testing
// | /
// controller
// |
// model
// |
// constantsYou can read more about each module at:
Current version is 0.1.7 (Dec 09, 2025). Released for Scala 3 only.
You will need a JVM, and Scala 3. This should give you everything you need.
Also you'll need an IDE:
- Metals extension on Visual Studio Code
- IntelliJ with Scala plugin
For Scala-cli (or just plain scala), add to your project.scala (or any file):
//> using dep io.github.spamegg1::tarski:0.1.7For SBT, add to your build.sbt:
libraryDependencies += "io.github.spamegg1" %% "tarski" % "0.1.7"Can be found at Javadoc
To get a quick look and feel, you can execute tarski.main.Example.runExample.
Tarski's world is intended to be used interactively inside an IDE such as IntelliJ or Visual Studio Code.
Generally, in an educational setting, a world and a list of formulas are given to you. Then you run the program to evaluate the formulas, move or change the blocks, add or change the formulas if necessary, based on what you are asked to do in exercises. Of course, you can write your own worlds and formulas too.
Then run it with tarski.main.runWorld to start interacting.
You will see the interactive window like the one above in the video.
Here are the details:
//> using dep io.github.spamegg1::tarski:0.1.7
import tarski.main.*, Shape.*, Sizes.*, Tone.*
val grid: Grid = Map(
(1, 2) -> Block(Sml, Tri, Lim, "a"),
(4, 3) -> Block(Mid, Tri, Blu),
(5, 6) -> Block(Big, Cir, Red, "d"),
(6, 3) -> Block(Sml, Sqr, Blu)
)
val formulas = Seq(
fof"¬(∃x Big(x))",
fof"∀x Sqr(x)",
fof"∀x ¬ Cir(x)",
fof"¬(∀x Sml(x))",
fof"∃x Tri(x)",
fof"∀x (¬(Shap(c, x) ∨ Less(x, c)) → ¬Tone(x, c))",
fof"∃x Cir(x)",
fof"a = b",
fof"∀x ∃y More(x, y)",
fof"c != d",
fof"∀x (Squ(x) → Tri(x))",
fof"∃x (Tri(x) ↔ Mid(x))",
fof"¬(∃x (Cir(x) ∧ Sml(x)))",
)
// The interface is 1600x800 by default.
// if the interface is too small or too large, try a different scale factor than 1.0:
@main
def run = runWorld(grid, formulas, 1.0)You can add or remove blocks interactively. Unfortunately editing the formulas inside the interface is not supported yet. To edit the formulas, close the window, edit them in your IDE, then restart.
All you need is to import tarski.main.*.
Optionally you can also import Shape.*, Sizes.*, Tone.*
to avoid repeatedly writing Shape., Sizes. or Tone..
Blocks have 3 attributes, each of which has 3 possible values:
| Attribute | value1 | value2 | value3 |
|---|---|---|---|
| Tone | Blu | Lim | Red |
| Shape | Tri | Sqr | Cir |
| Sizes | Sml | Mid | Big |
Blocks can also have an optional name, only one of: a, b, c, d, e, f.
Other names are not allowed. Formulas can then refer to these names as constants.
Then you can write a Grid, a map of positions Pos to Blocks, to define the board.
It's an 8x8 standard chess board; coordinates are 0-indexed.
See above for details and an example.
Then you can write a list of first-order logic formulas, FOLFormula
(courtesy of Gapt).
The formulas use a special string interpolator fof"...",
and can use the Unicode symbols or their ASCII equivalents for logical connectives:
| Connective | ASCII | Unicode |
|---|---|---|
| and | & |
∧ |
| or | | |
∨ |
| not | - |
¬ |
| implies | -> |
→ |
| biconditional | <-> |
↔ |
| forall | ! |
∀ |
| exists | ? |
∃ |
NOTE: Many of these predicate names are shortened from their normal spellings
(like Small -> Sml, Right -> Rgt) in order to fit longer formulas on the screen.
Please study them carefully. Apologies for any confusion!
(Hopefully I will fix this limitation in the future.)
The following predicates are supported:
| Syntax | Semantics |
|---|---|
Tri(x) |
"x is a triangle" |
Sqr(x) |
"x is a square" |
Cir(x) |
"x is a circle" |
Blu(x) |
"x has color blue" |
Lim(x) |
"x has color lime" |
Red(x) |
"x has color red" |
Sml(x) |
"x has small size" |
Mid(x) |
"x has medium size" |
Big(x) |
"x has big size" |
| Syntax | Semantics |
|---|---|
Left(x, y) |
"x is to the left of y" |
Rgt(x, y) |
"x is to the right of y" |
Bel(x, y) |
"x is below y" |
Abv(x, y) |
"x is above y" |
Adj(x, y) |
"x is adjacent (but not diagonally) to y" |
Less(x, y) |
"x is smaller in size than y" |
More(x, y) |
"x is larger in size than y" |
Row(x, y) |
"x is on the same row as y" |
Col(x, y) |
"x is on the same column as y" |
Size(x, y) |
"x has the same size as y" |
Shap(x, y) |
"x has the same shape as y" |
Tone(x, y) |
"x has the same tone as y" |
x = y |
"x is equal to y (in size, shape and tone)" |
| Syntax | Semantics |
|---|---|
Btw(x, y, z) |
"x is between y and z (vertically, horizontally or diagonally)" |
You can work through the examples in the companion repository
Stay tuned!
