rtype-native 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,568 +1,568 @@
1
- # Rtype: ruby with type
2
- [![Gem Version](https://badge.fury.io/rb/rtype.svg)](https://badge.fury.io/rb/rtype)
3
- [![Build Status](https://travis-ci.org/sputnikgugja/rtype.svg?branch=master)](https://travis-ci.org/sputnikgugja/rtype)
4
- [![Coverage Status](https://coveralls.io/repos/github/sputnikgugja/rtype/badge.svg?branch=master)](https://coveralls.io/github/sputnikgugja/rtype?branch=master)
5
-
6
- You can do the type checking in Ruby with this gem!
7
-
8
- ```ruby
9
- require 'rtype'
10
-
11
- class Test
12
- rtype [:to_i, Numeric] => Numeric
13
- def sum(a, b)
14
- a.to_i + b
15
- end
16
-
17
- rtype {state: Boolean} => Boolean
18
- def self.invert(state:)
19
- !state
20
- end
21
- end
22
-
23
- Test.new.sum(123, "asd")
24
- # (Rtype::ArgumentTypeError) for 2nd argument:
25
- # Expected "asd" to be a Numeric
26
-
27
- Test::invert(state: 0)
28
- # (Rtype::ArgumentTypeError) for 'state' argument:
29
- # Expected 0 to be a Boolean
30
- ```
31
-
32
- ## Requirements
33
- - Ruby >= 2.1
34
- - MRI
35
- - If C native extension is used, otherwise it is not required
36
- - JRuby
37
- - If Java extension is used, otherwise it is not required
38
-
39
- ## Features
40
- - Provide type checking for arguments and return
41
- - Support type checking for [keyword argument](#keyword-argument)
42
- - [Type checking for array elements](#array)
43
- - [Type checking for hash elements](#hash)
44
- - [Duck typing](#duck-typing)
45
- - Custom type behavior
46
-
47
- ## Installation
48
- Run `gem install rtype` or add `gem 'rtype'` to your `Gemfile`
49
-
50
- And add to your `.rb` source file:
51
- ```ruby
52
- require 'rtype'
53
- ```
54
-
55
- ### Native extension
56
- Rtype itself is pure-ruby gem. but you can make it more faster by using native extension.
57
-
58
- #### Native extension for MRI
59
- Just run
60
- ```ruby
61
- gem install rtype-native
62
- ```
63
- or add to your `Gemfile`:
64
- ```ruby
65
- gem 'rtype-native'
66
- ```
67
- then, Rtype use it. (Do not `require 'rtype-native'`)
68
-
69
- #### Java extension for JRuby
70
- Just run
71
- ```ruby
72
- gem install rtype-java
73
- ```
74
- or add to your `Gemfile`:
75
- ```ruby
76
- gem 'rtype-java'
77
- ```
78
- then, Rtype use it. (Do not `require 'rtype-java'`)
79
-
80
- ## Usage
81
-
82
- ### Supported Type Behaviors
83
- - `Module`
84
- - Value must be an instance of this module/class or one of its superclasses
85
- - `Any` : An alias for `BasicObject` (means Any Object)
86
- - `Boolean` : `true` or `false`
87
- - `Symbol`
88
- - Value must have(respond to) a method with this name
89
- - `Regexp`
90
- - Value must match this regexp pattern
91
- - `Range`
92
- - Value must be included in this range
93
- - `Array` (tuple)
94
- - Value must be an array
95
- - Each of value's elements must be valid
96
- - Value's length must be equal to the array's length
97
- - Of course, nested array works
98
- - Example: [Array](#array)
99
- - This can be used as a tuple
100
- - `Hash`
101
- - Value must be an hash
102
- - Each of value’s elements must be valid
103
- - Value's key list must be equal to the hash's key list
104
- - **String** key is **different** from **symbol** key
105
- - vs Keyword arguments
106
- - `[{}]` is **not** hash type argument. it is keyword argument because its position is last
107
- - `[{}, {}]` is empty hash type argument (first) and one empty keyword argument (second)
108
- - `[{}, {}, {}]` is two empty hash type argument (first, second) and empty keyword argument (last)
109
- - `{}` is keyword argument. non-keyword arguments must be in array.
110
- - Of course, nested hash works
111
- - Example: [Hash](#hash)
112
- - `Proc`
113
- - Value must return a truthy value for this proc
114
- - `true`
115
- - Value must be **truthy**
116
- - `false`
117
- - Value must be **falsy**
118
- - `nil`
119
- - Only available for **return type**. void return type in other languages
120
- - Special Behaviors
121
- - `Rtype::and(*types)` : Ensure value is valid for all the types
122
- - `Rtype::and(*types)`
123
- - `Rtype::Behavior::And[*types]`
124
- - `include Rtype::Behavior; And[...]`
125
- - `obj.and(*others)` (core extension)
126
-
127
- - `Rtype::or(*types)` : Ensure value is valid for at least one of the types
128
- - `Rtype::or(*types)`
129
- - `Rtype::Behavior::Or[*types]`
130
- - `include Rtype::Behavior; Or[...]`
131
- - `obj.or(*others)` (core extension)
132
-
133
- - `Rtype::xor(*types)` : Ensure value is valid for only one of the types
134
- - `Rtype::xor(*types)`
135
- - `Rtype::Behavior::Xor[*types]`
136
- - `include Rtype::Behavior; Xor[...]`
137
- - `obj.xor(*others)` (core extension)
138
-
139
- - `Rtype::not(*types)` : Ensure value is not valid for all the types
140
- - `Rtype::not(*types)`
141
- - `Rtype::Behavior::Not[*types]`
142
- - `include Rtype::Behavior; Not[...]`
143
- - `obj.not` (core extension)
144
-
145
- - `Rtype::nilable(type)` : Ensure value can be nil
146
- - `Rtype::nilable(type)`
147
- - `Rtype::Behavior::Nilable[type]`
148
- - `include Rtype::Behavior; Nilable[...]`
149
- - `obj.nilable` (core extension)
150
- - `obj.or_nil` (core extension)
151
-
152
- - You can create custom behavior by extending `Rtype::Behavior::Base`
153
-
154
- ### Examples
155
-
156
- #### Basic
157
- ```ruby
158
- require 'rtype'
159
-
160
- class Example
161
- rtype [Integer] => nil
162
- def test(i)
163
- end
164
-
165
- rtype [Any] => nil
166
- def any_type_arg(arg)
167
- end
168
-
169
- rtype [] => Integer
170
- def return_type_test
171
- "not integer"
172
- end
173
- end
174
-
175
- e = Example.new
176
- e.test("not integer")
177
- # (Rtype::ArgumentTypeError) for 1st argument:
178
- # Expected "not integer" to be a Integer
179
-
180
- e.any_type_arg("Any argument!") # Works
181
-
182
- e.return_type_test
183
- # (Rtype::ReturnTypeError) for return:
184
- # Expected "not integer" to be a Integer
185
- ```
186
-
187
- #### Keyword argument
188
- ```ruby
189
- require 'rtype'
190
-
191
- class Example
192
- rtype {name: String} => Any
193
- def say_your_name(name:)
194
- puts "My name is #{name}"
195
- end
196
-
197
- # Mixing positional arguments and keyword arguments
198
- rtype [String, {age: Integer}] => Any
199
- def name_and_age(name, age:)
200
- puts "Name: #{name}, Age: #{age}"
201
- end
202
- end
203
-
204
- Example.new.say_your_name(name: "Babo") # My name is Babo
205
- Example.new.name_and_age("Bamboo", age: 100) # Name: Bamboo, Age: 100
206
-
207
- Example.new.say_your_name(name: 12345)
208
- # (Rtype::ArgumentTypeError) for 'name' argument:
209
- # Expected 12345 to be a String
210
- ```
211
-
212
- #### Duck typing
213
- ```ruby
214
- require 'rtype'
215
-
216
- class Duck
217
- rtype [:to_i] => Any
218
- def says(i)
219
- puts "duck:" + " quack"*i.to_i
220
- end
221
- end
222
-
223
- Duck.new.says("2") # duck: quack quack
224
- ```
225
-
226
- #### Array
227
- This can be used as a tuple.
228
-
229
- ```ruby
230
- rtype :func, [[Numeric, Numeric]] => Any
231
- def func(arr)
232
- puts "Your location is (#{arr[0]}, #{arr[1]}). I will look for you. I will find you"
233
- end
234
-
235
- func [1, "str"]
236
- # (Rtype::ArgumentTypeError) for 1st argument:
237
- # Expected [1, "str"] to be an array with 2 elements:
238
- # - [0] index : Expected 1 to be a Numeric
239
- # - [1] index : Expected "str" to be a Numeric
240
-
241
- func [1, 2, 3]
242
- # (Rtype::ArgumentTypeError) for 1st argument:
243
- # Expected [1, 2, 3] to be an array with 2 elements:
244
- # - [0] index : Expected 1 to be a Numeric
245
- # - [1] index : Expected 2 to be a Numeric
246
-
247
- func [1]
248
- # (Rtype::ArgumentTypeError) for 1st argument:
249
- # Expected [1] to be an array with 2 elements:
250
- # - [0] index : Expected 1 to be a Numeric
251
- # - [1] index : Expected nil to be a Numeric
252
-
253
- func [1, 2] # Your location is (1, 2). I will look for you. I will find you
254
- ```
255
-
256
- #### Hash
257
- ```ruby
258
- # last hash element is keyword arguments
259
- rtype :func, [{msg: String}, {}] => Any
260
- def func(hash)
261
- puts hash[:msg]
262
- end
263
-
264
- # last hash is keyword arguments
265
- func({}, {})
266
- # (Rtype::ArgumentTypeError) for 1st argument:
267
- # Expected {} to be an hash with 1 elements:
268
- # - msg : Expected nil to be a String
269
-
270
- func({msg: 123}, {})
271
- # (Rtype::ArgumentTypeError) for 1st argument:
272
- # Expected {:msg=>123} to be an hash with 1 elements:
273
- # - msg : Expected 123 to be a String
274
-
275
- func({msg: "hello", key: 'value'}, {})
276
- # (Rtype::ArgumentTypeError) for 1st argument:
277
- # Expected {:msg=>"hello", :key=>"value"} to be an hash with 1 elements:
278
- # - msg : Expected "hello" to be a String
279
-
280
- func({"msg" => "hello hash"}, {})
281
- # (Rtype::ArgumentTypeError) for 1st argument:
282
- # Expected {"msg"=>"hello hash"} to be an hash with 1 elements:
283
- # - msg : Expected nil to be a String
284
-
285
- func({msg: "hello hash"}, {}) # hello hash
286
- ```
287
-
288
- #### rtype with attr_accessor
289
- `rtype_accessor` : call attr_accessor and make it typed method
290
-
291
- You can use `rtype_accessor_self` for static accessor.
292
-
293
- ```ruby
294
- require 'rtype'
295
-
296
- class Example
297
- rtype_accessor :value, String
298
-
299
- def initialize
300
- @value = 456
301
- end
302
- end
303
-
304
- Example.new.value = 123
305
- # (Rtype::ArgumentTypeError) for 1st argument:
306
- # Expected 123 to be a String
307
-
308
- Example.new.value
309
- # (Rtype::ReturnTypeError) for return:
310
- # Expected 456 to be a String
311
- ```
312
-
313
- #### Combined type
314
- ```ruby
315
- ### TEST 1 ###
316
- require 'rtype'
317
-
318
- class Example
319
- rtype [String.and(:func)] => Any
320
- # also works:
321
- # rtype [Rtype::and(String, :func)] => Any
322
- def and_test(arg)
323
- end
324
- end
325
-
326
- Example.new.and_test("A string")
327
- # (Rtype::ArgumentTypeError) for 1st argument:
328
- # Expected "A string" to be a String
329
- # AND Expected "A string" to respond to :func
330
- ```
331
- ```ruby
332
- ### TEST 2 ###
333
- # ... require rtype and define Example the same as above ...
334
-
335
- class String
336
- def func; end
337
- end
338
-
339
- Example.new.and_test("A string") # Works!
340
- ```
341
-
342
- #### Combined duck type
343
- Application of duck typing and combined type
344
-
345
- ```ruby
346
- require 'rtype'
347
-
348
- module Game
349
- ENEMY = [
350
- :name,
351
- :level
352
- ]
353
-
354
- class Player < Entity
355
- include Rtype::Behavior
356
-
357
- rtype [And[*ENEMY]] => Any
358
- def attacks(enemy)
359
- "Player attacks '#{enemy.name}' (level #{enemy.level})!"
360
- end
361
- end
362
-
363
- class Slime < Entity
364
- def name
365
- "Powerful Slime"
366
- end
367
-
368
- def level
369
- 123
370
- end
371
- end
372
- end
373
-
374
- Game::Player.new.attacks Game::Slime.new
375
- # Player attacks 'Powerful Slime' (level 123)!
376
- ```
377
-
378
- #### Position of `rtype` && (Specify method name || annotation mode) && (Symbol || String)
379
- ```ruby
380
- require 'rtype'
381
-
382
- class Example
383
- # Recommended. Annotation mode (no method name required)
384
- rtype [Integer, String] => String
385
- def hello_world(i, str)
386
- puts "Hello? #{i} #{st
387
- end
388
-
389
- # Works (specifying method name)
390
- rtype :hello_world, [Integer, String] => String
391
- def hello_world(i, str)
392
- puts "Hello? #{i} #{st
393
- end
394
-
395
- # Works
396
- def hello_world_two(i, str)
397
- puts "Hello? #{i} #{str}"
398
- end
399
- rtype :hello_world_two, [Integer, String] => String
400
-
401
- # Also works (String will be converted to Symbol)
402
- rtype 'hello_world_three', [Integer, String] => String
403
- def hello_world_three(i, str)
404
- puts "Hello? #{i} #{str}"
405
- end
406
-
407
- # Don't works. `rtype` works for next method
408
- def hello_world_four(i, str)
409
- puts "Hello? #{i} #{str}"
410
- end
411
- rtype [Integer, String] => String
412
- end
413
- ```
414
-
415
- #### Outside of module (root)
416
- Outside of module, annotation mode don't works. You must specify method name.
417
-
418
- ```ruby
419
- rtype :say, [String] => Any
420
- def say(message)
421
- puts message
422
- end
423
-
424
- Test.new.say "Hello" # Hello
425
-
426
- rtype [String] => Any
427
- # (ArgumentError) Annotation mode not working out of module
428
- ```
429
-
430
- #### Static(singleton) method
431
- rtype annotation mode works both instance and class method
432
-
433
- ```ruby
434
- require 'rtype'
435
-
436
- class Example
437
- rtype [:to_i] => Any
438
- def self.say_ya(i)
439
- puts "say" + " ya"*i.to_i
440
- end
441
- end
442
-
443
- Example::say_ya(3) #say ya ya ya
444
- ```
445
-
446
- however, if you specify method name, you must use `rtype_self` instead of `rtype`
447
-
448
- ```ruby
449
- require 'rtype'
450
-
451
- class Example
452
- rtype_self :say_ya, [:to_i] => Any
453
- def self.say_ya(i)
454
- puts "say" + " ya"*i.to_i
455
- end
456
- end
457
-
458
- Example::say_ya(3) #say ya ya ya
459
- ```
460
-
461
- #### Check type information
462
- This is just the 'information'
463
-
464
- Any change of this doesn't affect type checking
465
-
466
- ```ruby
467
- require 'rtype'
468
-
469
- class Example
470
- rtype [:to_i] => Any
471
- def test(i)
472
- end
473
- end
474
-
475
- Example.new.method(:test).type_info
476
- # => [:to_i] => Any
477
- Example.new.method(:test).argument_type
478
- # => [:to_i]
479
- Example.new.method(:test).return_type
480
- # => Any
481
- ```
482
-
483
- ## Documentation
484
- [RubyDoc.info](http://www.rubydoc.info/gems/rtype)
485
-
486
- ## Benchmarks
487
- Result of `rake benchmark` ([source](https://github.com/sputnikgugja/rtype/tree/master/benchmark/benchmark.rb))
488
-
489
- ### MRI
490
- ```
491
- Rtype with C native extension
492
- Ruby version: 2.1.7
493
- Ruby engine: ruby
494
- Ruby description: ruby 2.1.7p400 (2015-08-18 revision 51632) [x64-mingw32]
495
- Rtype version: 0.3.0
496
- Rubype version: 0.3.1
497
- Sig version: 1.0.1
498
- Contracts version: 0.13.0
499
- Typecheck version: 0.1.2
500
- Warming up --------------------------------------
501
- pure 85.328k i/100ms
502
- rtype 25.665k i/100ms
503
- rubype 21.414k i/100ms
504
- sig 8.921k i/100ms
505
- contracts 4.638k i/100ms
506
- typecheck 1.110k i/100ms
507
- Calculating -------------------------------------
508
- pure 3.282M (± 2.7%) i/s - 16.468M
509
- rtype 339.065k (± 2.6%) i/s - 1.720M
510
- rubype 266.893k (± 5.9%) i/s - 1.349M
511
- sig 99.952k (± 2.1%) i/s - 499.576k
512
- contracts 49.693k (± 1.5%) i/s - 250.452k
513
- typecheck 11.356k (± 1.6%) i/s - 57.720k
514
-
515
- Comparison:
516
- pure: 3282431.9 i/s
517
- rtype: 339064.9 i/s - 9.68x slower
518
- rubype: 266892.9 i/s - 12.30x slower
519
- sig: 99952.2 i/s - 32.84x slower
520
- contracts: 49693.0 i/s - 66.05x slower
521
- typecheck: 11355.9 i/s - 289.05x slower
522
- ```
523
-
524
- ### JRuby
525
- Without Rubype that doesn't support JRuby
526
-
527
- ```
528
- Rtype with Java extension
529
- Ruby version: 2.2.3
530
- Ruby engine: jruby
531
- Ruby description: jruby 9.0.5.0 (2.2.3) 2016-01-26 7bee00d Java HotSpot(TM) 64-Bit Server VM 25.60-b23 on 1.8.0_60-b27 +jit [Windows 10-amd64]
532
- Rtype version: 0.3.0
533
- Sig version: 1.0.1
534
- Contracts version: 0.13.0
535
- Typecheck version: 0.1.2
536
- Warming up --------------------------------------
537
- pure 9.994k i/100ms
538
- rtype 6.181k i/100ms
539
- sig 4.041k i/100ms
540
- contracts 951.000 i/100ms
541
- typecheck 970.000 i/100ms
542
- Calculating -------------------------------------
543
- pure 7.128M (?±35.6%) i/s - 30.831M
544
- rtype 121.556k (?± 6.2%) i/s - 605.738k
545
- sig 72.187k (?± 6.4%) i/s - 359.649k
546
- contracts 24.984k (?± 3.9%) i/s - 125.532k
547
- typecheck 12.041k (?± 9.5%) i/s - 60.140k
548
-
549
- Comparison:
550
- pure: 7128373.0 i/s
551
- rtype: 121555.8 i/s - 58.64x slower
552
- sig: 72186.8 i/s - 98.75x slower
553
- contracts: 24984.5 i/s - 285.31x slower
554
- typecheck: 12041.0 i/s - 592.01x slower
555
- ```
556
-
557
- ## Rubype, Sig
558
- Rtype is influenced by [Rubype](https://github.com/gogotanaka/Rubype) and [Sig](https://github.com/janlelis/sig).
559
-
560
- If you don't like Rtype, You can use other type checking gem such as Contracts, Rubype, Rtc, Typecheck, Sig.
561
-
562
- ## Author
563
- Sputnik Gugja (sputnikgugja@gmail.com)
564
-
565
- ## License
566
- MIT license (@ Sputnik Gugja)
567
-
1
+ # Rtype: ruby with type
2
+ [![Gem Version](https://badge.fury.io/rb/rtype.svg)](https://badge.fury.io/rb/rtype)
3
+ [![Build Status](https://travis-ci.org/sputnikgugja/rtype.svg?branch=master)](https://travis-ci.org/sputnikgugja/rtype)
4
+ [![Coverage Status](https://coveralls.io/repos/github/sputnikgugja/rtype/badge.svg?branch=master)](https://coveralls.io/github/sputnikgugja/rtype?branch=master)
5
+
6
+ You can do the type checking in Ruby with this gem!
7
+
8
+ ```ruby
9
+ require 'rtype'
10
+
11
+ class Test
12
+ rtype [:to_i, Numeric] => Numeric
13
+ def sum(a, b)
14
+ a.to_i + b
15
+ end
16
+
17
+ rtype {state: Boolean} => Boolean
18
+ def self.invert(state:)
19
+ !state
20
+ end
21
+ end
22
+
23
+ Test.new.sum(123, "asd")
24
+ # (Rtype::ArgumentTypeError) for 2nd argument:
25
+ # Expected "asd" to be a Numeric
26
+
27
+ Test::invert(state: 0)
28
+ # (Rtype::ArgumentTypeError) for 'state' argument:
29
+ # Expected 0 to be a Boolean
30
+ ```
31
+
32
+ ## Requirements
33
+ - Ruby >= 2.1
34
+ - MRI
35
+ - If C native extension is used, otherwise it is not required
36
+ - JRuby
37
+ - If Java extension is used, otherwise it is not required
38
+
39
+ ## Features
40
+ - Provide type checking for arguments and return
41
+ - Support type checking for [keyword argument](#keyword-argument)
42
+ - [Type checking for array elements](#array)
43
+ - [Type checking for hash elements](#hash)
44
+ - [Duck typing](#duck-typing)
45
+ - Custom type behavior
46
+
47
+ ## Installation
48
+ Run `gem install rtype` or add `gem 'rtype'` to your `Gemfile`
49
+
50
+ And add to your `.rb` source file:
51
+ ```ruby
52
+ require 'rtype'
53
+ ```
54
+
55
+ ### Native extension
56
+ Rtype itself is pure-ruby gem. but you can make it more faster by using native extension.
57
+
58
+ #### Native extension for MRI
59
+ Just run
60
+ ```ruby
61
+ gem install rtype-native
62
+ ```
63
+ or add to your `Gemfile`:
64
+ ```ruby
65
+ gem 'rtype-native'
66
+ ```
67
+ then, Rtype use it. (Do not `require 'rtype-native'`)
68
+
69
+ #### Java extension for JRuby
70
+ Just run
71
+ ```ruby
72
+ gem install rtype-java
73
+ ```
74
+ or add to your `Gemfile`:
75
+ ```ruby
76
+ gem 'rtype-java'
77
+ ```
78
+ then, Rtype use it. (Do not `require 'rtype-java'`)
79
+
80
+ ## Usage
81
+
82
+ ### Supported Type Behaviors
83
+ - `Module`
84
+ - Value must be an instance of this module/class or one of its superclasses
85
+ - `Any` : An alias for `BasicObject` (means Any Object)
86
+ - `Boolean` : `true` or `false`
87
+ - `Symbol`
88
+ - Value must have(respond to) a method with this name
89
+ - `Regexp`
90
+ - Value must match this regexp pattern
91
+ - `Range`
92
+ - Value must be included in this range
93
+ - `Array` (tuple)
94
+ - Value must be an array
95
+ - Each of value's elements must be valid
96
+ - Value's length must be equal to the array's length
97
+ - Of course, nested array works
98
+ - Example: [Array](#array)
99
+ - This can be used as a tuple
100
+ - `Hash`
101
+ - Value must be an hash
102
+ - Each of value’s elements must be valid
103
+ - Value's key list must be equal to the hash's key list
104
+ - **String** key is **different** from **symbol** key
105
+ - vs Keyword arguments
106
+ - `[{}]` is **not** hash type argument. it is keyword argument because its position is last
107
+ - `[{}, {}]` is empty hash type argument (first) and one empty keyword argument (second)
108
+ - `[{}, {}, {}]` is two empty hash type argument (first, second) and empty keyword argument (last)
109
+ - `{}` is keyword argument. non-keyword arguments must be in array.
110
+ - Of course, nested hash works
111
+ - Example: [Hash](#hash)
112
+ - `Proc`
113
+ - Value must return a truthy value for this proc
114
+ - `true`
115
+ - Value must be **truthy**
116
+ - `false`
117
+ - Value must be **falsy**
118
+ - `nil`
119
+ - Only available for **return type**. void return type in other languages
120
+ - Special Behaviors
121
+ - `Rtype::and(*types)` : Ensure value is valid for all the types
122
+ - `Rtype::and(*types)`
123
+ - `Rtype::Behavior::And[*types]`
124
+ - `include Rtype::Behavior; And[...]`
125
+ - `obj.and(*others)` (core extension)
126
+
127
+ - `Rtype::or(*types)` : Ensure value is valid for at least one of the types
128
+ - `Rtype::or(*types)`
129
+ - `Rtype::Behavior::Or[*types]`
130
+ - `include Rtype::Behavior; Or[...]`
131
+ - `obj.or(*others)` (core extension)
132
+
133
+ - `Rtype::xor(*types)` : Ensure value is valid for only one of the types
134
+ - `Rtype::xor(*types)`
135
+ - `Rtype::Behavior::Xor[*types]`
136
+ - `include Rtype::Behavior; Xor[...]`
137
+ - `obj.xor(*others)` (core extension)
138
+
139
+ - `Rtype::not(*types)` : Ensure value is not valid for all the types
140
+ - `Rtype::not(*types)`
141
+ - `Rtype::Behavior::Not[*types]`
142
+ - `include Rtype::Behavior; Not[...]`
143
+ - `obj.not` (core extension)
144
+
145
+ - `Rtype::nilable(type)` : Ensure value can be nil
146
+ - `Rtype::nilable(type)`
147
+ - `Rtype::Behavior::Nilable[type]`
148
+ - `include Rtype::Behavior; Nilable[...]`
149
+ - `obj.nilable` (core extension)
150
+ - `obj.or_nil` (core extension)
151
+
152
+ - You can create custom behavior by extending `Rtype::Behavior::Base`
153
+
154
+ ### Examples
155
+
156
+ #### Basic
157
+ ```ruby
158
+ require 'rtype'
159
+
160
+ class Example
161
+ rtype [Integer] => nil
162
+ def test(i)
163
+ end
164
+
165
+ rtype [Any] => nil
166
+ def any_type_arg(arg)
167
+ end
168
+
169
+ rtype [] => Integer
170
+ def return_type_test
171
+ "not integer"
172
+ end
173
+ end
174
+
175
+ e = Example.new
176
+ e.test("not integer")
177
+ # (Rtype::ArgumentTypeError) for 1st argument:
178
+ # Expected "not integer" to be a Integer
179
+
180
+ e.any_type_arg("Any argument!") # Works
181
+
182
+ e.return_type_test
183
+ # (Rtype::ReturnTypeError) for return:
184
+ # Expected "not integer" to be a Integer
185
+ ```
186
+
187
+ #### Keyword argument
188
+ ```ruby
189
+ require 'rtype'
190
+
191
+ class Example
192
+ rtype {name: String} => Any
193
+ def say_your_name(name:)
194
+ puts "My name is #{name}"
195
+ end
196
+
197
+ # Mixing positional arguments and keyword arguments
198
+ rtype [String, {age: Integer}] => Any
199
+ def name_and_age(name, age:)
200
+ puts "Name: #{name}, Age: #{age}"
201
+ end
202
+ end
203
+
204
+ Example.new.say_your_name(name: "Babo") # My name is Babo
205
+ Example.new.name_and_age("Bamboo", age: 100) # Name: Bamboo, Age: 100
206
+
207
+ Example.new.say_your_name(name: 12345)
208
+ # (Rtype::ArgumentTypeError) for 'name' argument:
209
+ # Expected 12345 to be a String
210
+ ```
211
+
212
+ #### Duck typing
213
+ ```ruby
214
+ require 'rtype'
215
+
216
+ class Duck
217
+ rtype [:to_i] => Any
218
+ def says(i)
219
+ puts "duck:" + " quack"*i.to_i
220
+ end
221
+ end
222
+
223
+ Duck.new.says("2") # duck: quack quack
224
+ ```
225
+
226
+ #### Array
227
+ This can be used as a tuple.
228
+
229
+ ```ruby
230
+ rtype :func, [[Numeric, Numeric]] => Any
231
+ def func(arr)
232
+ puts "Your location is (#{arr[0]}, #{arr[1]}). I will look for you. I will find you"
233
+ end
234
+
235
+ func [1, "str"]
236
+ # (Rtype::ArgumentTypeError) for 1st argument:
237
+ # Expected [1, "str"] to be an array with 2 elements:
238
+ # - [0] index : Expected 1 to be a Numeric
239
+ # - [1] index : Expected "str" to be a Numeric
240
+
241
+ func [1, 2, 3]
242
+ # (Rtype::ArgumentTypeError) for 1st argument:
243
+ # Expected [1, 2, 3] to be an array with 2 elements:
244
+ # - [0] index : Expected 1 to be a Numeric
245
+ # - [1] index : Expected 2 to be a Numeric
246
+
247
+ func [1]
248
+ # (Rtype::ArgumentTypeError) for 1st argument:
249
+ # Expected [1] to be an array with 2 elements:
250
+ # - [0] index : Expected 1 to be a Numeric
251
+ # - [1] index : Expected nil to be a Numeric
252
+
253
+ func [1, 2] # Your location is (1, 2). I will look for you. I will find you
254
+ ```
255
+
256
+ #### Hash
257
+ ```ruby
258
+ # last hash element is keyword arguments
259
+ rtype :func, [{msg: String}, {}] => Any
260
+ def func(hash)
261
+ puts hash[:msg]
262
+ end
263
+
264
+ # last hash is keyword arguments
265
+ func({}, {})
266
+ # (Rtype::ArgumentTypeError) for 1st argument:
267
+ # Expected {} to be an hash with 1 elements:
268
+ # - msg : Expected nil to be a String
269
+
270
+ func({msg: 123}, {})
271
+ # (Rtype::ArgumentTypeError) for 1st argument:
272
+ # Expected {:msg=>123} to be an hash with 1 elements:
273
+ # - msg : Expected 123 to be a String
274
+
275
+ func({msg: "hello", key: 'value'}, {})
276
+ # (Rtype::ArgumentTypeError) for 1st argument:
277
+ # Expected {:msg=>"hello", :key=>"value"} to be an hash with 1 elements:
278
+ # - msg : Expected "hello" to be a String
279
+
280
+ func({"msg" => "hello hash"}, {})
281
+ # (Rtype::ArgumentTypeError) for 1st argument:
282
+ # Expected {"msg"=>"hello hash"} to be an hash with 1 elements:
283
+ # - msg : Expected nil to be a String
284
+
285
+ func({msg: "hello hash"}, {}) # hello hash
286
+ ```
287
+
288
+ #### rtype with attr_accessor
289
+ `rtype_accessor` : calls `attr_accessor` if the accessor method(getter/setter) is not defined, and makes it typed method
290
+
291
+ You can use `rtype_accessor_self` for static accessor.
292
+
293
+ ```ruby
294
+ require 'rtype'
295
+
296
+ class Example
297
+ rtype_accessor :value, String
298
+
299
+ def initialize
300
+ @value = 456
301
+ end
302
+ end
303
+
304
+ Example.new.value = 123
305
+ # (Rtype::ArgumentTypeError) for 1st argument:
306
+ # Expected 123 to be a String
307
+
308
+ Example.new.value
309
+ # (Rtype::ReturnTypeError) for return:
310
+ # Expected 456 to be a String
311
+ ```
312
+
313
+ #### Combined type
314
+ ```ruby
315
+ ### TEST 1 ###
316
+ require 'rtype'
317
+
318
+ class Example
319
+ rtype [String.and(:func)] => Any
320
+ # also works:
321
+ # rtype [Rtype::and(String, :func)] => Any
322
+ def and_test(arg)
323
+ end
324
+ end
325
+
326
+ Example.new.and_test("A string")
327
+ # (Rtype::ArgumentTypeError) for 1st argument:
328
+ # Expected "A string" to be a String
329
+ # AND Expected "A string" to respond to :func
330
+ ```
331
+ ```ruby
332
+ ### TEST 2 ###
333
+ # ... require rtype and define Example the same as above ...
334
+
335
+ class String
336
+ def func; end
337
+ end
338
+
339
+ Example.new.and_test("A string") # Works!
340
+ ```
341
+
342
+ #### Combined duck type
343
+ Application of duck typing and combined type
344
+
345
+ ```ruby
346
+ require 'rtype'
347
+
348
+ module Game
349
+ ENEMY = [
350
+ :name,
351
+ :level
352
+ ]
353
+
354
+ class Player < Entity
355
+ include Rtype::Behavior
356
+
357
+ rtype [And[*ENEMY]] => Any
358
+ def attacks(enemy)
359
+ "Player attacks '#{enemy.name}' (level #{enemy.level})!"
360
+ end
361
+ end
362
+
363
+ class Slime < Entity
364
+ def name
365
+ "Powerful Slime"
366
+ end
367
+
368
+ def level
369
+ 123
370
+ end
371
+ end
372
+ end
373
+
374
+ Game::Player.new.attacks Game::Slime.new
375
+ # Player attacks 'Powerful Slime' (level 123)!
376
+ ```
377
+
378
+ #### Position of `rtype` && (Specify method name || annotation mode) && (Symbol || String)
379
+ ```ruby
380
+ require 'rtype'
381
+
382
+ class Example
383
+ # Recommended. Annotation mode (no method name required)
384
+ rtype [Integer, String] => String
385
+ def hello_world(i, str)
386
+ puts "Hello? #{i} #{st
387
+ end
388
+
389
+ # Works (specifying method name)
390
+ rtype :hello_world, [Integer, String] => String
391
+ def hello_world(i, str)
392
+ puts "Hello? #{i} #{st
393
+ end
394
+
395
+ # Works
396
+ def hello_world_two(i, str)
397
+ puts "Hello? #{i} #{str}"
398
+ end
399
+ rtype :hello_world_two, [Integer, String] => String
400
+
401
+ # Also works (String will be converted to Symbol)
402
+ rtype 'hello_world_three', [Integer, String] => String
403
+ def hello_world_three(i, str)
404
+ puts "Hello? #{i} #{str}"
405
+ end
406
+
407
+ # Don't works. `rtype` works for next method
408
+ def hello_world_four(i, str)
409
+ puts "Hello? #{i} #{str}"
410
+ end
411
+ rtype [Integer, String] => String
412
+ end
413
+ ```
414
+
415
+ #### Outside of module (root)
416
+ Outside of module, annotation mode don't works. You must specify method name.
417
+
418
+ ```ruby
419
+ rtype :say, [String] => Any
420
+ def say(message)
421
+ puts message
422
+ end
423
+
424
+ Test.new.say "Hello" # Hello
425
+
426
+ rtype [String] => Any
427
+ # (ArgumentError) Annotation mode not working out of module
428
+ ```
429
+
430
+ #### Static(singleton) method
431
+ rtype annotation mode works both instance and class method
432
+
433
+ ```ruby
434
+ require 'rtype'
435
+
436
+ class Example
437
+ rtype [:to_i] => Any
438
+ def self.say_ya(i)
439
+ puts "say" + " ya"*i.to_i
440
+ end
441
+ end
442
+
443
+ Example::say_ya(3) #say ya ya ya
444
+ ```
445
+
446
+ however, if you specify method name, you must use `rtype_self` instead of `rtype`
447
+
448
+ ```ruby
449
+ require 'rtype'
450
+
451
+ class Example
452
+ rtype_self :say_ya, [:to_i] => Any
453
+ def self.say_ya(i)
454
+ puts "say" + " ya"*i.to_i
455
+ end
456
+ end
457
+
458
+ Example::say_ya(3) #say ya ya ya
459
+ ```
460
+
461
+ #### Check type information
462
+ This is just the 'information'
463
+
464
+ Any change of this doesn't affect type checking
465
+
466
+ ```ruby
467
+ require 'rtype'
468
+
469
+ class Example
470
+ rtype [:to_i] => Any
471
+ def test(i)
472
+ end
473
+ end
474
+
475
+ Example.new.method(:test).type_info
476
+ # => [:to_i] => Any
477
+ Example.new.method(:test).argument_type
478
+ # => [:to_i]
479
+ Example.new.method(:test).return_type
480
+ # => Any
481
+ ```
482
+
483
+ ## Documentation
484
+ [RubyDoc.info](http://www.rubydoc.info/gems/rtype)
485
+
486
+ ## Benchmarks
487
+ Result of `rake benchmark` ([source](https://github.com/sputnikgugja/rtype/tree/master/benchmark/benchmark.rb))
488
+
489
+ ### MRI
490
+ ```
491
+ Rtype with C native extension
492
+ Ruby version: 2.1.7
493
+ Ruby engine: ruby
494
+ Ruby description: ruby 2.1.7p400 (2015-08-18 revision 51632) [x64-mingw32]
495
+ Rtype version: 0.3.0
496
+ Rubype version: 0.3.1
497
+ Sig version: 1.0.1
498
+ Contracts version: 0.13.0
499
+ Typecheck version: 0.1.2
500
+ Warming up --------------------------------------
501
+ pure 85.328k i/100ms
502
+ rtype 25.665k i/100ms
503
+ rubype 21.414k i/100ms
504
+ sig 8.921k i/100ms
505
+ contracts 4.638k i/100ms
506
+ typecheck 1.110k i/100ms
507
+ Calculating -------------------------------------
508
+ pure 3.282M (± 2.7%) i/s - 16.468M
509
+ rtype 339.065k (± 2.6%) i/s - 1.720M
510
+ rubype 266.893k (± 5.9%) i/s - 1.349M
511
+ sig 99.952k (± 2.1%) i/s - 499.576k
512
+ contracts 49.693k (± 1.5%) i/s - 250.452k
513
+ typecheck 11.356k (± 1.6%) i/s - 57.720k
514
+
515
+ Comparison:
516
+ pure: 3282431.9 i/s
517
+ rtype: 339064.9 i/s - 9.68x slower
518
+ rubype: 266892.9 i/s - 12.30x slower
519
+ sig: 99952.2 i/s - 32.84x slower
520
+ contracts: 49693.0 i/s - 66.05x slower
521
+ typecheck: 11355.9 i/s - 289.05x slower
522
+ ```
523
+
524
+ ### JRuby
525
+ Without Rubype that doesn't support JRuby
526
+
527
+ ```
528
+ Rtype with Java extension
529
+ Ruby version: 2.2.3
530
+ Ruby engine: jruby
531
+ Ruby description: jruby 9.0.5.0 (2.2.3) 2016-01-26 7bee00d Java HotSpot(TM) 64-Bit Server VM 25.60-b23 on 1.8.0_60-b27 +jit [Windows 10-amd64]
532
+ Rtype version: 0.3.0
533
+ Sig version: 1.0.1
534
+ Contracts version: 0.13.0
535
+ Typecheck version: 0.1.2
536
+ Warming up --------------------------------------
537
+ pure 9.994k i/100ms
538
+ rtype 6.181k i/100ms
539
+ sig 4.041k i/100ms
540
+ contracts 951.000 i/100ms
541
+ typecheck 970.000 i/100ms
542
+ Calculating -------------------------------------
543
+ pure 7.128M (?±35.6%) i/s - 30.831M
544
+ rtype 121.556k (?± 6.2%) i/s - 605.738k
545
+ sig 72.187k (?± 6.4%) i/s - 359.649k
546
+ contracts 24.984k (?± 3.9%) i/s - 125.532k
547
+ typecheck 12.041k (?± 9.5%) i/s - 60.140k
548
+
549
+ Comparison:
550
+ pure: 7128373.0 i/s
551
+ rtype: 121555.8 i/s - 58.64x slower
552
+ sig: 72186.8 i/s - 98.75x slower
553
+ contracts: 24984.5 i/s - 285.31x slower
554
+ typecheck: 12041.0 i/s - 592.01x slower
555
+ ```
556
+
557
+ ## Rubype, Sig
558
+ Rtype is influenced by [Rubype](https://github.com/gogotanaka/Rubype) and [Sig](https://github.com/janlelis/sig).
559
+
560
+ If you don't like Rtype, You can use other type checking gem such as Contracts, Rubype, Rtc, Typecheck, Sig.
561
+
562
+ ## Author
563
+ Sputnik Gugja (sputnikgugja@gmail.com)
564
+
565
+ ## License
566
+ MIT license (@ Sputnik Gugja)
567
+
568
568
  See `LICENSE` file.