Skip to content

Tutorial 4: Writing Rules

This tutorial teaches you how to write halachic rules using HLL (Halachic Logic Language), Mistaber's domain-specific language.

Learning Objectives

  • Understand HLL syntax and directives
  • Write rules with proper source citations (makor)
  • Set normative levels (madrega)
  • Compile HLL to ASP
  • Test your rules

Prerequisites

Estimated Time

25 minutes

Steps

Step 1: Introduction to HLL

HLL (Halachic Logic Language) is a domain-specific language that compiles to Answer Set Programming (ASP). It provides:

  • Directives for metadata (@world, @rule, @makor, @madrega)
  • Surface syntax for common patterns (basar(X) -> food_type(X, basar))
  • Type checking to catch errors early
  • Normative requirements enforcing source citations

Step 2: Basic Rule Structure

An HLL rule looks like this:

@world(mechaber)
@rule(r_example_rule)
@makor([sa("yd:87:1")])
@madrega(d_rabanan)

conclusion(X) :- condition1(X), condition2(X).

Let's break this down:

Element Purpose
@world(mechaber) Rule applies in the mechaber world
@rule(r_example_rule) Unique identifier for the rule
@makor([sa("yd:87:1")]) Source: Shulchan Aruch YD 87:1
@madrega(d_rabanan) Normative level: rabbinic
conclusion(X) :- ... The actual logical rule

Step 3: Write Your First HLL Rule

Create a file called my_rules.hll:

% my_rules.hll
% A custom rule for demonstration

@world(base)
@rule(r_forbidden_unknown_source)
@makor([custom("demo:1")])
@madrega(d_rabanan)

% If a food has unknown source, it's forbidden to eat
issur(achiila, F) :- food(F), unknown_source(F).

This rule says: "According to base halacha, eating food with unknown source is rabbinically forbidden."

Step 4: Compile HLL to ASP

Use the compiler to convert HLL to ASP:

from mistaber.dsl.compiler.compiler import compile_hll

hll_source = """
@world(base)
@rule(r_my_rule)
@makor([sa("yd:87:1")])
@madrega(d_rabanan)

forbidden(F) :- basar(F), chalav(F).
"""

asp_code = compile_hll(hll_source)
print("Generated ASP:")
print(asp_code)

Output:

% Generated ASP:
rule(r_my_rule).
makor(r_my_rule, sa("yd:87:1")).
madrega(r_my_rule, d_rabanan).
scope(r_my_rule, base).

forbidden(F) :- food_type(F, basar), food_type(F, chalav).

Notice how: - Metadata becomes facts (rule/1, makor/2, madrega/2, scope/2) - basar(F) was normalized to food_type(F, basar)

Step 5: HLL Directives

@world - Scope Declaration

Specifies which world this rule applies to:

@world(mechaber)       % Applies to mechaber world
@world(rema)           % Applies to rema world
@world(base)           % Applies to all (inherited)

@rule - Rule Identifier

Every normative rule needs a unique ID:

@rule(r_bb_beheima_achiila)    % Descriptive naming
@rule(r_yd87_1)                % Source-based naming
@rule(r_my_custom_rule)        % Custom naming

Naming convention: r_[topic]_[detail]

@makor - Source Citation

Required for normative rules. Supports multiple sources:

@makor([sa("yd:87:1")])                           % Shulchan Aruch
@makor([rema("yd:87:3")])                         % Rema's gloss
@makor([sa("yd:87:1"), rema("yd:87:1")])         % Multiple sources
@makor([taz("yd:87:3"), shach("yd:87:5")])       % Commentators
@makor([gemara("pesachim:76b")])                  % Talmudic source

@madrega - Normative Level

Sets the strength of the prohibition:

@madrega(d_oraita)     % Torah-level (strongest)
@madrega(d_rabanan)    % Rabbinic level
@madrega(minhag)       % Custom
@madrega(chumra)       % Stringency (weakest)

Step 6: Surface Syntax Shortcuts

HLL provides shortcuts for common patterns:

HLL Shortcut Expands To
basar(X) food_type(X, basar)
chalav(X) food_type(X, chalav)
beheima(X) food_type(X, beheima)
of(X) food_type(X, of)
dag(X) food_type(X, dag)
issur(Act, Item) issur(Act, Item, madrega)
mutar(Act, Item) heter(Act, Item)

Example:

% HLL (readable)
@world(base)
@rule(r_test)
@makor([sa("yd:87:1")])
@madrega(d_rabanan)

forbidden(F) :- of(F), chalav(F).

% Compiles to ASP:
forbidden(F) :- food_type(F, of), food_type(F, chalav).

Step 7: Writing Mixture Rules

Rules about mixtures use the mixture/1 and contains/2 predicates:

@world(mechaber)
@rule(r_beheima_chalav_mix)
@makor([sa("yd:87:1")])
@madrega(d_oraita)

% Eating a beheima+chalav mixture is forbidden d'oraita
asserts(mechaber, issur(achiila, M, d_oraita)) :-
    mixture(M),
    contains(M, Basar),
    contains(M, Dairy),
    food_type(Basar, beheima),
    food_type(Dairy, chalav).

Step 8: Type Checking and Validation

The compiler checks for:

  1. Undeclared predicates: Unknown predicates raise errors
  2. Arity mismatches: Wrong number of arguments
  3. Missing makor: Normative rules need sources
  4. OWA negation warnings: Using not on open-world predicates
