rtype 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 64e55ed4eec9458ee6526ed63ec8ea39417a2102
4
+ data.tar.gz: 8daa9c4b65bed25483f24c8a1dd029bbc80828c8
5
+ SHA512:
6
+ metadata.gz: 678e6efe0c5dd85db8253f35388e83c907c026912a22cce499015a5d18c17cc914d314618017e9a34dd696d37c5e3e1786ad4aace7c1fc1ce513f96d9731be98
7
+ data.tar.gz: 62cf0e908936c457153bf982aa5803a634efdefde4a878a454294ccf5b91c12fe8a36d870cf448eee31f1e7831e25c92d5bc084c0359feeef23cb4498ec5cd3e
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Sputnik Gugja
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,406 @@
1
+ # Rtype: ruby with type
2
+ [![Gem Version](https://badge.fury.io/rb/rtype.svg)](https://badge.fury.io/rb/rtype) [![Build Status](https://travis-ci.org/sputnikgugja/rtype.svg?branch=master)](https://travis-ci.org/sputnikgugja/rtype)
3
+
4
+ You can do the type checking in Ruby with this gem!
5
+
6
+ ```ruby
7
+ require 'rtype'
8
+
9
+ rtype :sum, [:to_i, Numeric] => Numeric
10
+ def sum(a, b)
11
+ a.to_i + b
12
+ end
13
+
14
+ sum(123, "asd")
15
+ # (Rtype::ArgumentTypeError) for 2nd argument:
16
+ # Expected "asd" to be a Numeric
17
+
18
+ class Test
19
+ rtype_self :invert, {state: Boolean} => Boolean
20
+ def self.invert(state:)
21
+ !state
22
+ end
23
+ end
24
+
25
+ Test::invert(state: 0)
26
+ # (Rtype::ArgumentTypeError) for 'state' argument:
27
+ # Expected 0 to be a Boolean
28
+ ```
29
+
30
+ ## Requirements
31
+ - Ruby >= 2.1
32
+
33
+ ## Features
34
+ - Very simple
35
+ - Provide type checking for argument and return
36
+ - Support type checking for [keyword argument](#keyword-argument)
37
+ - [Type checking for array elements](#array)
38
+ - [Duck typing](#duck-typing)
39
+ - Custom type behavior
40
+
41
+ ## Installation
42
+ Run `gem install rtype` or add `gem 'rtype'` to your `Gemfile`
43
+
44
+ And add to your `.rb` source file:
45
+ ```ruby
46
+ require 'rtype'
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ### Supported Type Behaviors
52
+ - `Module`
53
+ - Value must be an instance of this module/class or one of its superclasses
54
+ - `Any` : An alias for `BasicObject` (means Any Object)
55
+ - `Boolean` : `true` or `false`
56
+ - `Symbol`
57
+ - Value must have(respond to) a method with this name
58
+ - `Regexp`
59
+ - Value must match this regexp pattern
60
+ - `Range`
61
+ - Value must be included in this range
62
+ - `Array` (tuple)
63
+ - Value must be an array
64
+ - Each of value's elements must be valid
65
+ - Value's length must be equal to the array's length
66
+ - Of course, nested array works
67
+ - Example: [Array](#array)
68
+ - This can be used as a tuple
69
+ - `Proc`
70
+ - Value must return a truthy value for this proc
71
+ - `true`
72
+ - Value must be **truthy**
73
+ - `false`
74
+ - Value must be **falsy**
75
+ - `nil`
76
+ - Only available for **return type**. void return type in other languages
77
+ - Special Behaviors
78
+ - `Rtype::and(*types)` : Ensure value is valid for all the types
79
+ - It also can be used as `Rtype::Behavior::And[*types]` or `include Rtype::Behavior; And[...]`
80
+ - `Rtype::or(*types)` : Ensure value is valid for at least one of the types
81
+ - It also can be used as `Rtype::Behavior::Or[*types]` or `include Rtype::Behavior; Or[...]`
82
+ - `Rtype::xor(*types)` : Ensure value is valid for only one of the types
83
+ - It also can be used as `Rtype::Behavior::Xor[*types]` or `include Rtype::Behavior; Xor[...]`
84
+ - `Rtype::not(*types)` : Ensure value is not valid for all the types
85
+ - It also can be used as `Rtype::Behavior::Not[*types]` or `include Rtype::Behavior; Not[...]`
86
+ - `Rtype::nilable(type)` : Ensure value can be nil
87
+ - It also can be used as `Rtype::Behavior::Nilable[type]` or `include Rtype::Behavior; Nilable[...]`
88
+ - You can create custom behavior by extending `Rtype::Behavior::Base`
89
+
90
+ ### Examples
91
+
92
+ #### Basic
93
+ ```ruby
94
+ require 'rtype'
95
+
96
+ class Example
97
+ rtype :test, [Integer] => nil
98
+ def test(i)
99
+ end
100
+
101
+ rtype :any_type_arg, [Any] => nil
102
+ def any_type_arg(arg)
103
+ end
104
+
105
+ rtype :return_type_test, [] => Integer
106
+ def return_type_test
107
+ "not integer"
108
+ end
109
+ end
110
+
111
+ e = Example.new
112
+ e.test("not integer")
113
+ # (Rtype::ArgumentTypeError) for 1st argument:
114
+ # Expected "not integer" to be a Integer
115
+
116
+ e.any_type_arg("Any argument!") # Works
117
+
118
+ e.return_type_test
119
+ # (Rtype::ReturnTypeError) for return:
120
+ # Expected "not integer" to be a Integer
121
+ ```
122
+
123
+ #### Keyword argument
124
+ ```ruby
125
+ require 'rtype'
126
+
127
+ class Example
128
+ rtype :say_your_name, {name: String} => Any
129
+ def say_your_name(name:)
130
+ puts "My name is #{name}"
131
+ end
132
+
133
+ # Mixing positional arguments and keyword arguments
134
+ rtype :name_and_age, [String, {age: Integer}] => Any
135
+ def name_and_age(name, age:)
136
+ puts "Name: #{name}, Age: #{age}"
137
+ end
138
+ end
139
+
140
+ Example.new.say_your_name(name: "Babo") # My name is Babo
141
+ Example.new.name_and_age("Bamboo", age: 100) # Name: Bamboo, Age: 100
142
+
143
+ Example.new.say_your_name(name: 12345)
144
+ # (Rtype::ArgumentTypeError) for 'name' argument:
145
+ # Expected 12345 to be a String
146
+ ```
147
+
148
+ #### Duck typing
149
+ ```ruby
150
+ require 'rtype'
151
+
152
+ class Duck
153
+ rtype :says, [:to_i] => Any
154
+ def says(i)
155
+ puts "duck:" + " quack"*i.to_i
156
+ end
157
+ end
158
+
159
+ Duck.new.says("2") # duck: quack quack
160
+ ```
161
+
162
+ #### Array
163
+ This can be used as a tuple.
164
+
165
+ ```ruby
166
+ rtype :func, [[Numeric, Numeric]] => Any
167
+ def func(arr)
168
+ puts "Your location is (#{arr[0]}, #{arr[1]}). I will look for you. I will find you"
169
+ end
170
+
171
+ func [1, "str"]
172
+ # (Rtype::ArgumentTypeError) for 1st argument:
173
+ # Expected [1, "str"] to be an array with 2 elements:
174
+ # - [0] index : Expected 1 to be a Numeric
175
+ # - [1] index : Expected "str" to be a Numeric
176
+
177
+ func [1, 2, 3]
178
+ # (Rtype::ArgumentTypeError) for 1st argument:
179
+ # Expected [1, 2, 3] to be an array with 2 elements:
180
+ # - [0] index : Expected 1 to be a Numeric
181
+ # - [1] index : Expected 2 to be a Numeric
182
+
183
+ func [1]
184
+ # (Rtype::ArgumentTypeError) for 1st argument:
185
+ # Expected [1] to be an array with 2 elements:
186
+ # - [0] index : Expected 1 to be a Numeric
187
+ # - [1] index : Expected nil to be a Numeric
188
+
189
+ func [1, 2] # Your location is (1, 2). I will look for you. I will find you
190
+ ```
191
+
192
+ #### Combined type
193
+ ```ruby
194
+ ### TEST 1 ###
195
+ require 'rtype'
196
+
197
+ class Example
198
+ rtype :and_test, [Rtype::and(String, :func)] => Any
199
+ def and_test(arg)
200
+ end
201
+ end
202
+
203
+ Example.new.and_test("A string")
204
+ # (Rtype::ArgumentTypeError) for 1st argument:
205
+ # Expected "A string" to be a String
206
+ # AND Expected "A string" to respond to :func
207
+ ```
208
+ ```ruby
209
+ ### TEST 2 ###
210
+ # ... require rtype and define Example the same as above ...
211
+
212
+ class String
213
+ def func; end
214
+ end
215
+
216
+ Example.new.and_test("A string") # Works!
217
+ ```
218
+
219
+ #### Combined duck type
220
+ Application of duck typing and combined type
221
+
222
+ ```ruby
223
+ require 'rtype'
224
+
225
+ module Game
226
+ ENEMY = [
227
+ :name,
228
+ :level
229
+ ]
230
+
231
+ class Player < Entity
232
+ include Rtype::Behavior
233
+
234
+ rtype :attack, [And[*ENEMY]] => Any
235
+ def attacks(enemy)
236
+ "Player attacks '#{enemy.name}' (level #{enemy.level})!"
237
+ end
238
+ end
239
+
240
+ class Slime < Entity
241
+ def name
242
+ "Powerful Slime"
243
+ end
244
+
245
+ def level
246
+ 123
247
+ end
248
+ end
249
+ end
250
+
251
+ Game::Player.new.attacks Game::Slime.new
252
+ # Player attacks 'Powerful Slime' (level 123)!
253
+ ```
254
+
255
+ #### Position of `rtype` && (symbol || string)
256
+ ```ruby
257
+ require 'rtype'
258
+
259
+ class Example
260
+ # Works. Recommended
261
+ rtype :hello_world, [Integer, String] => String
262
+ def hello_world(i, str)
263
+ puts "Hello? #{i} #{st
264
+ end
265
+
266
+ # Works
267
+ def hello_world_two(i, str)
268
+ puts "Hello? #{i} #{str}"
269
+ end
270
+ rtype :hello_world_two, [Integer, String] => String
271
+
272
+ # Also works (String will be converted to Symbol)
273
+ rtype 'hello_world_three', [Integer, String] => String
274
+ def hello_world_three(i, str)
275
+ puts "Hello? #{i} #{str}"
276
+ end
277
+ end
278
+ ```
279
+
280
+ #### Outside of module (root)
281
+ Yes, it works
282
+
283
+ ```ruby
284
+ rtype :say, [String] => Any
285
+ def say(message)
286
+ puts message
287
+ end
288
+
289
+ say "Hello" # Hello
290
+ ```
291
+
292
+ #### Static method
293
+ Use `rtype_self`
294
+
295
+ ```ruby
296
+ require 'rtype'
297
+
298
+ class Example
299
+ rtype_self :say_ya, [:to_i] => Any
300
+ def self.say_ya(i)
301
+ puts "say" + " ya"*i.to_i
302
+ end
303
+ end
304
+
305
+ Example::say_ya(3) #say ya ya ya
306
+ ```
307
+
308
+ #### Check type information
309
+ This is just the 'information'
310
+
311
+ Any change of this doesn't affect type checking
312
+
313
+ ```ruby
314
+ require 'rtype'
315
+
316
+ class Example
317
+ rtype :test, [:to_i] => Any
318
+ def test(i)
319
+ end
320
+ end
321
+
322
+ Example.new.method(:test).type_info
323
+ # => [:to_i] => Any
324
+ Example.new.method(:test).argument_type
325
+ # => [:to_i]
326
+ Example.new.method(:test).return_type
327
+ # => Any
328
+ ```
329
+
330
+ ## Benchmarks
331
+ Result of `rake benchmark` ([source](https://github.com/sputnikgugja/rtype/tree/master/benchmark/benchmark.rb))
332
+
333
+ The benchmark doesn't include `Rubype` gem because I can't install Rubype on my environment.
334
+
335
+ ### MRI
336
+ ```
337
+ Ruby version: 2.1.7
338
+ Ruby engine: ruby
339
+ Ruby description: ruby 2.1.7p400 (2015-08-18 revision 51632) [x64-mingw32]
340
+ Rtype version: 0.0.1
341
+ Sig version: 1.0.1
342
+ Contracts version: 0.13.0
343
+ Typecheck version: 0.1.2
344
+ Warming up --------------------------------------
345
+ pure 84.672k i/100ms
346
+ rtype 10.221k i/100ms
347
+ sig 8.271k i/100ms
348
+ contracts 4.604k i/100ms
349
+ typecheck 1.102k i/100ms
350
+ Calculating -------------------------------------
351
+ pure 3.438M (±33.5%) i/s - 15.580M
352
+ rtype 115.274k (± 9.2%) i/s - 572.376k
353
+ sig 100.204k (± 8.0%) i/s - 504.531k
354
+ contracts 49.026k (± 9.6%) i/s - 244.012k
355
+ typecheck 11.108k (± 7.4%) i/s - 56.202k
356
+
357
+ Comparison:
358
+ pure: 3437842.1 i/s
359
+ rtype: 115274.1 i/s - 29.82x slower
360
+ sig: 100203.7 i/s - 34.31x slower
361
+ contracts: 49025.8 i/s - 70.12x slower
362
+ typecheck: 11107.6 i/s - 309.50x slower
363
+ ```
364
+
365
+ ### JRuby
366
+ ```
367
+ Ruby version: 2.2.3
368
+ Ruby engine: jruby
369
+ 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]
370
+ Rtype version: 0.0.1
371
+ Sig version: 1.0.1
372
+ Contracts version: 0.13.0
373
+ Typecheck version: 0.1.2
374
+ Warming up --------------------------------------
375
+ pure 17.077k i/100ms
376
+ rtype 2.774k i/100ms
377
+ sig 3.747k i/100ms
378
+ contracts 907.000 i/100ms
379
+ typecheck 937.000 i/100ms
380
+ Calculating -------------------------------------
381
+ pure 5.186M (±50.8%) i/s - 15.933M
382
+ rtype 69.206k (±15.3%) i/s - 341.202k
383
+ sig 64.460k (±16.4%) i/s - 314.748k
384
+ contracts 24.372k (±13.2%) i/s - 119.724k
385
+ typecheck 11.670k (±12.8%) i/s - 58.094k
386
+
387
+ Comparison:
388
+ pure: 5185896.5 i/s
389
+ rtype: 69206.2 i/s - 74.93x slower
390
+ sig: 64460.2 i/s - 80.45x slower
391
+ contracts: 24371.7 i/s - 212.78x slower
392
+ typecheck: 11670.0 i/s - 444.38x slower
393
+ ```
394
+
395
+ ## Rubype, Sig
396
+ Rtype is influenced by [Rubype](https://github.com/gogotanaka/Rubype) and [Sig](https://github.com/janlelis/sig).
397
+
398
+ If you don't like Rtype, You can use other type checking gem such as Contracts, Rubype, Rtc, Typecheck, Sig.
399
+
400
+ ## Author
401
+ Sputnik Gugja (sputnikgugja@gmail.com)
402
+
403
+ ## License
404
+ MIT license (@ Sputnik Gugja)
405
+
406
+ See `LICENSE` file.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ # Default pattern is 'spec/**{,/*/**}/*_spec.rb'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ # Benchmark
9
+ desc "Compare with pure ruby and other gems"
10
+ task :benchmark do
11
+ ruby "benchmark/benchmark.rb"
12
+ end
@@ -0,0 +1,157 @@
1
+ require 'benchmark/ips'
2
+
3
+ require_relative '../lib/rtype'
4
+ require "sig"
5
+ require "contracts"
6
+ require "contracts/version"
7
+ require "typecheck"
8
+
9
+ puts "Ruby version: #{RUBY_VERSION}"
10
+ puts "Ruby engine: #{RUBY_ENGINE}"
11
+ puts "Ruby description: #{RUBY_DESCRIPTION}"
12
+
13
+ puts "Rtype version: #{Rtype::VERSION}"
14
+ puts "Sig version: #{Sig::VERSION}"
15
+ puts "Contracts version: #{Contracts::VERSION}"
16
+ puts "Typecheck version: #{Typecheck::VERSION}"
17
+
18
+ class PureTest
19
+ def sum(x, y)
20
+ x + y
21
+ end
22
+
23
+ def mul(x, y)
24
+ x * y
25
+ end
26
+
27
+ def args(a, b, c, d)
28
+ end
29
+ end
30
+ pure_obj = PureTest.new
31
+
32
+ class RtypeTest
33
+ rtype :sum, [Numeric, Numeric] => Numeric
34
+ def sum(x, y)
35
+ x + y
36
+ end
37
+
38
+ rtype :mul, [:to_i, :to_i] => Numeric
39
+ def mul(x, y)
40
+ x * y
41
+ end
42
+
43
+ rtype :args, [Integer, Numeric, String, :to_i] => Any
44
+ def args(a, b, c, d)
45
+ end
46
+ end
47
+ rtype_obj = RtypeTest.new
48
+
49
+ class SigTest
50
+ sig [Numeric, Numeric], Numeric,
51
+ def sum(x, y)
52
+ x + y
53
+ end
54
+
55
+ sig [:to_i, :to_i], Numeric,
56
+ def mul(x, y)
57
+ x * y
58
+ end
59
+
60
+ # nil means wildcard
61
+ sig [Integer, Numeric, String, :to_i], nil,
62
+ def args(a, b, c, d)
63
+ end
64
+ end
65
+ sig_obj = SigTest.new
66
+
67
+ class ContractsTest
68
+ include Contracts
69
+
70
+ Contract Num, Num => Num
71
+ def sum(x, y)
72
+ x + y
73
+ end
74
+
75
+ Contract RespondTo[:to_i], RespondTo[:to_i] => Num
76
+ def mul(x, y)
77
+ x * y
78
+ end
79
+
80
+ Contract Int, Num, String, RespondTo[:to_i] => Any
81
+ def args(a, b, c, d)
82
+ end
83
+ end
84
+ contracts_obj = ContractsTest.new
85
+
86
+ class TypecheckTest
87
+ extend Typecheck
88
+
89
+ typecheck 'Numeric, Numeric -> Numeric',
90
+ def sum(x, y)
91
+ x + y
92
+ end
93
+
94
+ typecheck '#to_i, #to_i -> Numeric',
95
+ def mul(x, y)
96
+ x * y
97
+ end
98
+
99
+ typecheck 'Integer, Numeric, String, #to_i -> BasicObject',
100
+ def args(a, b, c, d)
101
+ end
102
+ end
103
+ typecheck_obj = TypecheckTest.new
104
+
105
+ Benchmark.ips do |x|
106
+ x.report("pure") do |times|
107
+ i = 0
108
+ while i < times
109
+ pure_obj.sum(1, 2)
110
+ pure_obj.mul(1, 2)
111
+ pure_obj.args(1, 2, "c", 4)
112
+ i += 1
113
+ end
114
+ end
115
+
116
+ x.report("rtype") do |times|
117
+ i = 0
118
+ while i < times
119
+ rtype_obj.sum(1, 2)
120
+ rtype_obj.mul(1, 2)
121
+ rtype_obj.args(1, 2, "c", 4)
122
+ i += 1
123
+ end
124
+ end
125
+
126
+ x.report("sig") do |times|
127
+ i = 0
128
+ while i < times
129
+ sig_obj.sum(1, 2)
130
+ sig_obj.mul(1, 2)
131
+ sig_obj.args(1, 2, "c", 4)
132
+ i += 1
133
+ end
134
+ end
135
+
136
+ x.report("contracts") do |times|
137
+ i = 0
138
+ while i < times
139
+ contracts_obj.sum(1, 2)
140
+ contracts_obj.mul(1, 2)
141
+ contracts_obj.args(1, 2, "c", 4)
142
+ i += 1
143
+ end
144
+ end
145
+
146
+ x.report("typecheck") do |times|
147
+ i = 0
148
+ while i < times
149
+ typecheck_obj.sum(1, 2)
150
+ typecheck_obj.mul(1, 2)
151
+ typecheck_obj.args(1, 2, "c", 4)
152
+ i += 1
153
+ end
154
+ end
155
+
156
+ x.compare!
157
+ end
@@ -0,0 +1,4 @@
1
+ module Rtype
2
+ class ArgumentTypeError < ArgumentError
3
+ end
4
+ end
@@ -0,0 +1,24 @@
1
+ module Rtype
2
+ module Behavior
3
+ class And < Base
4
+ def initialize(*types)
5
+ @types = types
6
+ end
7
+
8
+ def valid?(value)
9
+ @types.all? do |e|
10
+ Rtype::valid? e, value
11
+ end
12
+ end
13
+
14
+ def error_message(value)
15
+ arr = @types.map { |e| Rtype::type_error_message(e, value) }
16
+ arr.join "\nAND "
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.and(*args)
22
+ Behavior::And[*args]
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Rtype
2
+ module Behavior
3
+ class Base
4
+ def self.[](*vals)
5
+ new(*vals)
6
+ end
7
+
8
+ def valid?(value)
9
+ raise NotImplementedError, "Abstract method"
10
+ end
11
+
12
+ def error_message(value)
13
+ raise NotImplementedError, "Abstract method"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module Rtype
2
+ module Behavior
3
+ class Nilable < Base
4
+ def initialize(type)
5
+ @type = type
6
+ end
7
+
8
+ def valid?(value)
9
+ value.nil? || Rtype::valid?(e, value)
10
+ end
11
+
12
+ def error_message(value)
13
+ Rtype::type_error_message(@type, value) + "\nOR " + Rtype::type_error_message(nil, value)
14
+ end
15
+ end
16
+ end
17
+
18
+ def self.nilable(*args)
19
+ Behavior::Nilable[*args]
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ module Rtype
2
+ module Behavior
3
+ class Not < Base
4
+ def initialize(*types)
5
+ @types = types
6
+ end
7
+
8
+ def valid?(value)
9
+ @types.all? do |e|
10
+ !Rtype::valid?(e, value)
11
+ end
12
+ end
13
+
14
+ def error_message(value)
15
+ arr = @types.map { |e| "NOT " + Rtype::type_error_message(e, value) }
16
+ arr.join "\nAND "
17
+ end
18
+ end
19
+ end
20
+
21
+ def self.not(*args)
22
+ Behavior::Not[*args]
23
+ end
24
+ end