Reasoning Toolkit
Apologetic question: "Can faith be reasoned about?"
Project 5 — Reasoning Toolkit
“Come now, let us reason together, says the LORD.” — Isaiah 1:18
Chapter: 5 — Repetition II: Functions
Due: End of Week 5
Submit: A link to your code — an OnlineGDB project URL or a public GitHub repo URL — with reasoning.cpp as the main source file. See Appendix D for the full workflow.
Allowed tools: Everything through Chapter 5 — types, arithmetic, conditionals, loops, functions you define yourself.
Not yet allowed: Arrays, structs, classes.
The Setup
The Christian intellectual tradition has used formal reasoning — logic, probability, evidence-weighing — for as long as there has been a tradition. Augustine reasoned about time and creation. Aquinas built his Five Ways on formal logical structure. Pascal’s Wager is an expected-value calculation. The McGrews have written extensively on Bayesian analysis of the Resurrection. Plantinga’s modal arguments are tight, multi-step logical proofs.
This is not the only mode in which Christians think about Christianity — the lived practices and the affective dimensions of faith are also essential. But the intellectual tradition is real, and it has its own toolkit.
This project gives you a small computational version of that toolkit. By the time you ship it, you will have written, by hand, the Bayesian update formula, a modus ponens validity checker, a probability-conjunction calculator, and (depending on your tier) several more reasoning primitives. You’ll be more careful about probabilities afterward. That’s worth more than the grade.
A warning before you start, and one this project’s Coach’s Note repeats: the toolkit doesn’t settle apologetic debates. A Bayesian update with sloppy inputs gives confident garbage. The point of writing the math in code is to feel, in your fingers, how much each conclusion depends on the inputs. Humility is part of the lesson. Treat the program as a tool for thinking, not as a debate-winning machine.
Learning Targets
By completing this project, you will demonstrate that you can:
- Define multiple functions with parameters and return values.
- Compose functions — write one that calls another.
- Use
voidfunctions appropriately for actions that print or modify state. - Write a menu-driven
mainthat dispatches to functions based on user input. - Use pass-by-reference for output parameters (Hard tier).
- Use guard clauses and error return codes for input validation.
Normal Tier
Goal: A menu-driven reasoning toolkit with five named operations, each implemented as its own function.
Required features
-
Five functions, each with the signature and behavior described:
double bayes_update(double prior, double L_H, double L_notH)— Returns the Bayesian posterior given a prior and the two likelihoods. Math:(L_H * prior) / (L_H * prior + L_notH * (1 - prior)).double complement(double p)— Returns1 - p.double conjunction_probability(double p1, double p2)— Returnsp1 * p2. (Assumes independence — note this in a comment.)bool modus_ponens(bool premise_A, bool premise_A_implies_B)— Returnstrueif both premises aretrue. (This is the simplest possible logic-validity checker.)bool is_valid_probability(double p)— Returnstrueifpis between 0 and 1 inclusive.
-
A menu-driven
mainthat:- Prints a menu of all 5 operations (numbered 1–5) plus an “exit” option (6).
- Loops, reading the user’s choice.
- For each operation, prompts for the inputs, calls the corresponding function, and prints the result.
- For
is_valid_probabilityandmodus_ponens, the result is abool— print it withboolalpha. - Continues looping until the user chooses exit.
-
maincontains no business logic beyond the menu loop and dispatch. All math lives in the functions. The grader will dock you ifmaindoes any of the calculations directly. -
Compiles cleanly with
-Wall -Wextraenabled in OnlineGDB compiler settings (org++ -Wall -Wextraif you build locally). No warnings, no errors.
Example run
=== Reasoning Toolkit ===
1. Bayesian update
2. Complement
3. Conjunction probability
4. Modus ponens check
5. Is valid probability
6. Exit
Choice: 1
Prior P(H): 0.01
P(E|H): 0.99
P(E|~H): 0.01
Posterior P(H|E): 0.5
=== Reasoning Toolkit ===
...
Choice: 6
Goodbye.
Grading rubric — Normal (out of 100)
| Criterion | Points |
|---|---|
Compiles cleanly with -Wall -Wextra | 10 |
| Five functions implemented with correct signatures | 25 |
bayes_update produces correct results (test cases pass) | 10 |
modus_ponens produces correct results | 5 |
is_valid_probability correct | 5 |
| Menu loop dispatches all 5 + exit correctly | 15 |
main contains no math — only menu + I/O + dispatch | 10 |
Booleans print as true/false | 5 |
| Output is labeled and readable | 10 |
| OnlineGDB/GitHub link + reflection comment block | 5 |
Medium Tier (+up to 25% extra credit)
M1. Three more functions, with composition
Add these three functions:
double chained_bayes(double prior, double L1_H, double L1_notH, double L2_H, double L2_notH)— Updates twice in succession. Must callbayes_updateinternally (no duplicated math).double expected_value(double probability, double payoff)— Returnsprobability * payoff. This is the structure of Pascal’s Wager. (You’re not making a claim about Pascal’s Wager by implementing it — the program is just multiplying two numbers.)bool modus_tollens(bool premise_not_B, bool premise_A_implies_B)— Returnstrueif both premises hold; modus tollens lets you concludenot A.
Add menu options for all three. Total menu now: 8 operations + exit = 9 choices.
M2. Input validation as guard clauses
For every function that takes a probability, start the function with a guard clause:
double bayes_update(double prior, double L_H, double L_notH) {
if (!is_valid_probability(prior) || !is_valid_probability(L_H) || !is_valid_probability(L_notH)) {
return -1.0; // sentinel for "invalid"
}
// ... math
}
In main, after calling the function, check whether the result is -1.0 and print “Invalid input — probabilities must be in [0, 1].” instead of the result.
This isn’t error handling at the level of professional code — that comes later in your CS education. It’s the first taste of “what happens when the inputs are wrong.”
M3. Repeat last operation
Add a menu option 9. Repeat last operation. When selected, the program re-runs the last operation chosen, asking for fresh inputs. (Implement by tracking the last choice in a variable in main.)
Hard Tier (+up to 25% additional extra credit)
The Hard tier introduces pass-by-reference and a session log.
H1. Session log via pass-by-reference
Add a void log_operation(string operation_name, double result, int& step_counter) function. The int& step_counter is a reference parameter — every call increments it and the change persists in main.
In main, declare an int step = 0; at the top. Every time an operation completes, call:
log_operation("bayes_update", posterior, step);
The function should print: [Step N] bayes_update → 0.5 where N is the step counter before incrementing, then increment the counter.
Add a 10th menu option: View session log summary. It should print:
Operations run this session: 7
Last result: 0.92
To make that work, you’ll need additional reference parameters or globals (a const count is fine; track the last result in a double declared in main).
H2. Most-used operation
Track how many times each menu option has been chosen. After the user exits, print a summary:
=== Session Summary ===
Total operations: 12
Most used: bayes_update (5 times)
Without arrays (Chapter 6), this is tedious — you’ll need a counter per operation. The point is feeling the pain. Chapter 6 will simplify it.
H3. The flex move
Find one C++ function feature we haven’t covered. Strong candidates:
- Default parameter values:
double bayes_update(double prior, double L_H, double L_notH = 0.5)— the third parameter is optional. - Function overloading: two functions with the same name but different parameter types or counts. Useful for
print_result(double)vs.print_result(bool). inlinefunctions for tiny one-liners (mostly stylistic in modern C++ but worth knowing).
Document per Project 1 H4 rules.
Submission
Submit one URL via the course portal:
- OnlineGDB project link (recommended for Coding 1 and Coding 2). Create your project at onlinegdb.com, set compiler flags to
-Wall -Wextrain the project settings, build your solution, and share the link. See Appendix D for the full workflow. - GitHub repo link (optional). If you’ve set up local development on your own, push the source to a public repo and submit that URL. You’re responsible for making sure the code compiles when the grader checks it out.
What the linked project must contain
- The main source file —
reasoning.cpp— containing your full solution. - A reflection comment block at the very top of that file:
/*
* Tier targeted: Normal / Medium / Hard
* Features done: list each feature you completed
* What I learned: one short paragraph (no bullets)
* What I'd change: one sentence
* AI usage: where and how, if any. Be honest.
*/
- The program left in a “demonstrable” state — when the grader presses Run, the features for your targeted tier should be exercised. Hard-code inputs at the top of
main()(or pre-fill OnlineGDB’s Stdin panel) so the grader doesn’t have to guess what to type.
That’s it. No separate demo.txt. No screenshots. The instructor will open your link, read the comment block, run the program, and grade against the rubric.
Coach’s Note — Coding 1 and Coding 2 focus on writing code, not managing development environments. If something behaves oddly, you and the grader are looking at the exact same browser-hosted environment — there are no “works on my machine” defenses by design. Coding 3 will introduce a local toolchain properly.
Hints
- “My
mainis getting long.” That’s the rep. Your menu loop will be 40–60 lines, with dispatch + I/O. The function bodies are short. That’s correct. - “My function returns the wrong number for
bayes_update(0.01, 0.99, 0.01).” Walk through the math by hand. Numerator:0.99 * 0.01 = 0.0099. Denominator:0.0099 + 0.01 * 0.99 = 0.0099 + 0.0099 = 0.0198. Result:0.0099 / 0.0198 = 0.5. If you got something else, you probably have a parens bug or you used0.99where you meant(1.0 - prior). - “I need a function to do X but X is one line.” Write it anyway. One-line functions with clear names are easier to read than the inline version. Trust the abstraction.
- “How long should this take me?” Normal: 2–4 hours. Medium: 4–6 hours. Hard: 6–10 hours (the session-log plumbing is fiddly without arrays).
What Mastery Looks Like
A great Project 5 has functions that each do one thing well. Read each one out loud: the name is a verb phrase, the parameters are sensibly named and ordered, the return type is right, and the body has at most a handful of lines.
A great Project 5 composes. chained_bayes calls bayes_update. modus_tollens shares structure with modus_ponens. The functions know about each other in a clean way.
A great Project 5 has a main that reads like a menu, not like a calculator. The math has been pushed down into the functions where it belongs.
A great Project 5 has honest output. The user types 0.01, 0.99, 0.01 for a “rare-condition 99% test” scenario, and the program reports 0.5. The student writes a sentence in their README about how counterintuitive that result felt the first time. That’s the rep that matters.
When You’re Done
- Read your
reasoning.cppaloud. Each function should be a short, named idea. Each should be obvious. - Run it. Try at least each of the 5 base operations. Try the Bayesian update with the rare-condition-test scenario above. Be surprised honestly.
- Update README.
- Submit.
- Read Chapter 6. Collections show up — and Hard tier of P5 stops being so painful.
Coach’s Note — A working Reasoning Toolkit is, in a small way, the lab partner of an apologist who does serious Bayesian work. It’s not a substitute for the careful philosophical reasoning that informs the priors and likelihoods. It’s a check on whether the math behind the rhetoric is doing what the rhetoric claims. That check is often missing in popular apologetic writing — sometimes from generous people. Knowing the math, even at this elementary level, makes you a more careful thinker and a more honest one.
See you on Monday.