formal/formal.scm
2025-02-20 23:38:00 -05:00

190 lines
4.8 KiB
Scheme
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;; okay, i'll keep these procedures for the sake of nesting
(define (make-pand p q) `( ,p ,q))
(define pand
(case-lambda
[() #t]
[(p) p]
[(p . qs) (fold-left make-pand p qs)]))
(define (make-por p q) `( ,p ,q))
(define por
(case-lambda
[() #f]
[(p) p]
[(p . qs) (fold-left make-por p qs)]))
;; iirc, or: (not p) or q
(define (make-p-> p q) `( ,p ,q))
;; could fold but laziness
(define p->
(case-lambda
[() #t]
[(p) p]
[(p . qs) (make-p-> p (apply p-> qs))]))
(define (p<-> p q) (pand (p-> p q) (p-> q p)))
;; maybe could have some pretty-printing logic? this definition is just 2 good 2 pass up
(define (pnot p) (p-> p #f))
(define ~ pnot)
;; maybe a var is just a sym? no forall lel
;; existence l8r
;; nah
;; valued vars? :o
;; (define-record-type forall
;; (fields vars p)
;; (nongenerative forall))
;; (define-syntax forall
;; (syntax-rules ()
;; [(forall (var* ...) p)
;; (make-forall
;; '(var* ...)
;; (let ([var* 'var*] ...) p))]
;; [(forall var p) (forall (var) p)]))
(define-record-type argument
(fields premises conclusions)
(nongenerative argument))
(define-syntax argument
(syntax-rules ()
[(_ (var* ...) (premise* ...) (conclusion* ...))
(lambda (var* ...)
(make-argument (list premise* ...) (list conclusion* ...)))]))
(define modus-ponens
(argument (p q)
[(p-> p q)
p]
[q]))
(define modus-tollens
(argument (p q)
[(p-> p q)
(~ q)]
[(~ p)]))
;; YES: multiple conclusions (no more simplification-{l,r})
(define simplification
(argument (p q)
[(pand p q)]
[p q]))
(define conjunction
(argument (p q)
[p
q]
[(pand p q)]))
;; (follows (P* ... (-> p q) p Q* ...) q modus-ponens) => #t
;; iterative argument
;; laws of inference are taken as fact
;; norm vars
;; order irrelevant
;; search for reqs?
(define (premises<=? p1 p2)
(and (andmap (lambda (prem) (member prem p2)) p1)
#t))
(define (follows? prems q by)
(and (member q (argument-conclusions by))
(premises<=? (argument-premises by) prems)))
(define-record-type step
(fields conclusion rule)
(nongenerative step))
(define-record-type proof
(fields argument steps)
(nongenerative proof))
(define-syntax proof
(syntax-rules ()
[(_ (var* ...) arg (conclusion* rule*) ...)
(lambda (var* ...)
(make-proof (arg var* ...)
(list (make-step conclusion* rule*) ...)))]))
;; bleh
;; huh, procedure-arity-mask can handle 1000+ no problem
;; returns the lowest arity
(define (procedure-arity proc)
(define mask (procedure-arity-mask proc))
(let loop ([i 0])
(if (logbit? i mask)
i
(loop (add1 i)))))
;; so over-general lel
(define proof-variable
(let* ([bases '#(p q r s t u)]
[bases-count (vector-length bases)])
(lambda (n)
(define base (vector-ref bases (mod n bases-count)))
(define num (floor (/ n bases-count)))
(string->symbol
(format "~a~a"
base
(if (zero? num) "" num))))))
;; i think i prefer the turnstile to therefore
(define (argument-pp arg port)
(define prems (argument-premises arg))
(define concls (argument-conclusions arg))
(format port "~{~a\n~}~{⊢ ~a\n~}"
prems concls))
(define (general-argument-pp arg% port)
;; map iota is bad but convenient (macro so needed)
(define arg (apply arg% (map proof-variable (iota (procedure-arity arg%)))))
(argument-pp arg port))
(define (proof-valid? proof%)
(define (gensyms n xs)
(if (zero? n)
;; less confusing to apply (proof g0 g1 ...)
(reverse xs)
(gensyms (sub1 n) (cons (gensym) xs))))
(define proof (apply proof% (gensyms (procedure-arity proof%) '())))
(define arg (proof-argument proof))
;; reversing here lets us recover a nice order at the end
(define prems (reverse (argument-premises arg)))
(define goals (argument-conclusions arg))
(call/1cc
(lambda (return)
(for-each
(lambda (step)
(let ([concl (step-conclusion step)]
[rule (step-rule step)])
(unless (follows? prems concl rule)
(printf "Failed to apply rule:\n")
(argument-pp rule #t)
(printf "Proof state:\n~{~a\n~}⊢ ~a\n"
(reverse prems) concl)
(return #f))
(set! prems (cons concl prems))))
(proof-steps proof))
(when (or (member #f prems) (premises<=? goals prems))
(return #t))
(format #t "Proof incomplete:\n~{~a\n~}~{⊢ ~a\n~}\n"
prems goals))))
;; TODO: i can just guess lol
;; this really isn't far from the macroless version....
(define and-comm
(proof (p q)
(argument (p q)
[(pand p q)]
[(pand q p)])
[p (simplification p q)]
[q (simplification p q)]
[(pand q p) (conjunction q p)]))