stitcher 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 60b567e6e0cac2f1dacd168e411da63d097eeb68
4
+ data.tar.gz: 137483e2bf3e741fe861ff1ea2b4c7c0604b9be0
5
+ SHA512:
6
+ metadata.gz: 879f602d5e7456953ac1b0b16d98dd7346f6e2ac4f216702bc88600f7d64d939f7bbe1ad5c1299ccc9ddc951281b756d3605e917d7919e68a480e2c60bc1815a
7
+ data.tar.gz: 883c44327d337921e864614153f874f92b5c90eba6a5eb01ac542fd1a1bbf776a6056c865b9649b4f1dd99040bc0ede011031ba0dc5ee88f4dc1957e47e7949f
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in stitcher.gemspec
4
+ gemspec
@@ -0,0 +1,107 @@
1
+ [![Build Status](https://travis-ci.org/osyo-manga/gem-stitcher.svg?branch=master)](https://travis-ci.org/osyo-manga/gem-stitcher)
2
+
3
+ # Stitcher
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/stitcher`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ TODO: Delete this and the text above, and describe your gem
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'stitcher'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install stitcher
24
+
25
+ ## Usage
26
+
27
+ ```ruby
28
+ require "stitcher"
29
+
30
+ # Using stitcher library.
31
+ using Stitcher
32
+
33
+ class X
34
+ # Define accessor with variable type(Class).
35
+ stitcher_accessor name: String, age: Integer
36
+
37
+ def set name, age
38
+ self.name = name
39
+ self.age = age
40
+ end
41
+ # Register set method with Argument types(Classes).
42
+ stitch :set, [String, Integer]
43
+
44
+ # Register for next define method.
45
+ stitcher_require [Hash]
46
+ def set hash
47
+ set hash[:name], hash[:age]
48
+ end
49
+
50
+ # Define "set" method with Argument types.
51
+ # set(Integer, String)
52
+ stitcher_define_method(:set, age: Integer, name: String){
53
+ self.name = name
54
+ self.age = age
55
+ }
56
+
57
+ # Other define method
58
+ stitch.set(ary: [String, Integer]){
59
+ set *ary
60
+ }
61
+
62
+ def print
63
+ p "name:#{name} age:#{age}"
64
+ end
65
+
66
+ # Require format with block object.
67
+ stitcher_require [String] & Stitcher::Concepts.blockable
68
+ def print fmt
69
+ printf(fmt, *yield(name, age))
70
+ end
71
+ end
72
+
73
+ x = X.new
74
+ x.name = "homu"
75
+ x.age = 14
76
+ # x.age = 14.0 # Error: No match method.
77
+
78
+ x.set "mami", 15
79
+ x.print
80
+ # => "name:mami age:15"
81
+
82
+ x.set({ name: "saya", age: 14 })
83
+ x.print
84
+ # => "name:saya age:14"
85
+
86
+ x.set 14, "mado"
87
+ x.print("%s-%s\n"){ |name, age| [name, age] }
88
+ # => mado-14
89
+
90
+ x.set ["homu", 14]
91
+ x.print("%s-%s\n"){ |name, age| [age, name] }
92
+ # => 14-homu
93
+ ```
94
+
95
+ ## Development
96
+
97
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
98
+
99
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
100
+
101
+ ## Contributing
102
+
103
+ 1. Fork it ( https://github.com/osyo-manga/gem-stitcher )
104
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
105
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
106
+ 4. Push to the branch (`git push origin my-new-feature`)
107
+ 5. Create a new Pull Request
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "stitcher"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,57 @@
1
+ # Stitcher
2
+
3
+ ## 目的
4
+
5
+ * メソッドの多重定義をする
6
+ * 引数によって呼び出すメソッドを切り替える
7
+ * メソッドの引数に対しての制限を設ける
8
+ * 引数に対する型チェック
9
+ * 引数に対するコンセプトチェック
10
+ * モンキーパッチで定義するメソッドを制限したい
11
+ * 引数が○○○の場合でのみモンキーパッチのメソッドを呼び出す等
12
+
13
+
14
+ ## 動作イメージ
15
+
16
+ ```ruby
17
+ class X
18
+ include Stitcher
19
+
20
+ def plus a, b
21
+ p "plus(a.to_i, b.to_i)"
22
+ a.to_i + b.to_i
23
+ end
24
+ # #plus を呼び出すコンセプトを設定する
25
+ # 引数がそれぞれ #to_i を呼び出せればこのメソッドを呼び出す
26
+ concept :plus do |a, b|
27
+ a.respond_to?(:to_i) && b.respond_to?(:to_i)
28
+ end
29
+
30
+ # concept 設定後に別のメソッドを定義し直す事ができる
31
+ def plus a, b
32
+ p "plus(a + b)"
33
+ a + b
34
+ end
35
+
36
+ # 再定義したメソッドに対しても条件を設定できる
37
+ # こっちは両方共 Numeric だった場合に呼び出す
38
+ concept :plus do |a, b|
39
+ a.class <= Numeric && b.class <= Numeric
40
+ end
41
+ end
42
+
43
+ X.new.plus "1", "2"
44
+ # => 最初に定義したメソッドが呼ばれる
45
+ X.new.plus 1, 2
46
+ # => あとで定義したメソッドが呼ばれる
47
+
48
+ X.new.plus X, X
49
+ # => どれにも該当しないのでエラー
50
+ ```
51
+
52
+
53
+ ## 考えること
54
+
55
+ * 呼び出すことができるメソッドが複数あった場合どうするか
56
+ * 呼び出すメソッドの優先順位
57
+
@@ -0,0 +1,208 @@
1
+ # Stitcher
2
+
3
+ ## これは何?
4
+
5
+ 定義する(定義した)メソッドに対して引数のシグネチャを設定するライブラリです。
6
+ シグネチャを指定することにより、静的型付け言語のようなメソッドの多重定義を行うことを目的としています、
7
+
8
+
9
+ ## 導入
10
+
11
+ #### install
12
+
13
+ ```shell
14
+ $ gem install stitcher
15
+ ```
16
+
17
+ #### require
18
+
19
+ ```ruby
20
+ require "stitcher"
21
+
22
+ using Stitcher
23
+ ```
24
+
25
+ ## 使い方
26
+
27
+ #### シグネチャの設定
28
+
29
+ `stitcher` は定義したメソッドか、次に定義するメソッドに対してシグネチャを設定することで使用します。
30
+
31
+ ```ruby
32
+ require "stitcher"
33
+
34
+ # using することで Module クラスに Stitcher の機能が mixin される
35
+ using Stitcher
36
+
37
+
38
+ class Counter
39
+ attr_reader :value
40
+
41
+ def initialize value
42
+ @value = value
43
+ end
44
+ # 既存のメソッドに対して引数のシグネチャを設定する
45
+ # #initialize メソッドに対して Fixnum クラスのインスタンスのみ受け取るようにする
46
+ stitcher_register :initialize, [Fixnum]
47
+
48
+ # stitcher_register を使用することで次に定義するメソッドに対しての
49
+ # シグネチャを設定することもできる
50
+ # stitcher_register の第二引数と stitcher_require の第一引数は同じである
51
+ stitcher_require [Fixnum]
52
+ # add メソッドは Fixnum のインスタンスのみ受け付ける
53
+ def add value
54
+ @value += value
55
+ end
56
+
57
+ # また、違うシグネチャを設定することにより複数のメソッドを定義する事ができる
58
+ stitcher_require [String]
59
+ def add str
60
+ @value += str.to_i
61
+ end
62
+ end
63
+
64
+ count = Counter.new 0 # OK
65
+ # count = Counter.new "" # Error: Fixnum クラスのインスタンスではない
66
+
67
+ count.add 10 # OK
68
+ count.add "12" # OK
69
+ # count.add 0.42 # Error
70
+ p count.value
71
+ # => 22
72
+ ```
73
+
74
+ ```ruby
75
+ using Stitcher
76
+ ```
77
+
78
+ まず、`using` を使用して、特定の範囲でのみ Stitcher を使用することを定義します。
79
+ この時に `Module` クラス内に Stitcher の機能が `mixin` され、いくつかのクラス拡張が適用されます。
80
+
81
+ ```ruby
82
+ def initialize value
83
+ @value = value
84
+ end
85
+ stitcher_register :initialize, [Fixnum]
86
+ ```
87
+
88
+ `.stitcher_register` を使用して既存のメソッドに対してシグネチャを設定します。
89
+ この時に設定するシグネチャは各引数に対するリストになります。
90
+ また、各シグネチャと実引数の要素は `#===` を用いて比較が行われ、このメソッドが真を返せばシグネチャにマッチしたと認識されます。
91
+
92
+ ```ruby
93
+ stitcher_require [Fixnum]
94
+ def add value
95
+ @value += value
96
+ end
97
+ ```
98
+
99
+ `.stitcher_require` を使用して、次に定義されるメソッドに対してのシグネチャを設定します。
100
+ 設定するシグネチャは `.stitcher_register` と同じです。
101
+
102
+ ```ruby
103
+ stitcher_require [String]
104
+ def add str
105
+ @value += str.to_i
106
+ end
107
+ ```
108
+
109
+ 違うシグネチャを設定することで静的型付け言語のような多重定義を行うことができます。
110
+ これにより『引数のクラス』によって簡単に処理を分岐することができます。
111
+
112
+ #### シグネチャの設定
113
+
114
+ 各引数のシグネチャは `#===` が定義されてるオブジェクトであればなんでも設定する事ができます。
115
+ これは Ruby の `case` 文と似ています。
116
+ 例えば、`Class` 以外にも `Proc` や `Regexp` などを渡すことができます。
117
+
118
+ ```ruby
119
+ class Http
120
+ stitcher_require [/^https?/]
121
+ def post url
122
+ # ...
123
+ end
124
+
125
+ stitcher_require [ proc { |hash| (Hash === hash && hash.key?(:url)) } ]
126
+ def post hash
127
+ post hash[:url]
128
+ end
129
+ end
130
+
131
+ http = Http.new
132
+ http.post "http://docs.ruby-lang.org/" # OK
133
+ http.post({ url: "http://docs.ruby-lang.org/" }) # OK
134
+
135
+ http.post "ftp://docs.ruby-lang.org/" # Error
136
+ http.post({ uri: "http://docs.ruby-lang.org/" }) # Error
137
+ ```
138
+
139
+ このようにしてより厳密な引数に対する要求を設定することができます。
140
+ また、`Class` や `Proc`、`Regexp` は `&` や `|` で結合することもできます。
141
+
142
+ ```ruby
143
+ # Hash であることと Proc の条件を満たすことを要求する
144
+ stitcher_require [Hash & proc { |hash| (hash.key?(:url)) } ]
145
+ def post hash
146
+ post hash[:url]
147
+ end
148
+ ```
149
+
150
+ #### 多重定義字に呼び出されるメソッドの優先順位
151
+
152
+ Stitcehr を利用してメソッドを多重定義した場合に呼び出されるメソッドの優先順位は __下から順に優先順位が高くなります。__
153
+ 例えば、次のような定義の場合、
154
+
155
+ ```ruby
156
+ require "stitcher"
157
+
158
+ using Stitcher
159
+
160
+ class X
161
+ stitcher_require [Fixnum]
162
+ def func value
163
+ "Fixnum"
164
+ end
165
+
166
+ stitcher_require [Numeric]
167
+ def func value
168
+ "Numeric"
169
+ end
170
+ end
171
+ ```
172
+
173
+ あとから定義されている `func(Numeric)` の方が優先順位が高くなり、
174
+
175
+ ```
176
+ x = X.new
177
+ x.func 10 # Numeric === 10 # => true
178
+ ```
179
+
180
+ というような呼び出しの場合は、`Numeric#===` がどちらも真となるため、`func(Fixnum)` ではなくて `func(Numeric)` が呼び出されます。
181
+ これは次のように `Fixnum` にマッチしないようにすることで回避することは可能です。
182
+
183
+ ```ruby
184
+ require "stitcher"
185
+
186
+ using Stitcher
187
+
188
+ class X
189
+ stitcher_require [Fixnum]
190
+ def func value
191
+ "Fixnum"
192
+ end
193
+
194
+ # Numeric であり、Fixnum 以外にマッチする場合のシグネチャ
195
+ stitcher_require [Numeric & !Fixnum]
196
+ def func value
197
+ "Numeric"
198
+ end
199
+ end
200
+
201
+ x = X.new
202
+ p x.func 10
203
+ # => "Fixnum"
204
+ p x.func 0.42
205
+ # => "Numeric"
206
+ ```
207
+
208
+
@@ -0,0 +1,212 @@
1
+ # Ruby の型
2
+
3
+ ## やりたいこと
4
+
5
+ * Ruby で型を定義する
6
+ * 現状は Class = 型とする
7
+ * 型チェックを行う仕組み
8
+ * ジェネリックプログラミングや Variant 型(Haskell の Either みたいなの?)の定義
9
+ * C++ の template みたいなのもほしい
10
+ * 型によって処理を切り替える装置
11
+
12
+ ## 動作イメージ
13
+
14
+ #### 型の定義
15
+
16
+ ```ruby
17
+ # To type
18
+ 1.type # => Fixnum
19
+ "".type # => String
20
+ String.type # == String
21
+ ```
22
+
23
+ ```ruby
24
+ # Variant type
25
+ (String | Fixnum) # => String or Fixnum
26
+ (String | Fixnum | Hash) # => String or Fixnum or Hahs
27
+ ```
28
+
29
+ ```ruby
30
+ # Super class type
31
+ +Numeric # => Numeric is super class type
32
+
33
+ # No type
34
+ !Numerci # => Not Numeric type
35
+ ```
36
+
37
+ ##### 型の比較
38
+
39
+ ```ruby
40
+ 1.type == Fixnum # => true
41
+ 1.type == String # => false
42
+ 1.type <= Numeric # => true
43
+ 1.type == Numeric # => false
44
+ ```
45
+
46
+ ```ruby
47
+ # Type1 | Type2 => Variant type
48
+ (String | Fixnum) == 1 # true
49
+ (String | Fixnum) == "" # true
50
+ (String | Fixnum) == {} # false
51
+ ```
52
+
53
+
54
+ #### 型変数の定義
55
+
56
+ ```ruby
57
+ let :name, String
58
+ self.name = "" # OK
59
+ self.name = 42 # Error
60
+ ```
61
+
62
+
63
+ ```ruby
64
+ let :data, String | Numeric # Variant 型
65
+ self.data = "" # OK
66
+ self.data = 42 # OK
67
+ self.data = {} # Error
68
+ ```
69
+
70
+
71
+ ```ruby
72
+ let :bool, True | False # Variant 型
73
+ self.bool = true # OK
74
+ self.bool = false # OK
75
+ self.bool = nil # Error
76
+ ```
77
+
78
+
79
+ ```ruby
80
+ class X
81
+ let name: String, age: Numeric
82
+ end
83
+ x = X.new
84
+ x.name = "homu" # OK
85
+ x.age = 13 # OK
86
+ x.name 13 # Error
87
+ ```
88
+
89
+ ## 悩み
90
+
91
+ 型同士の比較をどうするか考える
92
+
93
+ 1.type # => Fixnum
94
+
95
+ 1.type == Numeric # => Fixnum == Numeric
96
+ 1.type <= Numeric # => Fixnum <= Numeric
97
+ 1.type === Numeric # => Fixnum === Numeric
98
+
99
+ 1.type == +Numeric # => Fixnum == Numeric
100
+ 1.type <= +Numeric # => Fixnum <= Numeric
101
+ 1.type === +Numeric # => Fixnum <= Numeric
102
+
103
+ 1.type == !Numeric # => Fixnum == Numeric
104
+ 1.type != !Numeric # => Fixnum != Numeric
105
+ 1.type === !Numeric # => Fixnum != Numeric
106
+
107
+
108
+
109
+
110
+
111
+ class A
112
+ def == rhs
113
+ p "A#== #{rhs.class}"
114
+ end
115
+ end
116
+
117
+ class B
118
+ def == rhs
119
+ p "B#== #{rhs.class}"
120
+ end
121
+ end
122
+
123
+ class A
124
+ prepend(Module.new do
125
+ define_method(:==) do |rhs|
126
+ rhs.class == B ? rhs == self : super(rhs)
127
+ end
128
+ end)
129
+ end
130
+
131
+
132
+ class C
133
+
134
+ end
135
+
136
+ A.new == B.new
137
+ A.new == C.new
138
+
139
+
140
+
141
+ class A
142
+ def == rhs
143
+ if rhs.respond_to? :callable_equal
144
+ rhs.callable_equal(self)
145
+ else
146
+ p "A#== #{rhs.class}"
147
+ end
148
+ end
149
+ end
150
+
151
+ class B
152
+ def callable_equal rhs
153
+ p "B#== #{rhs.class}"
154
+ end
155
+ end
156
+
157
+ class C
158
+
159
+ end
160
+
161
+ A.new == B.new
162
+ A.new == C.new
163
+
164
+
165
+
166
+ class A
167
+ def == rhs
168
+ p "A#== #{rhs.class}"
169
+ end
170
+ end
171
+
172
+ class B
173
+ def == rhs
174
+ p "B#== #{rhs.class}"
175
+ end
176
+ end
177
+
178
+ class A
179
+ prepend(Module.new do
180
+ define_method(:==) do |rhs|
181
+ rhs.class == B ? rhs == self : super(rhs)
182
+ end
183
+ end)
184
+ end
185
+
186
+
187
+ class C
188
+
189
+ end
190
+
191
+ class D
192
+ def == rhs
193
+ p "D#== #{rhs.class}"
194
+ end
195
+ end
196
+
197
+ class A
198
+ prepend(Module.new do
199
+ define_method(:==) do |rhs|
200
+ rhs.class == D ? rhs == self : super(rhs)
201
+ end
202
+ end)
203
+ end
204
+
205
+
206
+ A.new == Object.new
207
+ A.new == A.new
208
+ A.new == B.new
209
+ A.new == C.new
210
+ A.new == D.new
211
+
212
+