contracts.coffee
contracts.coffee copied to clipboard
How to put a contracts on a hashmap's keys and values
How to put a contracts on a hashmap's keys and values, for example custom key contract and object values.
Right now it's achivable via a custom function contracts which:
- assets it's an object
- checks all keys via object.keys()
- checks all values in a loop using a custom object contract
Is there/will be a syntax for this? How about contract parameters? Maybe
foo :: HashMap TMyKey, TMyObjValue
Or
foo :: { @TMyKey, TMyObjValue }
Don't have syntax for this right now. Closest I think is something like this:
class HashMap
# non-implementation of a HashMap :)
get: (key) -> @[key]
put: (key, val) -> @[key] = val
HashMapC = (keyContract, valContract) ->
?{
get: ((keyContract) -> valContract)
put: (keyContract, valContract) -> Any
}
StrHashMap = HashMapC Str, Str
f :: (StrHashMap) -> Any
f = (map) ->
map.get 4
m = new HashMap
m.put 4, "the number four"
f m # contract violation...
Does this match with what you've been trying?
We definitely need some nicer syntax for stuff like this. I'm leaning towards the parameterisation approach (foo :: MyContract T, V) over a specific new syntax just for HashMaps.
I think the problem I was just going to open up a ticket for is somewhat related. I am extending backbone views and models. The preferred way of doing this is
View = Backbone.View.extend
method: (args) ->
So all the methods are defined in a JSON style. It isn't obvious to me how to add a contract. I can define the method externally, but that leads to some duplication. Perhaps I should add it on after extending?
I think you should be able to do something like:
View :: {
method: (C) -> C
}
View = Backbone.View.extend
method: (args) -> ...
yeah, I know I should be able to do that but then my type definition would be far away from the function location. Backbone views can easily take up 100 lines of code.
Ah, I see. Yeah, there's probably not a great way to do it at the moment.
I'd like to add the ability to define inline contracts with object literals. So one could write:
obj =
(Str) -> Str
meth1: (x) -> ...
(Num) -> Num
meth2: (x) -> ...
Num
x: 42
This is related to adding support for classes since the grammar here is pretty similar. Hopefully I'll have some time soon to crank this out :)
But this still wouldn't entirely solve your backbone example. If we added inline contracts or tried this today:
spec :: { method : (C) -> C }
spec = { method: (args) -> ... }
View = Backbone.View.extend spec
so the object we pass View.extend is wrapped in a contract. Depending on exactly how extend works you might not get what you expect because there's a conceptual mismatch going on. We really want to put a contract on View not the bit of data (spec) that is used to generate View.
Not sure the best way to address this.
method :: (C) -> C
method = (args) -> ...
# create a new copy and manually add methods
View = Backbone.View.extend {}
mkView = () ->
v = new View
v.method = method
# call to the outside function
View = Backbone.View.extend
method : (args...) -> method(args...)
These seem like the only approach where I get a contract next to my function and place the contract on the correct object. Second seems preferable.
This seems to work well. A small amount of manageable boilerplate. I need the context, though, so it looks like:
View = Backbone.View.extend
method : (args...) -> method(this, args...)
I could apply the context (and maybe I will in the future), but I actually kind of like passing it around.
The style I am using now is
addContractMethods = (singleton) ->
singleton.method :: (Str) -> Any
singleton.method = (str) ->
mkClass contractMethods