contracts.ruby
contracts.ruby copied to clipboard
Contracts on `self`
Let's say I have the following contracts
Contract Alkohol, Underaged => String
def sell(drink, person)
"Sorry, come back in #{18 - person.age} years."
end
Contract Drink, Person => String
def sell(drink, person)
"Here's your #{drink}."
end
It works perfectly well, but let's say I want to define the sell
method on Person
. Now it becomes not obvious how would I check if the person is allowed to have the drink.
I propose to have some sort of self constraints. In Haskell-like syntax it would be
Contract :: (Underaged self) => Drink -> String
Just like that (normal OO way):
class Underaged
include Contracts::Core
Contract Drink => String
def sell(drink)
"Sorry, come back in #{18 - person.age} years."
end
end
class Person
include Contracts::Core
Contract Drink => String
def sell(drink)
"Here is your drink."
end
end
@waterlink, now I'd need a factory to create a person depending on age.
You propose to use polymorphism, whereas the gem advertises to enable methods overloading, which I'm trying to achieve. I understand that normally OO languages don't dispatch based on self
, but why not?
Update: I guess it's not correct to say that OO languages don't dispatch based on self. If you have only one criteria for dispatching - a type - there is nothing to choose from. Quite easy to pick one from a set of one, right? We, however, have more ways to describe a contract, and that raises a need for the feature I propose.
Thing is you already have a Underaged
defined in your example.
I do have an Underaged
contract, but it's a constraint, not a class, which I'd use for polymorphic behaviour.
I'm willing to implement the feature providing we agree on the syntax and its usefulness.
Propose a syntax then :)
Other way: just use private method, that doesn't depend on self
. Like this:
class Person
Contract Drink => String
def sell(drink)
_sell(self, drink)
end
private
Contract Underaged, Drink => String
def _sell(person, drink)
"Sorry, come back in #{18 - person.age} years."
end
Contract Person, Drink => String
def _sell(person, drink)
"Here is your #{drink}!"
end
end
I'm afraid I don't have enough knowledge about which symbols can be used. Since you chose =>
to delimit arguments from the return value, I guess ->
is not possible. Maybe something like
Contract Alkohol > Underaged => String
Contract Alkohol | Underaged => String
Contract Alkohol >> Underaged => String
I hope you'll agree with me that the workaround is super-ugly.
How about:
Contract Contracts::Receiver[Underaged], Drink => String
?
You know better, but won't it cause difficulties to distinguish whether the contract is for the first parameter or for the receiver?
It will cause this problem in any case. On Jul 29, 2015 3:04 PM, "Volodymyr Shatsky" [email protected] wrote:
You know better, but won't it cause difficulties to distinguish whether the contract is for the first parameter or for the receiver?
— Reply to this email directly or view it on GitHub https://github.com/egonSchiele/contracts.ruby/issues/191#issuecomment-125943552 .
I mean you chose the same separator as for the parameters list. If the separator was different, it would be easier to tell the difference.
This does sound like a useful feature. Maybe we could do
Contract(Underaged) Drink => String
and
Contract(Person) Drink => String
If we have that code, it will build the foundation for future changes, where we can pass in arbitrary data to the Contract
function before specifying the contract itself.
Unfortunately, this is not a valid ruby. To make it valid we will have to send some message after calling Contract(x)
:
Contract(Underaged).with Drink => String
Another example would be to return a lambda and call it:
Contract(Underaged).(Drink => String)
# or
Contract(Underaged)[Drink => String]
# or
Contract[Underaged].(Drink => String)
# or
Contract[Underaged][Drink => String]
Some interesting syntax still can be used:
Contract[Underaged].>> Drink => String
Last one resembles Haskell-like version the most.
Yeah that's what I was thinking @waterlink...Contract(Underage)
would return a callable object. Then Contract(Underage) foo => bar
would be like calling that function with foo => bar
.
I'm also ok with the >>
syntax:
Contract Underage >> Foo => Bar
But I thought that might get confusing and start to look like symbol soup. @shockone once we agree on a syntax a PR for this would be great!
@egonSchiele This does not work, I have tried it before. Even if Contract(Underage)
returns callable object, ruby parser can't handle something(args) more, args
. And callable objects (such as method, proc, block and lambda) in ruby can be called only with:
-
#call
, ie:callable.call(args)
orcallable.(args)
-
#[]
, ie:callable[args]
@egonSchiele What about using a word instead of a symbol, ie: Contract(Underage).with Foo => Bar
? Or any other short word, which means the same: "Underage with args: Foo and returns Bar".
Yeah, I like with
too, but would we be able to keep existing syntax? Like Contract Num => Num
?
Not a problem. We need to switch on fact if there is a hash in arguments to Contract
or not. If yes: it is an old syntax and we just instantiate contract object right from the bet. If no: we return contract factory object, that have #with
or #>>
method.
Just trying to figure out something that looks nice:
Contract Underaged => String { Alkohol } # Not sure if the block will be passed to String or Contract, though.
ContractWithSelf Alkohol, Underaged => String
SelfContract Alkohol; Contract Underaged => String
Is this one possible?
Contract Alkohol => Underaged => String
It is if you wrap everything in parenthesis and use .>> instead of => ;-)
But on the other issue we agreed that we don't want to expose Ruby people to currying concept here.. On Jul 30, 2015 8:38 AM, "Volodymyr Shatsky" [email protected] wrote:
Is this one possible?
Contract Alkohol => Underaged => String
— Reply to this email directly or view it on GitHub https://github.com/egonSchiele/contracts.ruby/issues/191#issuecomment-126202370 .
Ok. I'm definitely up for Contract(Underaged).with Drink => String
It seems we're coming up with the same kind of syntax suggestions for 3 problems, this one plus:
Namespacing: https://github.com/egonSchiele/contracts.ruby/issues/157#issuecomment-111726998
Contract {[ num, maybe[num] => String ]}
Arguments: https://github.com/egonSchiele/contracts.ruby/issues/113#issuecomment-87329450
Contract(:a, :b) { [ArrayOf[a], Func[a => b] => ArrayOf[b]] }
The three issues should probably be thought of together to prevent clashes and enhance simplicity.
I wonder if the arguments idea could solve this too somehow.
Underaged = lambda { |person| person.age < 18 }
# Have magic argument symbol :this that is always self
Contract(:this) { [Self[Underaged[this]], Drink => String] }
# But seems redundant as Self contract is needed to identify
# the first in list as self contract rather than param contract anyway
Contract { [Self[Underaged], Drink => String] }
# Or make Contract able to take either:
# a symbol for named args behaviour, lambda for self validation:
Contract(Underaged) { [Drink => String] }
# You can use both then:
Contract(OldEnough, :drink) { [Alcohol[:drink] => Vomit[:drink]] }
# It makes sense to limit to only 1 self contract
# and any number of argument contracts.
# So maybe only the first can be a self contract as above,
# or use array for argument contracts:
Contract(OldEnough, [:drink, :bill]) { [Tab[:bill], Alcohol[:drink] => [Overdraft[:bill], Vomit[:drink]]] }
# Perhaps the array style could be optional
Or I may have completely missed what is going on, these things are a bit mind bending.
I think you are correct.
Though I wanted to see this happening in more incremental way. (of course bearing in mind that all 3 should be implemented eventually and not making silly mistakes that will prevent from fixing other 2).
I keep forgetting about invariants but it just occurred to me that they use a block syntax already, so my vote is on the bracebox syntax for any fancy functional features added.
ruby``` invariant(:day) { 1 <= day && day <= 31 }