contract.scm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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. and-raise
  23. ->ensure-expressions
  24. ensure-expression
  25. replace-result-placeholder
  26. ensure-and-wrap
  27. define-with-contract
  28. define*-with-contract
  29. lambda-with-contract
  30. lambda*-with-contract
  31. make-contract-violated-exception)
  32. (import
  33. (except (rnrs base) let-values)
  34. (only (guile)
  35. lambda* λ
  36. syntax->datum
  37. syntax-error
  38. display
  39. newline
  40. record-constructor)
  41. (ice-9 exceptions)
  42. (srfi srfi-9 gnu)
  43. (srfi srfi-1)))
  44. ;; Define `require` and `ensure`, so that they are available
  45. ;; as identifiers. This will cause syntax-rules to be aware
  46. ;; of them as identifiers. When the `define-with-contract`
  47. ;; macro is used and one writes an expression like (require
  48. ;; ...), the identifier `require` is used, instead of it
  49. ;; being pure syntax. The advantage is, that the identifier
  50. ;; can be renamed, when imported in another module. This
  51. ;; enables users to change how macro call looks. They might
  52. ;; want to change `ensure` to `make-sure` or
  53. ;; `post-condition`, or any other renaming. For example like
  54. ;; the following import:
  55. ;; (import
  56. ;; (rename (contract)
  57. ;; (require req)
  58. ;; (ensure ens)))
  59. ;; Note, that even though `syntax-rules` specifies
  60. ;; "literals", specifying <?> still works and still allows
  61. ;; for renaming in another module. Very useful!
  62. (define require 'require)
  63. (define ensure 'ensure)
  64. (define <?> '<?>)
  65. ;; Create a custom exception type, to make it clearer, that
  66. ;; a contract failed, and not only an arbitrary assertion.
  67. (define make-contract-violated-exception
  68. ;; record-constructor is a procedure, which will return the
  69. ;; constructor for any record.
  70. (record-constructor
  71. ;; Create an exception type, which is a record. This record has a
  72. ;; constructor, which we can name using define for example.
  73. (make-exception-type
  74. ;; name of the new exception type
  75. '&contract-violated
  76. ;; parent exception type
  77. &programming-error
  78. ;; list of values the constructor of the exception takes and their
  79. ;; names in the record
  80. '(message irritants))))
  81. ;; `and-raise` needs to be a macro, because its arguments
  82. ;; must not be immediately evaluated, otherwise we cannot
  83. ;; raise an exception containing the literal expression of
  84. ;; the failing check.
  85. ;; Usage examples:
  86. ;; (list (< amount account-balance) (>= amount 0))
  87. ;; (list (integer? result) (positive? result))
  88. ;; Ensured expressions, which are arguments to `and-raise`,
  89. ;; are already expanded, before `and-raise` is called. For
  90. ;; example:
  91. ;; (and-raise
  92. ;; (list
  93. ;; (ensure-expression result-identifier (integer? <>))
  94. ;; (ensure-expression result-identifier (positive? <>))))
  95. ;; The calls to `ensure-expression` get expanded before
  96. ;; `and-raise` is actually called.
  97. (define-syntax and-raise
  98. (syntax-rules (list)
  99. ;; `and-raise` takes a list of expressions to check as
  100. ;; an argument.
  101. [(_ (list
  102. (op args* ...)
  103. ensure-expr* ...))
  104. (cond
  105. ;; If the first checked expression is not true, raise
  106. ;; a contract violated exception.
  107. [(not (op args* ...))
  108. (raise-exception
  109. (make-contract-violated-exception
  110. "contract violated"
  111. (quote (op args* ...))))]
  112. ;; Othewise continue with checking the remaining
  113. ;; checked expressions.
  114. [else
  115. (and-raise (list ensure-expr* ...))])]
  116. [(_ (list #|nothing|#))
  117. #t]))
  118. ;; `->ensure-expressions` builds up a list of
  119. ;; `ensure-expression`s, which contains all the ensured
  120. ;; conditions.
  121. ;; (->ensure-expression
  122. ;; result-identifier
  123. ;; (> <?> 10)
  124. ;; (integer? <?>))
  125. ;; ->
  126. ;; (list
  127. ;; (ensure-expression result-identifier (> <?> 10))
  128. ;; (ensure-expression result-identifier (integer? <?>)))
  129. (define-syntax ->ensure-expressions
  130. (syntax-rules (list)
  131. ;; Final case / Base case: Until all expressions have
  132. ;; been transformed. Then make a list.
  133. [(_ result-identifier #|no elements left|#
  134. (list transformed-expr* ...))
  135. (list transformed-expr* ...)]
  136. ;; Intermediate case: Then match the
  137. ;; ->ensure-expressions call with `ensure-expression`s.
  138. [(_ result-identifier
  139. ;; TODO: `...` matches greedy so there is nothing
  140. ;; left for `expr` and `expr* ...` gets the wrong
  141. ;; value.
  142. (op *args ...) ...
  143. expr
  144. ;; Now there are already one or more transformed
  145. ;; expressions.
  146. (list transformed-expr* ...))
  147. (->ensure-expressions result-identifier
  148. (op *args ...) ...
  149. ;; Transform one more expression.
  150. (list
  151. (ensure-expression result-identifier expr)
  152. transformed-expr* ...))]
  153. ;; Initial case: Begin transforming the expression from
  154. ;; right to left, from the last element, to the first.
  155. [(_ result-identifier expr* ... expr)
  156. (->ensure-expressions result-identifier
  157. expr* ...
  158. ;; Introduce another artificial
  159. ;; level of nesting to avoid
  160. ;; "syntax-case: misplaced
  161. ;; ellipsis in form ..."
  162. ;; error. Multiple ellipsis
  163. ;; cannot be at the same level of
  164. ;; nesting.
  165. (list
  166. (ensure-expression result-identifier expr)))]))
  167. ;; `ensure-expression` translates a single ensure expression
  168. ;; to a condition, in which the placeholder is replaced by
  169. ;; the result identifier.
  170. (define-syntax ensure-expression
  171. (syntax-rules (<?>)
  172. ;; case for placeholder at the end
  173. [(_ result-identifier (op args-before* ... <?>))
  174. (op args-before* ... result-identifier)]
  175. ;; case for placeholder at the start
  176. [(_ result-identifier (op <?> args-after* ...))
  177. (op result-identifier args-after* ...)]
  178. ;; case for placeholder somewhere in between other
  179. ;; arguments
  180. [(_ result-identifier (op args* ...))
  181. (replace-result-placeholder result-identifier (op args* ...))]))
  182. ;; TODO: What if the <?> placeholder is not at the top-level
  183. ;; of a check? Might need a replace for arbitrarily nested
  184. ;; expressions!
  185. (define-syntax replace-result-placeholder
  186. (syntax-rules (<?>)
  187. "Iterates through the arguments of an expression, search for
  188. the placeholder and replace the placeholder with the
  189. result-identifier."
  190. ;; Solve the trivial case of the placeholder being in
  191. ;; first place.
  192. [(_ result-identifier (op <?> expr* ...))
  193. (op result-identifier expr* ...)]
  194. ;; If the placeholder is not in first place, then
  195. ;; introduce a list for arguments, which gets extended,
  196. ;; to contain all arguments, which are not the
  197. ;; placeholder.
  198. [(_ result-identifier (op expr expr* ...))
  199. (replace-result-placeholder result-identifier (op expr* ...) (list expr))]
  200. ;; If such a list of previous first arguments already
  201. ;; exists, we need to match it and extend it. If the
  202. ;; placeholder now is in first place, we build the final
  203. ;; expression.
  204. [(_ result-identifier (op <?> rest-args* ...) (list prev-args* ...))
  205. (op prev-args* ... result-identifier rest-args* ...)]
  206. ;; If the placeholder is still not in first place, then
  207. ;; put the current first argument in the list of
  208. ;; previous first arguments.
  209. [(_ result-identifier (op arg rest-args* ...) (list prev-args* ...))
  210. (replace-result-placeholder result-identifier
  211. (op rest-args* ...)
  212. (list arg prev-args* ...))]
  213. ;; Maybe there is no placeholder at all. Catch this case
  214. ;; as well.
  215. [(_ result-identifier (op) (list prev-args* ...))
  216. (op prev-args* ...)]))
  217. ;; `ensure-and-wrap` only takes care of wrapping the
  218. ;; resulting expression with an `and-raise`.
  219. (define-syntax ensure-and-wrap
  220. (syntax-rules ()
  221. [(_ result-identifier expr* ...)
  222. ;; Wrap with the and-raise and pass on to macro dealing
  223. ;; with building arguments to and-raise.
  224. (and-raise
  225. (->ensure-expressions result-identifier expr* ...))]))
  226. (define-syntax define-with-contract
  227. (syntax-rules ()
  228. [(_ function-name
  229. (require reqs* ...)
  230. (ensure ensu-expr* ...)
  231. (lambda (args* ...)
  232. lambda-body-expr* ...))
  233. (define function-name
  234. (lambda-with-contract
  235. function-name
  236. (require reqs* ...)
  237. (ensure ensu-expr* ...)
  238. (args* ...)
  239. lambda-body-expr* ...))]))
  240. (define-syntax define*-with-contract
  241. (syntax-rules ()
  242. [(_ (function-name args* ...)
  243. (require reqs* ...)
  244. (ensure ensu-expr* ...)
  245. lambda-body-expr* ...)
  246. (define function-name
  247. (lambda*-with-contract
  248. function-name
  249. (require reqs* ...)
  250. (ensure ensu-expr* ...)
  251. (args* ...)
  252. lambda-body-expr* ...))
  253. ;; `lambda-with-contract` is implemented in terms of
  254. ;; `lambda*-with-contract`.
  255. (define-syntax lambda-with-contract
  256. (syntax-rules ()
  257. ;; CASE 1: A case for when `lambda-with-contract` is
  258. ;; called without a function name. This should be the
  259. ;; case, when `lambda-with-contract` is used directly,
  260. ;; without the indirection through a
  261. ;; `define-with-contract` call.
  262. [(_ (require reqs* ...)
  263. (ensure ensu-expr* ...)
  264. (args* ...)
  265. lambda-body-expr* ...)
  266. (lambda*-with-contract (require reqs* ...)
  267. (ensure ensu-expr* ...)
  268. (args* ...)
  269. lambda-body-expr* ...)]
  270. ;; CASE 2: A case for a call with an additional function
  271. ;; name. `lambda-with-contract` should be called with
  272. ;; function name from a define-with-contract call, but
  273. ;; not with function name, when used directly.
  274. [(_ function-name
  275. (require reqs* ...)
  276. (ensure ensu-expr* ...)
  277. (args* ...)
  278. lambda-body-expr* ...)
  279. (lambda*-with-contract function-name
  280. (require reqs* ...)
  281. (ensure ensu-expr* ...)
  282. (args* ...)
  283. lambda-body-expr* ...)]))
  284. (define-syntax lambda*-with-contract
  285. (syntax-rules ()
  286. ;; CASE 1: A case for when `lambda-with-contract` is
  287. ;; called without a function name. This should be the
  288. ;; case, when `lambda-with-contract` is used directly,
  289. ;; without the indirection through a
  290. ;; `define-with-contract` call.
  291. [(_ (require reqs* ...)
  292. (ensure ensu-expr* ...)
  293. (args* ...)
  294. lambda-body-expr* ...)
  295. (lambda* (args* ...)
  296. ;; temporarily store result of the function
  297. (let ([result
  298. (cond
  299. ;; check pre-conditions (requirements)
  300. [(not (and-raise (list reqs* ...)))
  301. (raise-exception
  302. (make-exception
  303. (make-contract-violated-exception "contract violated"
  304. (list args* ...))))]
  305. ;; Run the body of the procedure, to
  306. ;; calculate the result.
  307. [else
  308. lambda-body-expr* ...])])
  309. (cond
  310. ;; Check post-conditions (ensures) using the
  311. ;; result.
  312. [(not (->ensure-expressions result ensu-expr* ...))
  313. (raise-exception
  314. (make-exception
  315. (make-contract-violated-exception "contract violated"
  316. (list args* ...))))]
  317. ;; Return result if post-conditions are true.
  318. [else result])))]
  319. ;; CASE 2: A case for a call with an additional function
  320. ;; name. `lambda-with-contract` should be called with
  321. ;; function name from a define-with-contract call, but
  322. ;; not with function name, when used directly.
  323. [(_ function-name
  324. (require reqs* ...)
  325. (ensure ensu-expr* ...)
  326. (args* ...)
  327. lambda-body-expr* ...)
  328. (lambda* (args* ...)
  329. ;; temporarily store result of the function
  330. (let ([result
  331. (cond
  332. ;; check pre-conditions (requirements)
  333. [(not (and-raise (list reqs* ...)))
  334. (raise-exception
  335. (make-exception
  336. (make-contract-violated-exception "contract violated"
  337. (list args* ...))
  338. (make-exception-with-origin (syntax->datum function-name))))]
  339. ;; Run the body of the procedure, to
  340. ;; calculate the result.
  341. [else
  342. lambda-body-expr* ...])])
  343. (cond
  344. ;; Check post-conditions (ensures) using the
  345. ;; result.
  346. [(not (->ensure-expressions result ensu-expr* ...))
  347. (raise-exception
  348. (make-exception
  349. (make-contract-violated-exception "contract violated"
  350. (list args* ...))
  351. (make-exception-with-origin (syntax->datum function-name))))]
  352. ;; Return result if post-conditions are true.
  353. [else result])))]))
  354. ;; Lets make an example definition: Withdrawing an amount of
  355. ;; money from an account, returning the new account balance
  356. ;; (although not really mutating the account or anything,
  357. ;; really just a toy example).
  358. (define-with-contract account-withdraw
  359. (require (<= amount account-balance)
  360. (>= amount 0))
  361. (ensure (>= <?> 0)) ; depends on what the function returns
  362. (λ (amount account-balance)
  363. (- account-balance amount)))
  364. ;; Using the defined function just like any other function.
  365. #;(display
  366. (simple-format
  367. #f "~a\n" (account-withdraw 10 20)))
  368. #;(display
  369. (simple-format
  370. #f "~a\n" (account-withdraw 30 20)))
  371. ;; TODO: What does the following code do?
  372. ;; (define-syntax require
  373. ;; (identifier-syntax
  374. ;; (syntax-error "'require' can only be used as part of a contract
  375. ;; construct")))