7 ms·
Contracts For CoffeeScript
- deleted 15y ago[deleted]
- bilalhusain 15y agook, figured out that it takes care of the existing prototype operator (::) in lexing [1],[2]. It seems little annoying but guess I'll get the hang of it. [1] https://github.com/disnet/contracts.coffee/blob/master/src/lexer.coffee#L332 https://github.com/disnet/contracts.coffee/blob/master/src/l... [2] https://github.com/disnet/contracts.coffee/blob/master/src/lexer.coffee#L342 https://github.com/disnet/contracts.coffee/blob/master/src/l...
- mhansen 15y agoHow does it integrate with the `coffee` executable? Does coffeescript have a compiler plugin system, or are you patching the compiler? Will this cause me problems when I try to update my coffeescript compiler?
- disnet 15y agoIt's a fork of CoffeeScript so it replaces your "coffee" executable. If you don't want to overwrite your normal CoffeeScript compiler just run the contracts.coffee "coffee" executable from its own directory. I mention this briefly towards the end of the quick start section of the doc: http://disnetdev.com/contracts.coffee/#quickstart http://disnetdev.com/contracts.coffee/#quickstart
- mobileman 15y agoThis is just static types
- jashkenas 15y agoNot quite. isEven = (x) -> x % 2 is 0 f :: (!isOdd) -> !isEven f = (x) -> ...
- jvoorhis 15y agoNot hardly. The runtime can detect that a function invocation violates a post-condition only after all of that function's side-effects have been run.
- bitsai 15y agoI believe jashkenas' example defines both a pre-condition and a post-condition, specifying that f accepts only odd numbers and returns only even numbers ('!' here is the operator for new contract, rather than logical negation). If f is called with anything other than an odd number, an error will be thrown right away, before the body of the function is run.
- 6ren 15y agoThose '!'s are really confusing. From the example in the submission: isEven = (x) -> x % 2 is 0 isOdd = (x) -> x % 2 isnt 0 addEvens :: (!isEven) -> !isOdd addEvens = (x) -> x + 1 I am guessing that both '!'s define a new contract - and also guessing that coffeescript does not actually have a '!' operator for logical negation. So that addEvens simply takes an even number and returns an odd number. I find it confusing that the different syntax highlighting of the two '!' suggests (wrongly) that they have different meanings. Also, logical negation is something often used with isOdd/isEven functions.
- chc 15y agoCoffeeScript does allow ! for negation, but contracts have their own syntax and semantics separate from the rest of the language. It's a little confusing initially, but I suppose there aren't a lot of good operators left. There are a lot of similar convenient inconsistencies that people hardly notice once they sink in: • Indentation shifts mean different things depending on context (could mean we're doing an object literal, could mean we're defining a function, could mean we're entering a loop, etc.) • Looping through arrays uses "in" while looping through everything else uses "of" • Array and object literal syntax might create arrays and objects or define variables depending on which side of the equals sign you're reading
- chucknthem 15y agoNo, if you read the article, contracts are enforced at runtime!
- irrelative 15y agoI feel a little bit crazy because no one else has brought this up yet, but what's the point of declaring types if your errors don't get caught at compile time? If they're going to throw errors at runtime, how is that an improvement over not checking the types?
- disnet 15y agoWell, CoffeeScript/JavaScript doesn't have a static type system. And to add one would be...hard. But we would still like to express and enforce some invariants about our code. And we'd like to do it in a more elegant way than constantly writing "if(argument === ...) then ... else ..." Contracts also allow us to check invariants that might not result in a run-time exception but could lead to the code just being subtly wrong.
- irrelative 15y ago> Well, CoffeeScript/JavaScript doesn't have a static type system. And to add one would be...hard. Indeed it would be. That said, CoffeeScript is a compiler of sorts. > And we'd like to do it in a more elegant way than constantly writing "if(argument === ...) then ... else ..." This doesn't really seem to solve that problem. It just throws consistent errors if the types don't match. Your code would likely throw its own error if you didn't check the type manually. In my experience, native js errors are as easy or easier to track down that throwing errors yourself. > Contracts also allow us to check invariants that might not result in a run-time exception but could lead to the code just being subtly wrong. Ah, ok. That makes more sense. They're like python decorators, only single purpose.
- disnet 15y ago> Indeed it would be. That said, CoffeeScript is a compiler of sorts. The fact that we have a compiler doesn't give us all that much. Adding types to a dynamic language (where things can be monkey patched, evaled, and just generally mutated willy-nilly) is a hard problem (not impossible but definitely research [1 and 2 to start, but much more]). >> And we'd like to do it in a more elegant way than constantly writing "if(argument === ...) then ... else ..." > This doesn't really seem to solve that problem. It solves the problem of elegance :) > It just throws consistent errors if the types don't match. Your code would likely throw its own error if you didn't check the type manually. In my experience, native js errors are as easy or easier to track down that throwing errors yourself. Key word there is likely. Like I mentioned, there are some errors that don't result in a run-time error and we might like to check for those too. As a silly example, consider a binary search tree [3] that you want to keep balanced. If you get the balance wrong, there's no immediate fault (no TypeError/NPE). > Ah, ok. That makes more sense. They're like python decorators, only single purpose. I guess you could say that. If we had an expressive enough decorator-like system for CoffeeScript we could probably implement contracts with it. CS doesn't have one of course so path of least resistance is to add the contract language extension directly. [1] http://ecee.colorado.edu/~siek/blame-forall-2011.pdf http://ecee.colorado.edu/~siek/blame-forall-2011.pdf [2] http://dl.acm.org/citation.cfm?id=1863561 http://dl.acm.org/citation.cfm?id=1863561 [3] https://github.com/disnet/contracts.coffee/blob/master/test/webtest/test.coffee#L385 https://github.com/disnet/contracts.coffee/blob/master/test/...
- chucknthem 15y agoThis is great, it's like Google's compiler library[0] but with run-time checking! Coffee script is still very hard to debug, this should help avoid about 80% of the mistakes I normally make when coding. [0] http://code.google.com/closure/compiler/ http://code.google.com/closure/compiler/
- oinksoft 15y agoYou're thinking of the Closure Compiler and its annotations, not the Closure Library.
- chucknthem 15y agoAh you're right, edited
- swannodette 15y agoLibrary author covers the relevant history here, http://disnetdev.com/blog/2011/08/23/Contracts.coffee-Contracts-For-JavaScript-and-CoffeeScript/ http://disnetdev.com/blog/2011/08/23/Contracts.coffee-Contra... This Lambda The Ultimate discussion on Benjamin C. Pierce's (Types And Programming Languages) talk Types Considered Harmful is also of interest - the gist being that contracts are useful because sometimes static types are just too damn hard - http://lambda-the-ultimate.org/node/2828 http://lambda-the-ultimate.org/node/2828 EDIT: Might as well link to Brendan Eich's talk Proxies Are Awesome!, http://www.slideshare.net/BrendanEich/metaprog-5303821 http://www.slideshare.net/BrendanEich/metaprog-5303821. I haven't really been following the Proxy thing and they seem quite useful. It's nice to see them applied so powerfully here.
- d0m 15y agoI ♥ where it's going! "Nhaa, I won't use a statically typed language.. I prefer javascript with coffeescript syntax. But, I'll use contracts and tests to make it robust." Hands down, it's great and I'll definitely use it. Also +1 to be able to define your own type.
- riffraff 15y agovery cool project! Out of curiosity, what is the overhead of a program with-contracts vs one without them? It kind of looks like a JIT should be able to optimize away most of the guards. Also, I believe other DBC platforms had options to disable run time enforcing of contracts, is this possible somehow with CFCS ?
- disnet 15y agoI haven't done any benchmarks yet so not sure. In addition to the guards I think the use of Proxies will cause some slowdown. But you're right the JIT should be able to optimize most of those guards away (maybe even someday use the guards to help in their optimizing/tracing...just speculating here, I'm not a compiler/JIT guy). If you compile with "coffee -c" (compile, no contracts) instead of "coffee -cC" (compile, with contracts) you get pure JavaScript, no contracts and no runtime checks. Exactly what vanilla CoffeeScript would have given you.
- tlrobinson 15y agoNeat. I built runtime argument and return value type-checking into Objective-J awhile back (not enabled by default due to performance overhead). It was really easy since we already declare Objective-J argument and return value types, and we can easily intercept every Objective-J message invocation. https://github.com/cappuccino/cappuccino/blob/master/Objective-J/Debug.js#L141-245 https://github.com/cappuccino/cappuccino/blob/master/Objecti...
- clyfe 15y agoCan't this be implemented as a DSL? https://gist.github.com/1180516 https://gist.github.com/1180516 guardArgs = (def, args) -> i = 0 for arg, type of def throw new Error("bad type of #{arg}") unless typeof args[i] == type i++ typeEnsure = (def, f) -> -> guardArgs(def, arguments) f.apply @, arguments myFunc = typeEnsure {name: 'string', i: 'number'}, (name, i) -> console.log name, i myFunc("foo", 1) # ok myFunc("foo", "x") # Error: bad type of i I think this can be extended to custom types too.
- samth 15y agoFirst, I don't think what you show is a DSL -- it's just some functions. Rather, what Tim has implemented is a DSL. Second, you need proxies to handle things like functions and mutable arrays. Third, that there's a DSL for describing contracts is, I think, a large part of the point.
- Lamtard 15y agothere seems to be a pure js implementation of contracts around for a while: https://github.com/4z3/contract https://github.com/4z3/contract the CoffeeScript version is nice though :D
- AlexeyMK 15y agoHigher order functions: f :: ((Num) -> Bool, Num) -> Bool The type of f is not particularly obvious to me; instinctively, it could either be: 1. A function that takes (a function from Num to [Bool, Num]) and returns Bool, or 2. A function that takes (a function from Num to Bool) and a Num, and returns a Bool. Given enough time, I'm going to guess 2, but this makes CoffeeScript's destructing assignment, IE [a, b] = func() feel less 'first class'. Is this a serious ambiguity, or just me not having enough experience with precedence in CoffeeScript/functional languages?
- dherman 15y agoNo ambiguity; if you put brackets around it you get a tuple (really, an array of fixed elements), and without brackets, the comma is part of the function signature. If you wanted to express your interpretation #1 you'd write: f :: ((Num) -> [Bool, Num]) -> Bool Dave