contract.scm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  1. ;; Suppose we wanted to check assumptions about arguments to
  2. ;; our function. What kind of form could we write to express
  3. ;; this?
  4. ;; (define-with-contract account-withdraw
  5. ;; (requires (< amount account-balance))
  6. ;; (ensures (>= account-balance 0))
  7. ;; (lambda (amount account-balance)
  8. ;; ...))
  9. ;; Or abstractly:
  10. ;; (define-with-contract func
  11. ;; (requires req-pred* ...)
  12. ;; (ensures ensure-pred* ...)
  13. ;; lambda-expr)
  14. ;; We make a proper module, so that the bindings in this
  15. ;; module can be imported in other modules. It also allows
  16. ;; for things like (import (prefix (contract) contract:)),
  17. ;; which prefixes all things from this module.
  18. (library (lib contract)
  19. (export require
  20. ensure
  21. <?>
  22. define-with-contract
  23. define*-with-contract
  24. lambda-with-contract
  25. lambda*-with-contract
  26. make-contract-violated-exception)
  27. (import
  28. (except (rnrs base) let-values)
  29. (only (guile)
  30. lambda* λ
  31. syntax->datum
  32. syntax-error
  33. display
  34. newline
  35. record-constructor)
  36. (ice-9 exceptions)
  37. (srfi srfi-9 gnu)
  38. (srfi srfi-1))
  39. ;;; It turns out, that for recursive expansion of macros it
  40. ;;; becomes very difficult to write the macro
  41. ;;; correctly. However, CK macros promise a solution, by
  42. ;;; implementing a way of expanding macros in the same order
  43. ;;; as function application works.
  44. ;;; Code for the CK macro taken from:
  45. ;;; https://okmij.org/ftp/Scheme/macros.html#ck-macros.
  46. ;;; Some comments added by me <zelphirkaltstahl@posteo.de>.
  47. ;;; Some renaming for readability by me <zelphirkaltstahl@posteo.de>.
  48. ;;; Some formatting by me <zelphirkaltstahl@posteo.de>.
  49. ;; TODO: Explain this thing and why it works :D
  50. (define-syntax ck
  51. (syntax-rules (quote)
  52. ;; This is a base case, which unquotes quoted
  53. ;; expressions, so that they are evaluated at runtime,
  54. ;; instead of remaining quoted.
  55. [(ck () 'v) v] ; yield the value on empty stack
  56. [(ck (((op ...) ea ...) . s) 'v) ; re-focus on the other argument, ea
  57. (ck s "arg" (op ... 'v) ea ...)]
  58. [(ck s "arg" (op va ...)) ; all arguments are evaluated,
  59. (op s va ...)] ; do the redex
  60. [(ck s "arg" (op ...) 'v ea1 ...) ; optimization when the first ea
  61. (ck s "arg" (op ... 'v) ea1 ...)] ; was already a value
  62. [(ck s "arg" (op ...) ea ea1 ...) ; focus on ea, to evaluate it
  63. (ck (((op ...) ea1 ...) . s) ea)]
  64. [(ck s (op ea ...)) ; Focus: handle an application;
  65. (ck s "arg" (op) ea ...)] ; check if args are values
  66. ))
  67. (define-syntax c-cons
  68. (syntax-rules (quote)
  69. ;; As mentioned in the explanation on
  70. ;; https://okmij.org/ftp/Scheme/macros.html#ck-macros:
  71. ;; All things except the stack are quoted. c-cons
  72. ;; expects 2 values, head and tail.
  73. ;; In contrast to the normal pattern matching in
  74. ;; macros, the parts of a pattern need to be
  75. ;; quoted. This is probably due to CK macros building
  76. ;; yet another layer on top of normal macros, managing
  77. ;; things in a stack and having to avoid things
  78. ;; getting evaluated (or expanded in case of other,
  79. ;; possibly non-CK macros) too early.
  80. ;; For example, macro expansion would evaluate a
  81. ;; lambda too early, if it appeared without being
  82. ;; quoted.
  83. ;; This however, does not stop us from using pattern
  84. ;; matching on those quoted values! We can still write
  85. ;; something like the following:
  86. ;; (c-mymacro stack '(bla blub) 'other)
  87. ;; And then use the parts in the resulting expression
  88. ;; as we want, but again quote the resulting
  89. ;; expression.
  90. [(c-cons stack 'head 'tail)
  91. ;; Build up a pair. Always pass the stack along to
  92. ;; the CK macro.
  93. (ck stack '(head . tail))]))
  94. ;; c-map allows to map any kind of expression to a list of
  95. ;; expressions.
  96. (define-syntax c-map
  97. (syntax-rules (quote)
  98. ;; If the applied-thing is applied to the empty list,
  99. ;; then there is no need to even look at what the
  100. ;; applied-thing consists of or to destructure it via
  101. ;; pattern matching, because anything applied to the
  102. ;; empty list, will be the empty list.
  103. [(c-map stack
  104. 'applied-thing
  105. '())
  106. ;; Simply return the empty list.
  107. (ck stack
  108. ;; Base case gives the empty list, to build a
  109. ;; proper list.
  110. '())]
  111. ;; If however there are list elements, at least a head
  112. ;; and some arbitrary tail, then it becomes necessary
  113. ;; to take the applied thing apart, so that we can put
  114. ;; the first element of the list into the expression
  115. ;; of the applied-thing, thereby applying
  116. ;; applied-thing to the first element.
  117. [(c-map stack
  118. '(applied-thing ...)
  119. '(head . tail))
  120. (ck stack
  121. ;; Build up a list using cons.
  122. (c-cons
  123. ;; Apply the applied-thing to the first element.
  124. (applied-thing ... 'head)
  125. ;; Recursively expand c-map for the tail of the
  126. ;; list of expressions.
  127. (c-map '(applied-thing ...) 'tail)))]))
  128. ;; Example usage:
  129. ;; (ck ()
  130. ;; (c-map '(c-cons '10)
  131. ;; '((1) (2))))
  132. ;; (ck ()
  133. ;; (c-map '(c-cons '+)
  134. ;; '((1) (2))))
  135. (define-syntax c-apply
  136. (syntax-rules (quote)
  137. [(c-apply stack 'proc '(expr* ...))
  138. (ck stack '(proc expr* ...))]))
  139. ;; Example usage:
  140. ;; (ck ()
  141. ;; (c-apply '+
  142. ;; (c-map '(c-cons '+)
  143. ;; '((1) (2)))))
  144. ;; To quote things, we need to simply wrap with another
  145. ;; quote. This makes sense, because in the base case of
  146. ;; the CK-macro 1 quote wrapping is removed:
  147. ;; [(ck () 'v) v]
  148. ;; (see above).
  149. (define-syntax c-quote
  150. (syntax-rules (quote)
  151. [(c-quote s 'x)
  152. (ck s ''x)]))
  153. ;;; When we use the CK macro above, there are a few rules to
  154. ;;; adhere to, to make it all work:
  155. ;;; 1. A macro CK style macro must always expand into a call
  156. ;;; of the `ck` macro.
  157. ;;; 2. When expanding into the `ck` macro, never forget to
  158. ;;; pass the stack.
  159. ;;; 3. All values after the stack must be quoted, except
  160. ;;; when they are CK style macros themselves.
  161. ;;; 4. When A CK style macro expands into other CK style
  162. ;;; macros, their arguments still need to be quoted.
  163. ;;; 5. To start the expansion process, call a macro with the
  164. ;;; form (ck () macro-call).
  165. ;;; 6. The `ck` macro will always add the stack as an
  166. ;;; "argument", when expanding into a CK style macro. This
  167. ;;; means one must always match the stack, even though the
  168. ;;; call to a macro does not contain the stack.
  169. ;; Define `require` and `ensure`, so that they are
  170. ;; available as identifiers. This will cause syntax-rules
  171. ;; to be aware of them as identifiers. When the
  172. ;; `define-with-contract` macro is used and one writes an
  173. ;; expression like (require ...), the identifier `require`
  174. ;; is used, instead of it being pure syntax. The advantage
  175. ;; is, that the identifier can be renamed, when imported
  176. ;; in another module. This enables users to change how
  177. ;; macro call looks. They might want to change `ensure` to
  178. ;; `make-sure` or `post-condition`, or any other
  179. ;; renaming. For example like the following import:
  180. ;; (import
  181. ;; (rename (contract)
  182. ;; (require req)
  183. ;; (ensure ens)))
  184. ;; Note, that even though `syntax-rules` specifies
  185. ;; "literals", specifying <?> still works and still allows
  186. ;; for renaming in another module. Very useful!
  187. (define require 'require)
  188. (define ensure 'ensure)
  189. (define <?> '<?>)
  190. ;; Create a custom exception type, to make it clearer,
  191. ;; that a contract failed, and not only an arbitrary
  192. ;; assertion.
  193. (define make-contract-violated-exception
  194. ;; record-constructor is a procedure, which will return
  195. ;; the constructor for any record.
  196. (record-constructor
  197. ;; Create an exception type, which is a record. This
  198. ;; record has a constructor, which we can name using
  199. ;; define for example.
  200. (make-exception-type
  201. ;; name of the new exception type
  202. '&contract-violated
  203. ;; parent exception type
  204. &programming-error
  205. ;; list of values the constructor of the exception
  206. ;; takes and their names in the record
  207. '(message origin irritants))))
  208. ;; `c-and-raise` needs to be a macro, because its
  209. ;; arguments must not be evaluated, before we can look at
  210. ;; them and build up an expression, which contains the
  211. ;; argument in its unevaluated form. We need the not yet
  212. ;; evaluated form, to have a readable and understandable
  213. ;; error message, when raising an exception. The exception
  214. ;; will contain the literal expression, which failed to
  215. ;; evaluate to a truthy value.
  216. (define-syntax c-and-raise
  217. (syntax-rules (quote)
  218. ;; `and-raise` takes a list of expressions to check as
  219. ;; an argument.
  220. [(c-and-raise stack
  221. 'function-name
  222. '(list
  223. (op args* ...)
  224. expr* ...))
  225. (ck stack
  226. '(cond
  227. [(op args* ...)
  228. (ck stack
  229. (c-and-raise (quote function-name)
  230. (quote (list expr* ...))))]
  231. [else
  232. (raise-exception
  233. (make-contract-violated-exception
  234. "contract violated"
  235. (quote function-name)
  236. (quote (op args* ...))))]))]
  237. [(c-and-raise stack
  238. (quote function-name)
  239. (quote (list #|nothing|#)))
  240. (ck stack (quote #t))]))
  241. ;; Usage example:
  242. ;; (ck ()
  243. ;; (c-and-raise 'unknown-origin '(list (= 1 1) (= 2 3))))
  244. ;; (define result 3)
  245. ;; (ck ()
  246. ;; (c-and-raise
  247. ;; 'unknown-origin
  248. ;; (c-map '(c-replace-placeholder 'result)
  249. ;; '(list (= 1 <?>) (= 2 3)))))
  250. ;; (define result 3)
  251. ;; (ck ()
  252. ;; (c-and-raise
  253. ;; 'my-function-name
  254. ;; (c-map '(c-replace-placeholder 'result)
  255. ;; '(list (= 1 <?>) (= 2 3)))))
  256. (define-syntax c-replace-placeholder
  257. (syntax-rules (quote <?>)
  258. ;; Replace the placeholder, if it is the expression.
  259. [(c-replace-placeholder stack 'result (quote <?>))
  260. (ck stack (quote result))]
  261. ;; Only one expression remaining.
  262. [(c-replace-placeholder stack 'result '(expr))
  263. (ck stack
  264. (c-cons
  265. (c-replace-placeholder 'result 'expr)
  266. '()))]
  267. ;; There are multiple expressions left. (Case of single
  268. ;; expression is matched earlier.)
  269. [(c-replace-placeholder stack 'result '(expr expr* ...))
  270. (ck stack
  271. (c-cons
  272. (c-replace-placeholder 'result 'expr)
  273. (c-replace-placeholder 'result '(expr* ...))))]
  274. ;; Take care of vectors.
  275. [(c-replace-placeholder stack 'result (quote #(expr* ...)))
  276. (ck stack
  277. (c-list->vector
  278. (c-replace-placeholder 'result
  279. (c-vector->list
  280. '#(expr* ...)))))]
  281. ;; Or a non-compound expression, which is not the
  282. ;; placeholder.
  283. [(c-replace-placeholder stack 'result 'expr)
  284. (ck stack 'expr)]
  285. ))
  286. ;; Example usage:
  287. ;; (ck () (c-replace-placeholder 'result ''(1 2 <>)))
  288. ;; (ck ()
  289. ;; (c-replace-placeholder 'result
  290. ;; '(apply + (list 1 2 <?>))))
  291. ;; (ck ()
  292. ;; (c-map '(c-replace-placeholder 'result)
  293. ;; '((= 1 <?>))))
  294. (define-syntax c-list->vector
  295. (syntax-rules (quote list)
  296. [(_ stack (quote '(expr* ...)))
  297. ;; Replace with call to (vector ...), because #()
  298. ;; syntax does not evaluate the things inside
  299. ;; parentheses. If there was a reference to a
  300. ;; variable in there, it would be seen as a symbol
  301. ;; only. The actual value would not be in there.
  302. (ck stack (quote (vector expr* ...)))]
  303. [(_ stack (quote (list expr* ...)))
  304. (ck stack (quote (vector expr* ...)))]
  305. ;; Fallback for better error message.
  306. [(_ stack (quote other* ...))
  307. (syntax-error
  308. "could not recognize list in expression"
  309. other* ...)]))
  310. ;; Example usage:
  311. ;; (ck ()
  312. ;; (c-list->vector ''(a b c)))
  313. ;; (ck ()
  314. ;; (c-list->vector '(list 1 2 3)))
  315. (define-syntax c-vector->list
  316. (syntax-rules (quote list)
  317. [(_ stack (quote #(expr* ...)))
  318. (ck stack (quote '(expr* ...)))]
  319. [(_ stack (quote (vector expr* ...)))
  320. (ck stack (quote (list expr* ...)))]
  321. ;; Fallback for better error message.
  322. [(_ stack (quote other* ...))
  323. (syntax-error
  324. "could not recognize vector in expression"
  325. other* ...)]))
  326. (define-syntax define-with-contract
  327. (syntax-rules ()
  328. [(_ function-name
  329. (require reqs* ...)
  330. (ensure ensu-expr* ...)
  331. (lambda (args* ...)
  332. lambda-body-expr* ...))
  333. (define function-name
  334. (lambda-with-contract
  335. function-name
  336. (require reqs* ...)
  337. (ensure ensu-expr* ...)
  338. (args* ...)
  339. lambda-body-expr* ...))]
  340. [(_ (function-name args* ...)
  341. (require reqs* ...)
  342. (ensure ensu-expr* ...)
  343. lambda-body-expr* ...)
  344. (define function-name
  345. (lambda-with-contract
  346. function-name
  347. (require reqs* ...)
  348. (ensure ensu-expr* ...)
  349. (args* ...)
  350. lambda-body-expr* ...))]))
  351. ;; Example usage:
  352. ;; (define-with-contract account-withdraw
  353. ;; (require (<= amount account-balance)
  354. ;; (>= amount 0))
  355. ;; (ensure (>= <?> 0)) ; depends on what the function returns
  356. ;; (λ (amount account-balance)
  357. ;; (- account-balance amount)))
  358. ;; (define-with-contract (account-withdraw
  359. ;; amount
  360. ;; account-balance
  361. ;; add-withdraw-amount
  362. ;; add-add-withdraw-amount)
  363. ;; (require (<= amount account-balance)
  364. ;; (>= amount 0))
  365. ;; (ensure (>= <> 0))
  366. ;; (- account-balance
  367. ;; (+ amount
  368. ;; add-withdraw-amount
  369. ;; add-add-withdraw-amount)))
  370. (define-syntax define*-with-contract
  371. (syntax-rules ()
  372. [(_ (function-name args* ...)
  373. (require reqs* ...)
  374. (ensure ensu-expr* ...)
  375. lambda-body-expr* ...)
  376. (define function-name
  377. (lambda*-with-contract
  378. function-name
  379. (require reqs* ...)
  380. (ensure ensu-expr* ...)
  381. (args* ...)
  382. lambda-body-expr* ...))]))
  383. ;; Example usage:
  384. ;; (define*-with-contract (account-withdraw
  385. ;; amount
  386. ;; account-balance
  387. ;; #:optional (add-withdraw-amount 0)
  388. ;; #:key (add-add-withdraw-amount 0))
  389. ;; (require (<= amount account-balance)
  390. ;; (>= amount 0))
  391. ;; (ensure (>= <> 0))
  392. ;; (- account-balance
  393. ;; (+ amount
  394. ;; add-withdraw-amount
  395. ;; add-add-withdraw-amount)))
  396. ;; `lambda-with-contract` is implemented in terms of
  397. ;; `lambda*-with-contract`.
  398. (define-syntax lambda-with-contract
  399. (syntax-rules ()
  400. ;; CASE 1: A case for when `lambda-with-contract` is
  401. ;; called without a function name. This should be the
  402. ;; case, when `lambda-with-contract` is used directly,
  403. ;; without the indirection through a
  404. ;; `define-with-contract` call.
  405. [(_ (require reqs* ...)
  406. (ensure ensu-expr* ...)
  407. (args* ...)
  408. lambda-body-expr* ...)
  409. (lambda*-with-contract (require reqs* ...)
  410. (ensure ensu-expr* ...)
  411. (args* ...)
  412. lambda-body-expr* ...)]
  413. ;; CASE 2: A case for a call with an additional
  414. ;; function name. `lambda-with-contract` should be
  415. ;; called with function name from a
  416. ;; define-with-contract call, but not with function
  417. ;; name, when used directly.
  418. [(_ function-name
  419. (require reqs* ...)
  420. (ensure ensu-expr* ...)
  421. (args* ...)
  422. lambda-body-expr* ...)
  423. (lambda*-with-contract function-name
  424. (require reqs* ...)
  425. (ensure ensu-expr* ...)
  426. (args* ...)
  427. lambda-body-expr* ...)]))
  428. ;; Example usage:
  429. ;; ((lambda-with-contract
  430. ;; (require (> a 0))
  431. ;; (ensure (> ((λ (res) (+ res 1)) <>) 0))
  432. ;; (a b)
  433. ;; (+ a b)) 1 -1)
  434. ;; `lambda*-with-contract` is the macro, which is the
  435. ;; entrypoint to calling CK macros. It is also used for
  436. ;; implementing `lambda-with-contract`,
  437. ;; `define-with-contract` and `define*-with-contract`.
  438. (define-syntax lambda*-with-contract
  439. (syntax-rules ()
  440. ;; CASE 1: A case for when `lambda-with-contract` is
  441. ;; called without a function name. This should be the
  442. ;; case, when `lambda-with-contract` is used directly,
  443. ;; without the indirection through a
  444. ;; `define-with-contract` call.
  445. [(_ (require reqs* ...)
  446. (ensure ensu-expr* ...)
  447. (args* ...)
  448. lambda-body-expr* ...)
  449. (lambda* (args* ...)
  450. ;; temporarily store result of the function
  451. (let ([result
  452. (cond
  453. ;; check pre-conditions (requirements)
  454. [(not (ck ()
  455. (c-and-raise
  456. 'unknown-origin
  457. (c-map '(c-replace-placeholder 'result)
  458. '(list reqs* ...)))))
  459. (raise-exception
  460. (make-exception
  461. (make-contract-violated-exception "contract violated"
  462. (list args* ...))))]
  463. ;; Run the body of the procedure, to
  464. ;; calculate the result.
  465. [else
  466. lambda-body-expr* ...])])
  467. (cond
  468. ;; Check post-conditions (ensures) using the
  469. ;; result.
  470. [(not (ck ()
  471. (c-and-raise
  472. 'unknown-origin
  473. (c-map '(c-replace-placeholder 'result)
  474. '(list ensu-expr* ...)))))
  475. (raise-exception
  476. (make-exception
  477. (make-contract-violated-exception "contract violated"
  478. (list args* ...))))]
  479. ;; Return result if post-conditions are true.
  480. [else result])))]
  481. ;; CASE 2: A case for a call with an additional
  482. ;; function name. `lambda-with-contract` should be
  483. ;; called with function name from a
  484. ;; define-with-contract call, but not with function
  485. ;; name, when used directly.
  486. [(_ function-name
  487. (require reqs* ...)
  488. (ensure ensu-expr* ...)
  489. (args* ...)
  490. lambda-body-expr* ...)
  491. (lambda* (args* ...)
  492. ;; temporarily store result of the function
  493. (let ([result
  494. (cond
  495. ;; check pre-conditions (requirements)
  496. [(not (ck ()
  497. (c-and-raise
  498. (quote function-name)
  499. (c-map '(c-replace-placeholder 'result)
  500. '(list reqs* ...)))))
  501. (raise-exception
  502. (make-exception
  503. (make-contract-violated-exception "contract violated"
  504. (list args* ...))
  505. (make-exception-with-origin (syntax->datum function-name))))]
  506. ;; Run the body of the procedure, to
  507. ;; calculate the result.
  508. [else
  509. lambda-body-expr* ...])])
  510. (cond
  511. ;; Check post-conditions (ensures) using the
  512. ;; result.
  513. [(not (ck ()
  514. (c-and-raise
  515. (quote function-name)
  516. (c-map '(c-replace-placeholder 'result)
  517. '(list ensu-expr* ...)))))
  518. (raise-exception
  519. (make-exception
  520. (make-contract-violated-exception "contract violated"
  521. (list args* ...))
  522. (make-exception-with-origin (syntax->datum function-name))))]
  523. ;; Return result if post-conditions are true.
  524. [else result])))]))
  525. ;; Example usage:
  526. ;; ((lambda*-with-contract
  527. ;; (require (> a 0))
  528. ;; (ensure (> ((λ (res) (+ res 1)) <>) 0))
  529. ;; (a #:optional (b 0) #:key (c 0))
  530. ;; (+ a b)) 1 0 #:c -1)
  531. ;; Lets make an example definition: Withdrawing an amount
  532. ;; of money from an account, returning the new account
  533. ;; balance (although not really mutating the account or
  534. ;; anything, really just a toy example).
  535. ;; Simple example:
  536. ;; (define-with-contract account-withdraw
  537. ;; (require (<= amount account-balance)
  538. ;; (>= amount 0))
  539. ;; (ensure (>= <?> 0)) ; depends on what the function returns
  540. ;; (λ (amount account-balance)
  541. ;; (- account-balance amount)))
  542. ;; Using the defined function just like any other
  543. ;; function.
  544. #;(display
  545. (simple-format
  546. #f "~a\n" (account-withdraw 10 20)))
  547. #;(display
  548. (simple-format
  549. #f "~a\n" (account-withdraw 30 20)))
  550. ;; More complex usage example:
  551. #;(define-with-contract account-withdraw
  552. (require (<= amount account-balance)
  553. (>= amount (vector-ref #(0) 0)))
  554. (ensure (>= ((lambda (a) (vector-ref #(<>) 0)) <>) 0)) ; depends on what the function returns
  555. (λ (amount account-balance)
  556. (- account-balance amount)))
  557. )