In the Scryer Prolog discussions, Alex has shared a few ideas and considerations for possible improvements to the Prolog code, including the use of metaprogramming to automatically generate more general relations:
https://github.com/mthom/scryer-prolog/discussions/3221
I hope for an interesting followup article!
Another very interesting Prolog program by Alex is factgraph.pl:
https://github.com/alexpetros/factgraph.pl
It's a Prolog implementation of the IRS Fact Graph, an application of Law as Code.
I think this is explained in The Power of Prolog[1] that the answers coming from Prolog are not printing text to a terminal, they are valid Prolog terms(/data/code). That's why the result uses the same `;` for OR as code does. Answer (x ; y ; false) is "query can be answered by x or y or no other answer found". (This would let you do meta-programming, reasoning about the results and rewriting the results in a LISPy data-as-code way, if you were more advanced than I am).
Prolog systems do optimisations to jump to the correct answer without searching, if they can, (e.g. database style indexing on the facts and rules) and in those cases there is no code left to search after showing the first answer, no need to prompt the user "should I search for more answers in the remaining code?", and so no need for an output "false" to say "I finished searching and found no more solutions".
SELECT DISTINCT pokemon, special_attack
FROM pokemon as p
WHERE
p.special_attack > 120
AND EXISTS (
SELECT 1
FROM pokemon_moves as pm
WHERE p.pokemon_name = pm.pokemon_name AND move = 'freezedry'
)
AND EXISTS (
SELECT 1
FROM pokemon_types as pt
WHERE p.pokemon_name = pt.pokemon_name AND type = 'ice'
);
Hmm. I wonder if this SELECT DISTINCT pokemon, special_attack
FROM pokemon as p
NATURAL JOIN pokemon_moves as pm
NATURAL JOIN pokemon_types as pt
WHERE
p.special_attack > 120 AND
pm.move = 'freezedry' AND
pt.type = 'ice'
;
would work instead.Do you have an Odin tutorial that's as easy to digest?
Sol
What does this say about Forth? Not much except that it isn't for me.
Take Prolog. I know few things more insulting than having to code in
Prolog. Whereas Armstrong developed Erlang in Prolog and liked it much
better than reimplementing Erlang in C for speed. I can't imagine how
this could be, but this is how it was. People are different.
from Yossi Kreinin's "My history with Forth & stack machines" [0]. Some people write APL and enjoy it. Some can't bear Lisp even after 10 years of working with it.[0] https://yosefk.com/blog/my-history-with-forth-stack-machines...
https://github.com/mthom/scryer-prolog/discussions/3322
and also in the course of a discussion on various approaches to implement concurrency in Prolog:
Man, where was a post like this when I was struggling trying to learn Prolog, modelling something with knights and knaves...
Simple general-purpose opponents can be coded using just recursive backtracking search, while more advanced ones (supporting moves that need to destructively change state) can still be conveniently modelled by reifying facts and thereby enable backtracking over assert/retract-like Prolog DB modifications, as used in discrete combinatorial planners [1].
[1]: https://quantumprolog.sgml.net/container-planning-demo/part1...
I think. Doing this in my head, but you could verify it trivially with SQLite or any other RDBMS.
And because you don't control the engine (you're not supposed to think about it, after all), there's nothing you can do but rewrite the whole thing in a traditional programming language.
The same is true of SQL query planners. You can perform basic queries without understanding how your SQL engine of choice works under the hood, but if you want performance, you must understand how your DB works. SQL is just the interface.
This is different in kind from imperative programming languages (which are much closer in abstraction to the underlying machine architecture), but we rub along with SQL ok; why not Prolog?
However, most prolog books focus on rooting the declarative mindset because programmers are generally more familiar with imperative programming. But just as with SQL or lisp there are definitely good ways, bad ways and plain mistakes you can make when approaching a problem.
https://www.broadlearners.com/t/understanding-the-meaning-of...
https://www.merriam-webster.com/grammar/whole-nother
https://old.reddit.com/r/etymology/comments/13s19j0/wtf_happ...
In my case, I was using it as almost a blend of the two meanings, something mostly meaning “unimpressed”, with a touch of “and a bit perplexed why such effort is going into this”. Basically a shoulder shrug and “okay…?”
I now find myself nonplussed, wondering if I should be using the word at all given it seems to have two opposite meanings.
One example I often think about is from Ken Silverman: "sub eax, 128" → "add eax, -128". So equivalent ways to write the same program may have different performance characteristics also depending on the tools that are applied. How many people could tell without trying which way to write this example is preferable?
The same phenomenon will be encountered in all kinds of languages, where engine and compiler improvements make existing code faster or slower.
And yet Prolog can express all examples in the article. For these kinds of problems, giving up TC is mostly a feature. And if you need more expressiveness, there's a lot of practical Datalog-ish systems that can recover Turing completeness (Flix, Formulog, parts of Souffle), while still being saner than SWI Prolog and co. for this type of work, as you generally don't have to care about atom order or search order in the same way. They act so much more predictably.
In my opinion, a key difference between Prolog and other languages in that regard is one of degree, not kind: Compared to other languages, addressing performance problems in Prolog engines tends to have far greater effects on Prolog programs, because so much is implicit (i.e., left to the engine).
If the performance problem is not in the engine, but in the program itself, then we will face the same questions with Prolog as with other languages: How to formulate the program better, is there a better approach altogether?
For example, earlier today an interesting question regarding performance was posted in the Scryer discussions:
https://github.com/mthom/scryer-prolog/discussions/3341
The comparison in this case is between Gecode and Scryer on a seemingly simple but nontrivial combinatorial task. What is the problem here? Most likely the Scryer engine itself can be improved. And also very likely, there are better ways to model the task, and also better search strategies, and these tend to have far greater performance impact than the base language, and these questions remain also if we change the base language.
In my opinion, these questions regarding different kinds of formulations tend to be more frequently associated with Prolog than with other languages because Prolog is more frequently used for complex tasks where it is not a priori clear how to even approach the problem.
January 05, 2026
The project that inspired this post is a little silly—I am about to describe the mechanics of a children’s video game in great detail—but this particular problem is what finally made Prolog click for me, an epiphany I’ve been hunting for ever since reading Bruce Tate’s “Seven Languages in Seven Weeks.”
This exercise has taught me a lot about the kinds of interfaces I’m trying to build in somewhat more practical domains. For certain kinds of relationships, logic programming is by far the most concise and expressive programming system I’ve ever used.
To understand why, let’s talk about Pokémon.
Pokémon is a video game series/multimedia franchise/lifestyle brand set in a world where humans live alongside a menagerie of colorful animal characters.
“Pokémon” is both the name of the franchise and the generic term for the animal characters themselves, which all have their own individual species names. There are over a thousand distinct species of Pokémon, from Bulbasaur (#1) to Pecharunt (#1025).

Popular Pokémon include (from left to right):
Pikachu (#25), Archeops (#567) , and Dipplin (#1101).
There are all sorts of Pokémon games now, but the main series has always been about catching and battling them. During a battle, your team of six Pokémon faces off against another team. Each Pokémon is equipped with four moves that it can choose to (usually) do damage to their opponent. You need to reduce the HP (Hit Points) of all your opponent’s Pokémon to zero before they are able to do so to you.
Each Pokémon has unique traits that affects how it battles. They have a set of base stats, a large pool of possible moves, a handful of abilities, and a typing. As you will see in a moment, the immense number of combinations here is the motivation for trying to track this with software.

Scizor is a Bug/Steel type with high Attack and low Speed (via Smogon)
Typing is especially important. Moves have a type, like Fire or Rock, and Pokémon can have up to two types. A move with a type that is Super Effective against the opposing Pokémon will do double damage; a move that is Not Very Effective will do half damage.
It’s a little more intuitive with examples. The Fire-type move Flamethrower will do 2x to Grass-type Pokémon, because Grass is weak to Fire, but the Water-type move Surf will only do ½ damage to them, because Grass resists Water.

Lunatone is a Rock/Psychic Type. Rock is weak to Water, and Psychic is neutral to it, so Surf will do 2x damage.
Type modifiers can stack. Scizor is a Bug/Steel type, and both Bug and Steel are weak to Fire, so Fire moves will do 4x damage to Scizor. Electric is weak to Water, but Ground is immune, so if you use an Electric type move against Water/Ground Swampert, you’ll do zero damage, since 0×2 is still 0.
Naturally, there is a chart to help you keep track.
Pokémon Type Chart (via Wikimedia)
Those are effectively the mechanics of the Pokémon video games as I understood them when I was 8. Click moves to do damage, try to click moves with good type matchups. These games are for children and, at the surface level, they’re not very hard.
Before I explain how wonky the Pokémon mechanics can get under the hood, I first need to explain how logic programming works. Pokémon is a great fit for logic programming because Pokémon battles are essentially an extremely intricate rules engine.
Let’s start by creating a file with a bunch of facts.
pokemon(bulbasaur).
pokemon(ivysaur).
pokemon(venusaur).
pokemon(charmander).
pokemon(charmeleon).
pokemon(charizard).
pokemon(squirtle).
pokemon(wartortle).
pokemon(blastoise).
In Prolog, we declare “predicates.” Predicates define relationships: bulbasaur is a pokemon, charmander is a pokemon, and so on. We refer to this predicate as pokemon/1, because the name of the predicate is pokemon and it has one argument.
These facts are loaded into an interactive prompt called the “top-level.” You query the top-level by typing a statement into the prompt; Prolog tries to find all the ways to make that statement true. When there’s more than one possible solution, the top-level displays the first solution and then awaits user input. You can then have it display one more solution, all the solutions, or stop entirely.
In this first example, we type pokemon(squirtle). and hit Enter. The top-level replies true. Squirtle is, in fact, a Pokémon.
?- pokemon(squirtle).
true.
Not all things are Pokémon.
?- pokemon(alex).
false.
Let’s add Pokémon types in there, as the predicate type/2.
type(bulbasaur, grass).
type(bulbasaur, poison).
type(ivysaur, grass).
type(ivysaur, poison).
type(venusaur, grass).
type(venusaur, poison).
type(charmander, fire).
type(charmeleon, fire).
type(charizard, fire).
type(charizard, flying).
type(squirtle, water).
type(wartortle, water).
type(blastoise, water).
Recall that some Pokémon have just one type while others have two. In the latter case, that’s modeled with two type facts. Bulbasaur is a Grass type, and Bulbasaur is a Poison type; both are true. The paradigm is similar to a One-To-Many relation in a SQL database.
Interactively, we can confirm whether Squirtle is a water type.
?- type(squirtle, water).
true.
Can we state that Squirtle is a Grass type?
?- type(squirtle, grass).
false.
No, because Squirtle is a Water type.
Suppose we didn’t know what type Squirtle was. We can ask!
?- type(squirtle, Type).
Type = water.
In Prolog, names that start with an upper-case letter are variables. Prolog tries to “unify” the predicate with all possible matches for the variable. There’s only one way to make this particular predicate true though: Type has to be water, because Squirtle’s only type is Water.
For Pokémon with two types, the predicate unifies twice.
?- type(venusaur, Type).
Type = grass
; Type = poison.
Semantically, that leading semicolon on the third line means “or.” type(venusaur, Type) is true when Type = grass or when Type = poison.
Any of the terms can be be a variable, which means we can ask questions in any direction. What are all the Grass types? Just make the first argument the variable, and set the second argument to grass.
?- type(Pokemon, grass).
Pokemon = bulbasaur
; Pokemon = ivysaur
; Pokemon = venusaur
; Pokemon = oddish
; Pokemon = gloom
; Pokemon = vileplume
; Pokemon = paras
; Pokemon = parasect
; Pokemon = bellsprout
; ... .
I cut it off, but the prompt would happily would list all 164 of them.
Commas can be used to list multiple predicates—Prolog will unify the variables such that all of them are true. Listing all the Water/Ice types is just a matter of asking what Pokémon exist that unify with both the Water and Ice types.
?- type(Pokemon, water), type(Pokemon, ice).
Pokemon = dewgong
; Pokemon = cloyster
; Pokemon = lapras
; Pokemon = laprasgmax
; Pokemon = spheal
; Pokemon = sealeo
; Pokemon = walrein
; Pokemon = arctovish
; Pokemon = ironbundle
; false.
Even though Pokemon is a variable, in the context of the query, both instances of it have to be the same (just like in algebra). The query only unifies for values of Pokemon where both those predicates hold. For instance, the Water/Ice type Dewgong is a solution because our program contains the following two facts:
type(dewgong, water).
type(dewgong, ice).
Therefore, subbing in dewgong for the Pokemon variable satisfies the query. Squirtle, by contrast, is just a Water type: pokemon(squirtle, water) exists, but not pokemon(squirtle, ice). The query requires both to unify, so squirtle is not a possible value for Pokemon.
Pokémon have lots of data that you can play around with. Iron Bundle is a strong Water/Ice-type Pokémon with high Special Attack. How high exactly?
?- pokemon_spa(ironbundle, SpA).
SpA = 124.
With Special Attack that high, we want to make use of strong Special moves. What Special moves does Iron Bundle know?
?- learns(ironbundle, Move), move_category(Move, special).
Move = aircutter
; Move = blizzard
; Move = chillingwater
; Move = freezedry
; Move = hydropump
; Move = hyperbeam
; Move = icebeam
; Move = icywind
; Move = powdersnow
; Move = swift
; Move = terablast
; Move = waterpulse
; Move = whirlpool.
Freeze-Dry is a particularly good Special move. Here’s a query for all Ice-type Pokémon with Special Attack greater than 120 that learn Freeze-Dry.
?- pokemon_spa(Pokemon, SpA), SpA #> 120, learns(Pokemon, freezedry), type(Pokemon, ice).
Pokemon = glaceon, SpA = 130
; Pokemon = kyurem, SpA = 130
; Pokemon = kyuremwhite, SpA = 170
; Pokemon = ironbundle, SpA = 124
; false.
One last concept before we move on: Rules. Rules have a head and a body, and they unify if the body is true.
A move is considered a damaging move if it’s either a Physical Move or a Special Move. The damaging_move/2 predicate defines all the moves that do direct damage.
damaging_move(Move) :-
move_category(Move, physical)
; move_category(Move, special).
This will unify with any moves that do direct damage.
?- damaging_move(tackle).
true.
?- damaging_move(rest).
false.
Nothing I’ve shown so far is, logically speaking, very ambitious—just “and” and “or” statements about various facts. It’s essentially a glorified lookup table. Still, take a moment to appreciate how much nicer it is to query this database than a plausible alternative, like SQL.
For the facts we’ve seen so far, I would probably set up SQL tables like this:
-- Omitting the other stats to be concise
CREATE TABLE pokemon (pokemon_name TEXT, special_attack INTEGER);
CREATE TABLE pokemon_types(pokemon_name TEXT, type TEXT);
CREATE TABLE pokemon_moves(pokemon_name TEXT, move TEXT, category TEXT);
Then query it like so:
SELECT DISTINCT pokemon, special_attack
FROM pokemon as p
WHERE
p.special_attack > 120
AND EXISTS (
SELECT 1
FROM pokemon_moves as pm
WHERE p.pokemon_name = pm.pokemon_name AND move = 'freezedry'
)
AND EXISTS (
SELECT 1
FROM pokemon_types as pt
WHERE p.pokemon_name = pt.pokemon_name AND type = 'ice'
);
For comparison, here’s the equivalent Prolog query again:
?- pokemon_spa(Pokemon, SpA),
SpA #> 120,
learns(Pokemon, freezedry),
type(Pokemon, ice).
I’m not ripping on SQL—I love SQL—but that’s the best declarative query language most people interact with. It’s amazing to me how much simpler and more flexible the Prolog version is. The SQL query would become unmanageably complex if we continued to add clauses, while the Prolog query remains easy to read and edit (once you get the hang of how variables work).
With the basics established, here’s some context on the project I’m working on.
Pokémon battles have an outrageous number of mechanics that all interact in complex and probabilistic ways. Part of the appeal of these games is the futile attempt to keep them all in your head better than your opponent, using that information to out-predict and out-maneuver their plans. It’s a sort of like very silly Poker.
A small subset of game mechanics I have not yet mentioned
The challenge, if you want to build software for this game, is to model all that complexity without losing your mind. Prolog is stunningly good at this, for two main reasons:
To illustrate that, here’s how I implemented priority moves for my Pokémon draft league.
Pokémon draft is pretty much what it sounds like. Pokémon are given a point value based on how good they are, each player is given a certain amount of points to spend, and you draft until every player has spent their points. Your team ends up with about 8-11 Pokémon and each week you go head to head against another person in the league. My friend and WMI collaborator Morry invited me to his a couple years ago and I’ve been hooked on the format ever since.
The games are 6v6, so a big part of the battle is preparing for all the possible combinations of six your opponent could bring, and putting together six of your own that can handle all of them.
Naturally, you can only build teams with the Pokémon you drafted. I just made that predicate my name: alex/1.
alex(meowscarada).
alex(weezinggalar).
alex(swampertmega).
alex(latios).
alex(volcarona).
alex(tornadus).
alex(politoed).
alex(archaludon).
alex(beartic).
alex(dusclops).
What Pokémon do I have that learn Freeze-Dry?
?- alex(Pokemon), learns(Pokemon, freezedry).
false.
None. Rats.
One very important type of move is priority moves. Earlier I mentioned that the Speed stat controls which Pokémon moves first. Some nuance: the Pokémon that used the move with the highest priority goes first, and if they both selected a move of the same priority, then the one with the higher Speed goes first.
Most moves have a priority of zero.
?- move_priority(Move, P).
Move = '10000000voltthunderbolt', P = 0
; Move = absorb, P = 0
; Move = accelerock, P = 1
; Move = acid, P = 0
; Move = acidarmor, P = 0
; Move = aciddownpour, P = 0
; Move = acidspray, P = 0
; Move = acrobatics, P = 0
; Move = acupressure, P = 0
; Move = aerialace, P = 0
; Move = aeroblast, P = 0
Ah, but not all! Accelerock has a priority of 1. A Pokémon that uses Accelerock will move before any Pokémon that uses a move with priority 0 (or less), even if the latter Pokémon has a higher Speed stat.
I define a learns_priority/3 predicate that unifies with a Pokémon, the priority move it learns, and what priority that move is.
learns_priority(Pokemon, Move, P) :-
learns(Pokemon, Move),
move_priority(Move, P),
P #> 0.
A simple query that asks “what priority moves does my team learn” returns a lot of answers.
?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).
Pokemon = meowscarada, Move = endure, Priority = 4
; Pokemon = meowscarada, Move = helpinghand, Priority = 5
; Pokemon = meowscarada, Move = protect, Priority = 4
; Pokemon = meowscarada, Move = quickattack, Priority = 1
; Pokemon = meowscarada, Move = allyswitch, Priority = 2
; Pokemon = meowscarada, Move = suckerpunch, Priority = 1
; Pokemon = weezinggalar, Move = endure, Priority = 4
; Pokemon = weezinggalar, Move = protect, Priority = 4
; Pokemon = swampertmega, Move = bide, Priority = 1
; Pokemon = swampertmega, Move = endure, Priority = 4
; Pokemon = swampertmega, Move = helpinghand, Priority = 5
; Pokemon = swampertmega, Move = protect, Priority = 4
; Pokemon = swampertmega, Move = wideguard, Priority = 3
; Pokemon = latios, Move = allyswitch, Priority = 2
; Pokemon = latios, Move = endure, Priority = 4
; Pokemon = latios, Move = helpinghand, Priority = 5
; Pokemon = latios, Move = magiccoat, Priority = 4
; Pokemon = latios, Move = protect, Priority = 4
; Pokemon = volcarona, Move = endure, Priority = 4
; Pokemon = volcarona, Move = protect, Priority = 4
; Pokemon = volcarona, Move = ragepowder, Priority = 2
; Pokemon = tornadus, Move = endure, Priority = 4
; Pokemon = tornadus, Move = protect, Priority = 4
; Pokemon = politoed, Move = detect, Priority = 4
; Pokemon = politoed, Move = endure, Priority = 4
; Pokemon = politoed, Move = helpinghand, Priority = 5
; Pokemon = politoed, Move = protect, Priority = 4
; Pokemon = politoed, Move = bide, Priority = 1
; Pokemon = archaludon, Move = endure, Priority = 4
; Pokemon = archaludon, Move = protect, Priority = 4
; Pokemon = beartic, Move = aquajet, Priority = 1
; Pokemon = beartic, Move = bide, Priority = 1
; Pokemon = beartic, Move = endure, Priority = 4
; Pokemon = beartic, Move = protect, Priority = 4
; Pokemon = dusclops, Move = allyswitch, Priority = 2
; Pokemon = dusclops, Move = endure, Priority = 4
; Pokemon = dusclops, Move = helpinghand, Priority = 5
; Pokemon = dusclops, Move = protect, Priority = 4
; Pokemon = dusclops, Move = shadowsneak, Priority = 1
; Pokemon = dusclops, Move = snatch, Priority = 4
; Pokemon = dusclops, Move = suckerpunch, Priority = 1
; false.
Although this is technically correct (the best kind), most of these answers are not actually useful. Helping Hand and Ally Switch have very high priority, but they only have a purpose in Double Battles, which isn’t the format I’m playing.
To fix this, I define all the Double Battle moves and exclude them. I’m going to exclude the move Bide too, which is functionally useless. The \+/1 predicate means “true if this goal fails”, and dif/2 means “these two terms are different.”
learns_priority(Mon, Move, Priority) :-
learns(Mon, Move),
\+ doubles_move(Move),
dif(Move, bide),
move_priority(Move, Priority),
Priority #> 0.
doubles_move(helpinghand).
doubles_move(afteryou).
doubles_move(quash).
doubles_move(allyswitch).
doubles_move(followme).
doubles_move(ragepowder).
doubles_move(aromaticmist).
doubles_move(holdhands).
doubles_move(spotlight).
We get the following results:
?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).
Pokemon = meowscarada, Move = endure, Priority = 4
; Pokemon = meowscarada, Move = protect, Priority = 4
; Pokemon = meowscarada, Move = quickattack, Priority = 1
; Pokemon = meowscarada, Move = suckerpunch, Priority = 1
; Pokemon = weezinggalar, Move = endure, Priority = 4
; Pokemon = weezinggalar, Move = protect, Priority = 4
; Pokemon = swampertmega, Move = endure, Priority = 4
; Pokemon = swampertmega, Move = protect, Priority = 4
; Pokemon = latios, Move = endure, Priority = 4
; Pokemon = latios, Move = magiccoat, Priority = 4
; Pokemon = latios, Move = protect, Priority = 4
; Pokemon = volcarona, Move = endure, Priority = 4
; Pokemon = volcarona, Move = protect, Priority = 4
; Pokemon = tornadus, Move = endure, Priority = 4
; Pokemon = tornadus, Move = protect, Priority = 4
; Pokemon = politoed, Move = detect, Priority = 4
; Pokemon = politoed, Move = endure, Priority = 4
; Pokemon = politoed, Move = protect, Priority = 4
; Pokemon = archaludon, Move = endure, Priority = 4
; Pokemon = archaludon, Move = protect, Priority = 4
; Pokemon = beartic, Move = aquajet, Priority = 1
; Pokemon = beartic, Move = endure, Priority = 4
; Pokemon = beartic, Move = protect, Priority = 4
; Pokemon = dusclops, Move = endure, Priority = 4
; Pokemon = dusclops, Move = protect, Priority = 4
; Pokemon = dusclops, Move = shadowsneak, Priority = 1
; Pokemon = dusclops, Move = snatch, Priority = 4
; Pokemon = dusclops, Move = suckerpunch, Priority = 1
; false.
Much better, but there’s a handful of moves in there that go first because they protect the user from damage or status, like Detect. That’s not really what I mean by priority move—I’m interested in moves that will surprise my opponent with damage or an adverse side effect, like Quick Attack and Sucker Punch.
learns_priority(Mon, Move, Priority) :-
learns(Mon, Move),
\+ doubles_move(Move),
\+ protection_move(Move),
Move \= bide,
move_priority(Move, Priority),
Priority #> 0.
protection_move(detect).
protection_move(protect).
protection_move(kingsshield).
protection_move(burningbulwark).
protection_move(spikyshield).
protection_move(banefulbunker).
protection_move(endure).
protection_move(magiccoat).
With those rules in place, we arrive at a very useful answer!
?- alex(Pokemon), learns_priority(Pokemon, Move, Priority).
Pokemon = meowscarada, Move = quickattack, Priority = 1
; Pokemon = meowscarada, Move = suckerpunch, Priority = 1
; Pokemon = beartic, Move = aquajet, Priority = 1
; Pokemon = dusclops, Move = shadowsneak, Priority = 1
; Pokemon = dusclops, Move = snatch, Priority = 4
; Pokemon = dusclops, Move = suckerpunch, Priority = 1
; false.
It’s even more useful to look up what priority moves my opponent for the week has.
?- morry(Pokemon), learns_priority(Pokemon, Move, Priority).
Pokemon = mawilemega, Move = snatch, Priority = 4
; Pokemon = mawilemega, Move = suckerpunch, Priority = 1
; Pokemon = walkingwake, Move = aquajet, Priority = 1
; Pokemon = ursaluna, Move = babydolleyes, Priority = 1
; Pokemon = lokix, Move = feint, Priority = 2
; Pokemon = lokix, Move = firstimpression, Priority = 2
; Pokemon = lokix, Move = suckerpunch, Priority = 1
; Pokemon = alakazam, Move = snatch, Priority = 4
; Pokemon = skarmory, Move = feint, Priority = 2
; Pokemon = froslass, Move = iceshard, Priority = 1
; Pokemon = froslass, Move = snatch, Priority = 4
; Pokemon = froslass, Move = suckerpunch, Priority = 1
; Pokemon = dipplin, Move = suckerpunch, Priority = 1.
At this point, I showed the program to Morry and he hit me with a challenge. Pokémon with the Prankster ability get an additional +1 priority on their status moves. Could the rule be extended to note that?
I happen to have one such Pokémon on my team.
?- alex(Pokemon), pokemon_ability(Pokemon, prankster).
Pokemon = tornadus
; false.
This took me 3 minutes, using Prolog’s if/then construct, ->/2.
learns_priority(Mon, Move, Priority) :-
learns(Mon, Move),
\+ doubles_move(Move),
\+ protection_move(Move),
Move \= bide,
move_priority(Move, BasePriority),
(
pokemon_ability(Mon, prankster), move_category(Move, status) ->
Priority #= BasePriority + 1
; Priority #= BasePriority
),
Priority #> 0.
Now the same query includes all of Tornadus’ status moves, with their increased priority.
?- alex(Pokemon), learns_priority(Pokemon, Move, P).
Pokemon = meowscarada, Move = quickattack, P = 1
; Pokemon = meowscarada, Move = suckerpunch, P = 1
; Pokemon = tornadus, Move = agility, P = 1
; Pokemon = tornadus, Move = attract, P = 1
; Pokemon = tornadus, Move = bulkup, P = 1
; Pokemon = tornadus, Move = confide, P = 1
; Pokemon = tornadus, Move = defog, P = 1
; Pokemon = tornadus, Move = doubleteam, P = 1
; Pokemon = tornadus, Move = embargo, P = 1
; Pokemon = tornadus, Move = leer, P = 1
; Pokemon = tornadus, Move = metronome, P = 1
; Pokemon = tornadus, Move = nastyplot, P = 1
; Pokemon = tornadus, Move = raindance, P = 1
; Pokemon = tornadus, Move = rest, P = 1
; Pokemon = tornadus, Move = roleplay, P = 1
; Pokemon = tornadus, Move = sandstorm, P = 1
; Pokemon = tornadus, Move = scaryface, P = 1
; Pokemon = tornadus, Move = sleeptalk, P = 1
; Pokemon = tornadus, Move = snowscape, P = 1
; Pokemon = tornadus, Move = substitute, P = 1
; Pokemon = tornadus, Move = sunnyday, P = 1
; Pokemon = tornadus, Move = swagger, P = 1
; Pokemon = tornadus, Move = tailwind, P = 1
; Pokemon = tornadus, Move = taunt, P = 1
; Pokemon = tornadus, Move = torment, P = 1
; Pokemon = tornadus, Move = toxic, P = 1
; Pokemon = beartic, Move = aquajet, P = 1
; Pokemon = dusclops, Move = shadowsneak, P = 1
; Pokemon = dusclops, Move = snatch, P = 4
; Pokemon = dusclops, Move = suckerpunch, P = 1
; false.
At the top, I said that this experience had taught me about the kinds of interfaces I want to build. One of those lessons is fairly obvious: Prolog can be a little clunky, but it’s an elegant language for expressing and querying relations like the ones described here. That has implications if you, like me, are interested in the judicious use of declarative DSLs for programming.
The other lesson is what kinds of tools work for non-programmers.
I’m not the first person to think “it would be nice to know what priority moves my opponent’s team has.” The Pokémon community has resources like this, built in the best programming interface of all time: the humble spreadsheet.

