没有语言扩展的coffeescript中的合同设计支持

时间:2013-07-03 22:12:25

标签: coffeescript dsl design-by-contract

我认为按合同设计是一种有用的技术,并希望将其应用于我的coffeescript代码。

contracts.coffee,看起来非常好(如Haskell):

id :: (Num) -> Num
id = (x) -> x

下行是它是一种语言扩展。我在犹豫,因为我害怕用工具支持来解决问题。 (我太保守了吗?)

虽然它看起来很棒,但我现在更喜欢图书馆解决方案。对于Ruby,我最近发现了contracts.ruby,它具有相同的优雅,但它的优势在于它只是简单的Ruby:

require 'contracts'
include Contracts

Contract Num => Num
def id(x) ; x ; end

coffeescript有类似的东西吗?

我读过关于jsContracts的内容,但尚未对其进行测试。似乎是一个有用的库,但它缺乏Ruby DSL或contract.coffee语言扩展的优雅。

问题:

  • 是否有一个语法上很好的coffeescript(或Javascript)合同设计库,它可以无缝集成到常用的工具链中?

  • 我对contract.coffee的担忧是否合理? (如果没有,它似乎是最合适的。)

2 个答案:

答案 0 :(得分:1)

请参阅此问题:Is there a code contract library for JavaScript?

你可以使用https://npmjs.org/package/contracts-js,这是一种后端,如果你愿意的话,可以使用contract.coffee。缺点是它需要代理,前端JavaScript不能很好地支持代理。

对于不同类型的库来说,似乎是一个有趣的想法,也许是一个用契约扩展函数的库...

答案 1 :(得分:1)

在CoffeeScript中定义自己的DSL非常容易。如果你想创建一个类型检查框架,你可以创建一个像这样的类

class A
     @def foo:
          params: [isNum, isBool,isNotNull]
          body: (x, y, z) -> console.log "foo: #{x}, #{y}, #{z}"

@def应该创建一个名为“foo”的方法,并通过调用“params”数组中给出的函数来根据它们的位置检查它们的参数。

让我们先写一些测试

a = new A()
a.foo 3, true, "foo"
a.foo "string", true, "foo"
a.foo 3, "string", "foo"
a.foo 3, false, null

然后我们需要一些辅助方法来进行实际的参数检查

isNum = (p)-> console.log "p isnt a number its => #{p}" if typeof p != "number"
isBool = (p)-> console.log "p isnt a bool its => #{p}" if typeof p != "boolean"
isNotNull = (p)-> console.log "p is null" if p == null or p == undefined

可能他们应该做一些更有用的事情(比如抛出异常)。对于我们的例子,它们就足够了。

现在我们的类A调用一个尚未定义的类方法。我们将为此创建一个基类,并从中继承我们的类A

class ContractBase
    @def: (fndef)->
        #get the name of the "function definition" object
        #should be the only key
        name = Object.keys(fndef)[0]
        #get the real function body
        fn = fndef[name]["body"]
        #get the params
        params = fndef[name]["params"]

        # create a closure and assign it to the prototype
        @::[name] = ->
            #check the parameters first
            for value, index in arguments
                #get the check at the index of the argument
                check = params[index]
                #and run it if available
                check(value) if check
            #call the real function body
            fn arguments...

#and finally change A to extend from ContractBase
class A extends ContractBase
    ...

显然它中有一些瑕疵

  • arguments数组和参数数组可以有不同的长度(目前还没有检查)
  • 辅助函数应抛出异常
  • 辅助函数应该像isNotNull(isNum)
  • 一样可以组合
  • 您正在规避定义方法的“正常”方式,因此您生成的javascript代码将难以阅读和调试 - 可能不会

这是一次性完整运行的代码

class ContractBase
    @def: (fndef)->
        name = Object.keys(fndef)[0]
        fn = fndef[name]["body"]
        params = fndef[name]["params"]
        @::[name] = ->
            for value, index in arguments
                check = params[index]
                check(value) if check
            fn arguments...

isNum = (p)-> console.log "p isnt a number its => #{p}" if typeof p != "number"
isBool = (p)-> console.log "p isnt a bool its => #{p}" if typeof p != "boolean"
isNotNull = (p)-> console.log "p is null" if p == null or p == undefined

class A extends ContractBase
    @def foo:
        params: [isNum, isBool,isNotNull]
        body: (x, y, z) -> console.log "foo: #{x}, #{y}, #{z}"

a = new A()
a.foo 3, true, "foo"
a.foo "string", true, "foo"
a.foo 3, "string", "foo"
a.foo 3, false, null

它大约是相应Javascript代码长度的1/3,当然更可读,因为它更好地传达意图(imo)