contract.scm 20 KB

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