非容器中的容器和 Racket 中的 Hacking Contracts
Containees in nonContainers and Hacking Contracts in Racket
序言:我真的不知道自己在做什么。我是球拍和编程的新手。我学得慢。
我安装了 DrRacket,但我正在处理“racket/gui/base”。
我想将容器放入不是容器的对象中。
合同阻止我这样做。
我的第一个想法是想方设法破坏合同或修改合同。
- 这是一条富有成果的途径吗?
- 时间会不会太长?
我开始读这个:https://docs.racket-lang.org/guide/contract-boundaries.html
- 考虑到我想解决如下问题,这是在浪费时间吗?
例如,假设我想在菜单中放置一个按钮而不是菜单项。
在上述情况下,我该怎么做才能避免违反合同?
我可以将该解决方案概括为 4 个吗?还是特定于这些对象?
我可以在球拍中投掷物体来避免这个合同问题吗?
清楚合同的用途很重要。就像法律合同一样,Racket 的合同做两件事:
- 他们告诉合同的所有各方他们必须做什么才能遵守合同;
- 他们告诉合同的所有各方他们可以根据合同承担什么。
这两个方面都很重要,但第二个在这里更重要。例如考虑阶乘函数:
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
这一切似乎都很好:
> (factorial 10)
3628800
> (factorial 100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
除了一点都不好:
> (factorial 12.2)
然后有很长的停顿,然后在某个时候 Racket 内存不足。
嗯,那是因为当我写 factorial
时,我是在理解阶乘函数的定义域和范围的情况下写的:对于这个版本的阶乘函数,它的定义域是自然数(整数大于或等于 0)并且它的范围是大于或等于 1 的整数。并且该实现严重依赖于 属性 自然数:自然数 n 要么为零,要么从中减去 1次数够多,这个数字就是零。
当我调用 factorial
时,我用一个不在其域中的号码调用它,但它不知道,所以它根本无法终止.好吧,我们可以通过为 factorial
提供合约来解决这个问题。我将通过直接在函数上而不是在模块级别提供合同来做到这一点,因为这意味着我必须输入更少的内容:
(define/contract (factorial n)
(-> natural-number/c (integer-in 1 #f))
(if (= n 0)
1
(* n (factorial (- n 1)))))
现在
> (factorial 10)
3628800
> (factorial 12.2)
; factorial: contract violation
; expected: natural-number/c
; given: 12.2
; [...]
现在有一份合同可以保护 factorial
免受我的伤害。不幸的是,它也保护它不受自身的影响;每次它调用自己时,它都必须尽职地检查自己的合同。好吧,我们可以避免这种情况:
(define/contract (factorial n)
(-> natural-number/c (integer-in 1 #f))
(define (floop m r)
(if (= m 0)
r
(floop (- m 1) (* r m))))
(floop n 0))
在factorial
之内,合同的事情我可以假设,事情不需要执着去核对
除了我犯了一个错误:
> (factorial 10)
; factorial: broke its own contract
; promised: exact-positive-integer?
; produced: 0
所以现在 factorial
上的合约正在保护 me 免受错误实施的影响。我编写的使用 factorial
的代码可以安全地假设它的 return 值将是一个大于或等于 1 的整数。我当然可以解决这个问题:
(define/contract (factorial n)
(-> natural-number/c (integer-in 1 #f))
(define (floop m r)
(if (= m 0)
r
(floop (- m 1) (* r m))))
(floop n 1))
好的,所以这是一个很长的序言:
- 合同规定了双方的义务;
- 合同允许各方做出假设。
特别是,几乎可以肯定的是,容器 假设 放入其中的对象是容器,并且具有容器的行为,因为它们知道是一份使那成为现实的合同。
如果您以某种方式设法规避了该合同,那么将会发生的情况是容器仍会假定它是真实的,并且仍然会做出相同的假设。结果将是某种灾难:如果你幸运的话,你会得到错误,如果你不幸运,屏幕上只会出现垃圾或者程序会崩溃(或者,最糟糕的是,不会崩溃,但用废话填满它的记忆)。
所以答案很简单:合同的存在是有原因的,如果你想把东西放进容器里,你需要确保它是一个容器,而且它不是谎称是一个容器– 它需要正确地实际实现容器的行为,因为(几乎可以肯定)容器将依赖于该行为来工作。这就是合同的用途。
For example, let's say that I want to put a button in a menu instead of a menu item.
既然如此,答案就是你的追求不是一条有成果的大道。
菜单对象背后的想法是保存将被发送到要在屏幕上绘制的基础 GUI 的“内容”。各种 GUIS(macOS、Linux、Windows)不允许在菜单中绘制任意元素,因此 Racket 中的 GUI 层必须检查菜单是否只包含菜单项这就说得通了。因此,假设您设法绕过合同检查器并将一个按钮作为菜单项传递。最终该菜单项将从 Racket GUI 层传递到 OS,然后您将 运行 陷入错误 - 很可能是崩溃(程序因核心转储而停止)。
换句话说:合同已经到位,以确保菜单项是放置在菜单中有意义的东西。它可以防止您意外地将错误类型的对象存储在菜单中。
序言:我真的不知道自己在做什么。我是球拍和编程的新手。我学得慢。
我安装了 DrRacket,但我正在处理“racket/gui/base”。
我想将容器放入不是容器的对象中。
合同阻止我这样做。
我的第一个想法是想方设法破坏合同或修改合同。
- 这是一条富有成果的途径吗?
- 时间会不会太长?
我开始读这个:https://docs.racket-lang.org/guide/contract-boundaries.html
- 考虑到我想解决如下问题,这是在浪费时间吗?
例如,假设我想在菜单中放置一个按钮而不是菜单项。
在上述情况下,我该怎么做才能避免违反合同?
我可以将该解决方案概括为 4 个吗?还是特定于这些对象?
我可以在球拍中投掷物体来避免这个合同问题吗?
清楚合同的用途很重要。就像法律合同一样,Racket 的合同做两件事:
- 他们告诉合同的所有各方他们必须做什么才能遵守合同;
- 他们告诉合同的所有各方他们可以根据合同承担什么。
这两个方面都很重要,但第二个在这里更重要。例如考虑阶乘函数:
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
这一切似乎都很好:
> (factorial 10)
3628800
> (factorial 100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
除了一点都不好:
> (factorial 12.2)
然后有很长的停顿,然后在某个时候 Racket 内存不足。
嗯,那是因为当我写 factorial
时,我是在理解阶乘函数的定义域和范围的情况下写的:对于这个版本的阶乘函数,它的定义域是自然数(整数大于或等于 0)并且它的范围是大于或等于 1 的整数。并且该实现严重依赖于 属性 自然数:自然数 n 要么为零,要么从中减去 1次数够多,这个数字就是零。
当我调用 factorial
时,我用一个不在其域中的号码调用它,但它不知道,所以它根本无法终止.好吧,我们可以通过为 factorial
提供合约来解决这个问题。我将通过直接在函数上而不是在模块级别提供合同来做到这一点,因为这意味着我必须输入更少的内容:
(define/contract (factorial n)
(-> natural-number/c (integer-in 1 #f))
(if (= n 0)
1
(* n (factorial (- n 1)))))
现在
> (factorial 10)
3628800
> (factorial 12.2)
; factorial: contract violation
; expected: natural-number/c
; given: 12.2
; [...]
现在有一份合同可以保护 factorial
免受我的伤害。不幸的是,它也保护它不受自身的影响;每次它调用自己时,它都必须尽职地检查自己的合同。好吧,我们可以避免这种情况:
(define/contract (factorial n)
(-> natural-number/c (integer-in 1 #f))
(define (floop m r)
(if (= m 0)
r
(floop (- m 1) (* r m))))
(floop n 0))
在factorial
之内,合同的事情我可以假设,事情不需要执着去核对
除了我犯了一个错误:
> (factorial 10)
; factorial: broke its own contract
; promised: exact-positive-integer?
; produced: 0
所以现在 factorial
上的合约正在保护 me 免受错误实施的影响。我编写的使用 factorial
的代码可以安全地假设它的 return 值将是一个大于或等于 1 的整数。我当然可以解决这个问题:
(define/contract (factorial n)
(-> natural-number/c (integer-in 1 #f))
(define (floop m r)
(if (= m 0)
r
(floop (- m 1) (* r m))))
(floop n 1))
好的,所以这是一个很长的序言:
- 合同规定了双方的义务;
- 合同允许各方做出假设。
特别是,几乎可以肯定的是,容器 假设 放入其中的对象是容器,并且具有容器的行为,因为它们知道是一份使那成为现实的合同。
如果您以某种方式设法规避了该合同,那么将会发生的情况是容器仍会假定它是真实的,并且仍然会做出相同的假设。结果将是某种灾难:如果你幸运的话,你会得到错误,如果你不幸运,屏幕上只会出现垃圾或者程序会崩溃(或者,最糟糕的是,不会崩溃,但用废话填满它的记忆)。
所以答案很简单:合同的存在是有原因的,如果你想把东西放进容器里,你需要确保它是一个容器,而且它不是谎称是一个容器– 它需要正确地实际实现容器的行为,因为(几乎可以肯定)容器将依赖于该行为来工作。这就是合同的用途。
For example, let's say that I want to put a button in a menu instead of a menu item.
既然如此,答案就是你的追求不是一条有成果的大道。
菜单对象背后的想法是保存将被发送到要在屏幕上绘制的基础 GUI 的“内容”。各种 GUIS(macOS、Linux、Windows)不允许在菜单中绘制任意元素,因此 Racket 中的 GUI 层必须检查菜单是否只包含菜单项这就说得通了。因此,假设您设法绕过合同检查器并将一个按钮作为菜单项传递。最终该菜单项将从 Racket GUI 层传递到 OS,然后您将 运行 陷入错误 - 很可能是崩溃(程序因核心转储而停止)。
换句话说:合同已经到位,以确保菜单项是放置在菜单中有意义的东西。它可以防止您意外地将错误类型的对象存储在菜单中。