from mistaber.dsl.compiler.compiler import compile_hll, CompileError

# This will raise an error - missing makor
try:
    compile_hll("""
        @world(base)
        @rule(r_test)
        forbidden(X) :- bad_thing(X).
    """)
except CompileError as e:
    print(f"Error: {e}")

# This will succeed with a warning
asp, warnings = compile_hll("""
    @world(base)
    @rule(r_test)
    @makor([sa("yd:1:1")])
    permitted(F) :- not is_forbidden(F).
""", return_warnings=True)

for w in warnings:
    print(f"Warning: {w.message}")

Step 9: Complete Example - Encoding a New Rule

Let's encode a real halachic rule from scratch. We'll add a rule about "davar sheyesh lo matirin" (something that will become permitted):

% davar_sheyesh_lo_matirin.hll
% Rule: Items that will become permitted are not nullified

@world(base)
@rule(r_davar_sheyesh_lo_matirin)
@makor([sa("yd:102:1"), rema("yd:102:1")])
@madrega(d_rabanan)

% If an item will become permitted (e.g., before its time),
% it cannot be nullified even in a majority
min_lo_batel(M) :-
    mixture(M),
    contains(M, F),
    will_become_permitted(F).

% The issur remains even with shishim (60:1)
issur(achiila, M) :-
    mixture(M),
    contains(M, F),
    will_become_permitted(F),
    issur_present(F).

Step 10: Test Your Rules

After writing rules, test them:

from pathlib import Path
from mistaber.engine import HsrsEngine
from mistaber.dsl.compiler.compiler import compile_hll

# Compile the HLL
hll_source = """
@world(base)
@rule(r_test_rule)
@makor([sa("yd:1:1")])
@madrega(d_rabanan)

asserts(base, custom_issur(F)) :- food(F), is_suspect(F).
"""

asp_code = compile_hll(hll_source)

# Create scenario to test
engine = HsrsEngine(Path("mistaber/ontology"))

test_scenario = f"""
{asp_code}

food(suspicious_item).
is_suspect(suspicious_item).
"""

result = engine.analyze(test_scenario, world="base")

# Check if our rule fired
custom_issurim = [a for a in result["atoms"] if "custom_issur" in a]
print(f"Custom issur derived: {custom_issurim}")

Best Practices

1. Always Cite Sources

Every normative rule must have a makor. This ensures traceability:

% Good
@rule(r_my_rule)
@makor([sa("yd:87:1")])

% Bad - will fail compilation
@rule(r_my_rule)
% no makor

2. Use Descriptive Rule Names

% Good - descriptive
@rule(r_beheima_chalav_achiila_doraita)

% Bad - cryptic
@rule(r_123)

3. Set Appropriate Madrega

Match the normative level to the source:

% Torah-level prohibition
@madrega(d_oraita)

% Rabbinic decree
@madrega(d_rabanan)

% Community custom
@madrega(minhag)

4. Document Complex Logic

Use comments to explain the halachic reasoning:

% SA YD 87:3 - Poultry with milk
% The Gemara (Chullin 104b) derives that "gedi" excludes fowl,
% making the prohibition only d'rabanan
@rule(r_of_chalav)
@makor([sa("yd:87:3"), gemara("chullin:104b")])
@madrega(d_rabanan)

Exercises

Exercise 1: Write a Basic Rule

Write an HLL rule that forbids eating something that is both meat and fish together.

Solution
@world(base)
@rule(r_basar_dag_forbidden)
@makor([gemara("pesachim:76b")])
@madrega(sakana)

issur(achiila, M) :-
    mixture(M),
    contains(M, Meat),
    contains(M, Fish),
    basar_type(Meat),
    food_type(Fish, dag).

Exercise 2: Add World-Specific Override

Write rules for both Mechaber and Rema on a disputed topic.

Solution
% Mechaber: Stringent view
@world(mechaber)
@rule(r_strict_view)
@makor([sa("yd:1:1")])
@madrega(d_rabanan)

asserts(mechaber, issur(achiila, item_x)) :- condition(item_x).

% Rema: Override with lenient view
@world(rema)
@rule(r_lenient_view)
@makor([rema("yd:1:1")])

override(rema, issur(achiila, item_x), lenient_tradition).
asserts(rema, heter(achiila, item_x)) :- condition(item_x).

What You've Learned

  • HLL syntax with directives (@world, @rule, @makor, @madrega)
  • How to write rules using surface syntax shortcuts
  • How to compile HLL to ASP
  • Type checking and validation
  • Best practices for rule authoring

Next Steps

Continue to Tutorial 5: Encoding Machloket to learn how to model halachic disputes.


Production Encoding Workflow

For systematic encoding of halacha with human review checkpoints, see the Encoding Workflow Guide which uses the mistaber-skills Claude Code plugin for AI-assisted encoding.

Quick Reference

% HLL Template
@world(world_name)
@rule(r_unique_id)
@makor([sa("siman:seif"), ...])
@madrega(d_oraita | d_rabanan | minhag | chumra)

conclusion(Args) :- body(Args).
# Compile HLL
from mistaber.dsl.compiler.compiler import compile_hll
asp = compile_hll(hll_source)
asp, warnings = compile_hll(hll_source, return_warnings=True)