1

我认为按合同设计是一种有用的技术,并希望将其应用到我的咖啡脚本代码中。

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

咖啡脚本有类似的东西吗?

我阅读了有关jsContracts的信息,但尚未对其进行测试。似乎是一个有用的库,但它缺乏 Ruby DSL 或contracts.coffee 语言扩展的优雅。

问题:

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

  • 我对contracts.coffee 的担忧是否合理?(如果不是,它似乎是完美的选择。)

4

2 回答 2

1

看到这个问题:有 JavaScript 的代码契约库吗?

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

对于另一种库来说,这似乎是一个有趣的想法,也许是一个用合约扩展功能的库......

于 2013-07-14T19:26:37.680 回答
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
    ...

显然里面有几个疣

  • 参数数组和参数数组可以有不同的长度(还没有检查)
  • 辅助函数应该抛出异常
  • 辅助函数应该像 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),因此它的可读性肯定更高

于 2013-07-26T15:33:54.427 回答