TUTORIAL.md in contracts-0.10.1 vs TUTORIAL.md in contracts-0.11.0
- old
+ new
@@ -15,11 +15,11 @@
## Basics
A simple example:
```ruby
-Contract Num, Num => Num
+Contract Contracts::Num, Contracts::Num => Contracts::Num
def add(a, b)
a + b
end
```
@@ -29,13 +29,13 @@
```ruby
require 'contracts'
class Math
- include Contracts
+ include Contracts::Core
- Contract Num, Num => Num
+ Contract Contracts::Num, Contracts::Num => Contracts::Num
def self.add(a, b)
a + b
end
end
@@ -86,10 +86,11 @@
* Collections
* [`ArrayOf`](http://www.rubydoc.info/gems/contracts/Contracts/ArrayOf) – checks that the argument is an array, and all elements pass the given contract, e.g. `ArrayOf[Num]`
* [`SetOf`](http://www.rubydoc.info/gems/contracts/Contracts/SetOf) – checks that the argument is a set, and all elements pass the given contract, e.g. `SetOf[Num]`
* [`HashOf`](http://www.rubydoc.info/gems/contracts/Contracts/HashOf) – checks that the argument is a hash, and all keys and values pass the given contract, e.g. `HashOf[Symbol => String]` or `HashOf[Symbol,String]`
* [`RangeOf`](http://www.rubydoc.info/gems/contracts/Contracts/RangeOf) – checks that the argument is a range whose elements (#first and #last) pass the given contract, e.g. `RangeOf[Date]`
+ * [`Enum`](http://www.rubydoc.info/gems/contracts/Contracts/Enum) – checks that the argument is part of a given collection of objects, e.g. `Enum[:a, :b, :c]`
* Keyword arguments
* [`KeywordArgs`](http://www.rubydoc.info/gems/contracts/Contracts/KeywordArgs) – checks that the argument is an options hash, and all required keyword arguments are present, and all values pass their respective contracts, e.g. `KeywordArgs[:number => Num, :description => Optional[String]]`
* [`Optional`](http://www.rubydoc.info/gems/contracts/Contracts/Optional) – checks that the keyword argument is either not present or pass the given contract, can not be used outside of `KeywordArgs` contract, e.g. `Optional[Num]`
@@ -102,10 +103,25 @@
* [`Eq`](http://www.rubydoc.info/gems/contracts/Contracts/Eq) – checks that the argument is precisely equal to the given value, e.g. `Eq[String]` matches the class `String` and not a string instance.
* [`Func`](http://www.rubydoc.info/gems/contracts/Contracts/Func) – specifies the contract for a proc/lambda e.g. `Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]`. See section "Contracts On Functions".
To see all the built-in contracts and their full descriptions, check out the [RDoc](http://rubydoc.info/gems/contracts/Contracts).
+It is recommended to use shortcut for referring builtin contracts:
+
+```ruby
+# define shortcut somewhere at the top level of your codebase:
+C = Contracts
+
+# and use it:
+Contract C::Maybe[C::Num], String => C::Num
+```
+
+Shortcut name should not be necessary `C`, can be anything that you are comfort
+with while typing and anything that does not conflict with libraries you use.
+
+All examples after this point assume you have chosen a shortcut as `C::`.
+
## More Examples
### Hello, World
```ruby
@@ -124,41 +140,41 @@
- an instance of a class that responds to the `valid?` method (more on this later)
### A Double Function
```ruby
-Contract Or[Fixnum, Float] => Or[Fixnum, Float]
+Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float]
def double(x)
2 * x
end
```
Sometimes you want to be able to choose between a few contracts. `Or` takes a variable number of contracts and checks the argument against all of them. If it passes for any of the contracts, then the `Or` contract passes.
This introduces some new syntax. One of the valid values for a contract is an instance of a class that responds to the `valid?` method. This is what `Or[Fixnum, Float]` is. The longer way to write it would have been:
```ruby
-Contract Or.new(Fixnum, Float) => Or.new(Fixnum, Float)
+Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float)
```
All the built-in contracts have overridden the square brackets (`[]`) to give the same functionality. So you could write
```ruby
-Contract Or[Fixnum, Float] => Or[Fixnum, Float]
+Contract C::Or[Fixnum, Float] => C::Or[Fixnum, Float]
```
or
```ruby
-Contract Or.new(Fixnum, Float) => Or.new(Fixnum, Float)
+Contract C::Or.new(Fixnum, Float) => C::Or.new(Fixnum, Float)
```
whichever you prefer. They both mean the same thing here: make a new instance of `Or` with `Fixnum` and `Float`. Use that instance to validate the argument.
### A Product Function
```ruby
-Contract ArrayOf[Num] => Num
+Contract C::ArrayOf[C::Num] => C::Num
def product(vals)
total = 1
vals.each do |val|
total *= val
end
@@ -177,11 +193,11 @@
```
### Another Product Function
```ruby
-Contract Args[Num] => Num
+Contract C::Args[C::Num] => C::Num
def product(*vals)
total = 1
vals.each do |val|
total *= val
end
@@ -201,11 +217,11 @@
If an array is one of the arguments and you know how many elements it's going to have, you can put a contract on it:
```ruby
# a function that takes an array of two elements...a person's age and a person's name.
-Contract [Num, String] => nil
+Contract [C::Num, String] => nil
def person(data)
p data
end
```
@@ -215,11 +231,11 @@
Here's a contract that requires a Hash. We can put contracts on each of the keys:
```ruby
# note the parentheses around the hash; without those you would get a syntax error
-Contract ({ :age => Num, :name => String }) => nil
+Contract ({ :age => C::Num, :name => String }) => nil
def person(data)
p data
end
```
@@ -239,11 +255,11 @@
even though we don't specify a type for `:foo`.
Peruse this contract on the keys and values of a Hash.
```ruby
-Contract HashOf[Symbol, Num] => Num
+Contract C::HashOf[Symbol, C::Num] => C::Num
def give_largest_value(hsh)
hsh.values.max
end
```
Which you use like so:
@@ -267,18 +283,18 @@
```
You can of course put `Hash` contract on it:
```ruby
-Contract String, { :port => Num, :user => String, :password => String } => Connection
+Contract String, { :port => C::Num, :user => String, :password => String } => Connection
def connect(host, port:, user:, password:)
```
But this will not quite work if you want to have a default values:
```ruby
-Contract String, { :port => Num, :user => String, :password => String } => Connection
+Contract String, { :port => C::Num, :user => String, :password => String } => Connection
def connect(host, port: 5000, user:, password:)
# ...
end
# No value is passed for port
@@ -294,17 +310,17 @@
Value guarded in: Object::connect
With Contract: String, Hash => Connection
At: (irb):12
```
-This can be fixed with contract `{ :port => Maybe[Num], ... }`, but that will
+This can be fixed with contract `{ :port => C::Maybe[C::Num], ... }`, but that will
allow `nil` to be passed in, which is not the original intent.
So that is where `KeywordArgs` and `Optional` contracts jump in:
```ruby
-Contract String, KeywordArgs[ :port => Optional[Num], :user => String, :password => String ] => Connection
+Contract String, C::KeywordArgs[ :port => C::Optional[C::Num], :user => String, :password => String ] => Connection
def connect(host, port: 5000, user:, password:)
```
It looks just like the hash contract, but wrapped in `KeywordArgs` contract. Notice the usage of `Optional` contract - this way you specify that `:port` argument is optional. And it will not fail, when you omit this argument, but it will fail when you pass in `nil`.
@@ -317,11 +333,11 @@
```
`map` takes an array, and a function. Suppose you want to add a contract to this function. You could try this:
```ruby
-Contract ArrayOf[Any], Proc => ArrayOf[Any]
+Contract C::ArrayOf[C::Any], Proc => C::ArrayOf[C::Any]
def map(arr, func)
```
This says that the second argument should be a `Proc`. You can call the function like so:
@@ -332,11 +348,11 @@
But suppose you want to have a contract on the Proc too! Suppose you want to make sure that the Proc returns a number. Use the `Func` contract. `Func` takes a contract as its argument, and uses that contract on the function that you pass in.
Here's a `map` function that requires an array of numbers, and a function that takes a number and returns a number:
```ruby
-Contract ArrayOf[Num], Func[Num => Num] => ArrayOf[Num]
+Contract C::ArrayOf[C::Num], C::Func[C::Num => C::Num] => C::ArrayOf[C::Num]
def map(arr, func)
ret = []
arr.each do |x|
ret << func[x]
end
@@ -360,32 +376,32 @@
```
NOTE: This is not valid:
```ruby
-Contract ArrayOf[Num], Func => ArrayOf[Num]
+Contract C::ArrayOf[C::Num], C::Func => C::ArrayOf[C::Num]
def map(arr, &func)
```
Here I am using `Func` without specifying a contract, like `Func[Num => Num]`. That's not a legal contract. If you just want to validate that the second argument is a proc, use `Proc`.
### Returning Multiple Values
Treat the return value as an array. For example, here's a function that returns two numbers:
```ruby
-Contract Num => [Num, Num]
+Contract C::Num => [C::Num, C::Num]
def mult(x)
return x, x+1
end
```
## Synonyms For Contracts
If you use a contract a lot, it's a good idea to give it a meaningful synonym that tells the reader more about what your code returns. For example, suppose you have many functions that return a `Hash` or `nil`. If a `Hash` is returned, it contains information about a person. Your contact might look like this:
```ruby
-Contract String => Or[Hash, nil]
+Contract String => C::Or[Hash, nil]
def some_func(str)
```
You can make your contract more meaningful with a synonym:
@@ -413,11 +429,11 @@
The first two don't need any extra work to define: you can just use any constant or class name in your contract and it should just work. Here are examples for the rest:
### A Proc
```ruby
-Contract lambda { |x| x.is_a? Numeric } => Num
+Contract lambda { |x| x.is_a? Numeric } => C::Num
def double(x)
```
The lambda takes one parameter: the argument that is getting passed to the function. It checks to see if it's a `Numeric`. If it is, it returns true. Otherwise it returns false.
It's not good practice to write a lambda right in your contract...if you find yourself doing it often, write it as a class instead:
@@ -458,20 +474,20 @@
The `Or` contract takes a sequence of contracts, and passes if any of them pass. It uses `Contract.valid?` to validate the value against the contracts.
This class inherits from `CallableClass`, which allows us to use `[]` when using the class:
```ruby
-Contract Or[Fixnum, Float] => Num
+Contract C::Or[Fixnum, Float] => C::Num
def double(x)
2 * x
end
```
Without `CallableClass`, we would have to use `.new` instead:
```ruby
-Contract Or.new(Fixnum, Float) => Num
+Contract C::Or.new(Fixnum, Float) => C::Num
def double(x)
# etc
```
You can use `CallableClass` in your own contracts to make them callable using `[]`.
@@ -541,14 +557,15 @@
Possible validator overrides:
- `override_validator(MyCustomContract)` - allows to add some special behaviour for custom contracts,
- `override_validator(Proc)` - e.g. `lambda { true }`,
-- `override_validator(Array)` - e.g. `[Num, String]`,
-- `override_validator(Hash)` - e.g. `{ :a => Num, :b => String }`,
-- `override_validator(Contracts::Args)` - e.g. `Args[Num]`,
-- `override_validator(Contracts::Func)` - e.g. `Func[Num => Num]`,
+- `override_validator(Array)` - e.g. `[C::Num, String]`,
+- `override_validator(Hash)` - e.g. `{ :a => C::Num, :b => String }`,
+- `override_validator(Range)` - e.g. `(1..10)`,
+- `override_validator(Contracts::Args)` - e.g. `C::Args[C::Num]`,
+- `override_validator(Contracts::Func)` - e.g. `C::Func[C::Num => C::Num]`,
- `override_validator(:valid)` - allows to override how contracts that respond to `:valid?` are handled,
- `override_validator(:class)` - allows to override how class/module contract constants are handled,
- `override_validator(:default)` - otherwise, raw value contracts.
Default validators can be found here: [lib/contracts/validators.rb](https://github.com/egonSchiele/contracts.ruby/blob/master/lib/contracts/validators.rb).
@@ -562,11 +579,11 @@
You can use contracts for method overloading! This is commonly called "pattern matching" in functional programming languages.
For example, here's a factorial function without method overloading:
```ruby
-Contract Num => Num
+Contract C::Num => C::Num
def fact x
if x == 1
x
else
x * fact(x - 1)
@@ -580,11 +597,11 @@
Contract 1 => 1
def fact x
x
end
-Contract Num => Num
+Contract C::Num => C::Num
def fact x
x * fact(x - 1)
end
```
@@ -606,22 +623,22 @@
```
Note that the second `get_ticket` contract above could have been simplified to:
```ruby
-Contract Num => Ticket
+Contract C::Num => Ticket
```
This is because the first contract eliminated the possibility of `age` being less than 12. However, the simpler contract is less explicit; you may want to "spell out" the age condition for clarity, especially if the method is overloaded with many contracts.
## Contracts in modules
Usage is the same as contracts in classes:
```ruby
module M
- include Contracts
+ include Contracts::Core
Contract String => String
def self.parse
# do some hard parsing
end
@@ -636,16 +653,16 @@
A simple example:
```ruby
class MyBirthday < Struct.new(:day, :month)
- include Contracts
+ include Contracts::Core
include Contracts::Invariants
invariant(:day) { 1 <= day && day <= 31 }
invariant(:month) { 1 <= month && month <= 12 }
- Contract None => Fixnum
+ Contract C::None => Fixnum
def silly_next_day!
self.day += 1
end
end