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