Much prettier to look at, too.
I use a copy of “Techno’s Prep Doc”, which is one of those spectacularly-advanced Google Sheets you come across in the wild sometimes. You put in the teams and it generates tons of useful information about the matchup. It has a great interface, support for a variety of formats, scannable visuals, and even auto-complete.
I was curious about the formula for finding priority moves. It’s gnarly.
={IFERROR(ARRAYFORMULA(VLOOKUP(FILTER(INDIRECT(Matchup!$S$3&"!$AV$4:$AV"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"),{Backend!$L$2:$L,Backend!$F$2:$F},2,FALSE))),IFERROR(FILTER(INDIRECT(Matchup!$S$3&"!$AW$4:$AW"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"))}
With a little bit of clicking around, I was basically able to figure out what this does. There’s a “Backend” sheet that lists all the moves. It’s effectively a hard-coded version of my Prolog query.

The lookup formula does some filtering, VLOOKUP-ing, and kinda-metaprogramming (INDIRECT returns a cell reference) to find all the Pokémon on your team that are in that Backend list, and display them.
There are a number of reasons that I, personally, would prefer to work on a version of this database implemented in Prolog instead of one implemented with spreadsheet VLOOKUPs. I plan to build webapps with this that do things the existing suite of Pokémon tooling can’t. (If I can ever get scryer-prolog to compile to WASM, that is.)
Furthermore, the Prolog paradigm is clearly more extensible. The spreadsheet backend is a hard-coded list of notable moves; my database can look up any move. I still can’t really believe this query, which finds all the Special moves that Tornadus learns which are super-effective against any member of Justin’s team. Nothing like that exists in any tool that I know of—it’s the kind of thing I normally try to figure out by endlessly switching tabs. With the grammar established by my program, I put this together in like 30 seconds.
?- justin(Target), learns(tornadus, Move), super_effective_move(Move, Target), move_category(Move, special).
Target = charizardmegay, Move = chillingwater
; Target = terapagosterastal, Move = focusblast
; Target = alomomola, Move = grassknot
; Target = scizor, Move = heatwave
; Target = scizor, Move = incinerate
; Target = runerigus, Move = chillingwater
; Target = runerigus, Move = darkpulse
; Target = runerigus, Move = grassknot
; Target = runerigus, Move = icywind
; Target = screamtail, Move = sludgebomb
; Target = screamtail, Move = sludgewave
; Target = trapinch, Move = chillingwater
; Target = trapinch, Move = grassknot
; Target = trapinch, Move = icywind
; false.
?-
I’m not interested in how structured programming is more extensible than spreadsheets, though. I already know why I don’t do all my programming in spreadsheets.
={IFERROR(ARRAYFORMULA(VLOOKUP(FILTER(INDIRECT(Matchup!$S$3&"!$AV$4:$AV"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"),{Backend!$L$2:$L,Backend!$F$2:$F},2,FALSE))),IFERROR(FILTER(INDIRECT(Matchup!$S$3&"!$AW$4:$AW"),INDIRECT(Matchup!$S$3&"!$AT$4:$AT")="X"))}
A question I find very important is: What is it about this particular problem, and the kinds of people who were motivated to solve it, where the most well-maintained solution available is a spreadsheet?
I believe there are a great many problems like that in the world, and a lot of improvements on that programming paradigm yet to be properly realized.
Thanks to Morry Kolman for reading a draft of this blog.
\+/2 or ->/2 predicates. I couldn’t really figure out how to express what I wanted with the replacements offered, though. Happy to edit if anyone wants to help.