kindah 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YzkwOGY0YWQ3YTZiYjA0Yzc3MmY0NjY0MTNlMmZjOWYzMTI5MjNlYw==
5
+ data.tar.gz: !binary |-
6
+ Zjk3MTgyZDFlMGFkNTliMzRhY2Q0MWEwYzY5YTlmZjUyM2MzZTYwMQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MTZlZmI1OGZlNWQ1NjU2NmY3MzJkYzM2MmY0NDQ1MWIzMDI0NmQ0NzQ1NzE2
10
+ Nzc0MDBjZTY4MTQyYmU1ZTg2ZDU3OGYzNDUyZTAzNDlmNzc2NzA0MTQ5Y2Fi
11
+ MDM0MWMwNzBmOTY2MWVjOTEzOGM1ZjEzYTE3ZDhmMTkyYWQwYWQ=
12
+ data.tar.gz: !binary |-
13
+ YzI1M2FkZTUyOWVhMjVlOWE3MzlmZDAwZDYxY2RjZTk4YTY0OGJiZDM4YjE4
14
+ YTE5YTZhZTNiYzAwZGJjYWZlNTZhY2IzY2E2NjEwZDE5NzNjZjVjOGRmNWIy
15
+ MjdhMWE2ZmVlNmNiYzM1ZmFiZDM4ODlmZjU5ZjU1Y2UzYmQ3MGY=
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ kindah
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-1.9.3-p429
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 1.9.3
5
+ - jruby-19mode # JRuby in 1.9 mode
6
+ - rbx-19mode
7
+ env:
8
+ - COVERALLS=true
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'crystalline'
4
+
5
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Joe Fredette
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # kindah [![Gem Version](https://badge.fury.io/rb/kindah.png)](http://badge.fury.io/rb/kindah)[![Build Status](https://travis-ci.org/jfredett/kindah.png?branch=master)](http://travis-ci.org/jfredett/kindah)[![Code Climate](https://codeclimate.com/github/jfredett/kindah.png)](https://codeclimate.com/github/jfredett/kindah)[![Coverage Status](https://coveralls.io/repos/jfredett/kindah/badge.png?branch=master)](https://coveralls.io/r/jfredett/kindah)
2
+
3
+ Kindah is an implementation of Parameterized Classes for Ruby.
4
+
5
+ Kindah is unreleased, pre-alpha software, use at your own risk.
6
+
7
+ Most of this README should work, but it's not yet well-tested, so be careful.
8
+
9
+ ## Description
10
+
11
+ A parameterized class is a 'higher order' class, it's similar to (but definitely
12
+ not equivalent or as powerful as) a dependently-typed class. If you're familiar
13
+ with type theory (and particularly with haskell), it's analogous to a 'kind',
14
+ but one rank lower. So whereas a rank-2 type is the 'type of types' and is built
15
+ of types and other 'kinds' (ie, rank-1 and rank-2 types), these parameterized
16
+ classes occupy some of the same space as dependent types (ie, rank-1 types which
17
+ are parameterized by rank-0 types (that is, values)).
18
+
19
+ This library is not a rigorous implementation of any type system, it's merely
20
+ inspired by the concept, and built for a very specific purpose. The static
21
+ injection pattern.
22
+
23
+ ### Static Injection
24
+
25
+ Static injection is a type of dependency injection which happens once, generally
26
+ at compile-time, or at interpretation/loading time for interpeted languages.
27
+ Compared with setter/constructor injection, this is more akin to setter
28
+ injection, but where the setter is promoted to the class itself. This is useful
29
+ particularly where you need multiple versions of the same class which have
30
+ different 'engines' which drive them. A motivating example is an Indexing
31
+ interface for a database. Consider
32
+
33
+
34
+ class Index
35
+ attr_reader :indexing_engine
36
+
37
+ def initialize(indexing_engine = DefaultIndexingEnging)
38
+ @indexing_engine = indexing_engine.new
39
+ end
40
+
41
+ def insert(data)
42
+ indexing_engine.insert(data)
43
+ end
44
+
45
+ #snip
46
+ end
47
+
48
+ #later
49
+
50
+ Index.new(BTree)
51
+
52
+ Here, we use constructor injection to manage which indexing engine to use,
53
+ however, this leaks the abstraction to an inappropriate place, it should never
54
+ be possible to change the engine after it's been chosen the first time. In
55
+ particular it expresses the wrong relationship. An index does not _have a_
56
+ engine, an index, in some sense, _is it's_ engine. That is, a BTree Index _is_
57
+ a BTree, with some pleasant interface adhered to it. Similarly, a Hash Index
58
+ _is_ fundamentally a hashtable, with the _same pleasant interface_.
59
+
60
+ One method for accomplishing this type of abstraction in ruby is via modules,
61
+ but Modules ought to expose cross-cutting functionality and ought to bear no
62
+ statefullness of their own, and in the case of Index, there is a fair amount of
63
+ desire to include some statefulness. Further, an aesthetic desire exists to
64
+ invert this relationship to better express the dominant status of the _Index_
65
+ over it's _Engine_. The solution that Kindah offers looks something like this:
66
+
67
+ parameterized_class :Index do |engine|
68
+ def indexing_engine
69
+ @engine ||= engine.new
70
+ end
71
+
72
+ def insert(data)
73
+ indexing_engine.insert(data)
74
+ end
75
+
76
+ #snip
77
+ end
78
+
79
+ #later
80
+
81
+ Index(BTree).new
82
+
83
+ So, looking at that last line, you're probably thinking, "Okay, so what? You
84
+ moved the BTree bit to the left." Yes. Precisely, I've also imposed a set of
85
+ restrictions and implemented some pleasant features for talking about
86
+ `Index(BTree)` as a type in it's own right. For instance,
87
+ `Index(BTree).new.is_a? Index` will return `true`, but `Index(BTree).new.is_a?
88
+ Index(HashTable)` will return `false`. Similarly, depending on defaults provided
89
+ to the above block, `Index` will be an instantiable class.
90
+
91
+
92
+ ### Other uses
93
+
94
+ In this way you can hopefully see how to use this to manage some basic
95
+ dependency injection problems, however, Kindah lends itself to other uses, one
96
+ of the chief uses is metaprogramming based on values (rather than classes).
97
+
98
+ Consider the implementation of a classic Dependent Type, the finite vector.
99
+
100
+ A Vector is a list of length `n` with `O(1)` access to any element of the list
101
+ for read or write, and at most `O(n)` memory use. A simple implementation in
102
+ ruby is to metaprogram `n` 'slots' into a class, and wrap them in an interface
103
+ so that the usual `#[]` and `#[]=` operations work as expected, with bounds
104
+ checking and the like.
105
+
106
+ An implementation follows using pure ruby and using Kindah.
107
+
108
+ #pure ruby
109
+ class Vector
110
+ attr_reader :order
111
+
112
+ def initialize(order)
113
+ @order = order
114
+ end
115
+
116
+ def [](slot)
117
+ bounds_check! slot
118
+ instance_variable_get("@slot_#{slot}")
119
+ end
120
+
121
+ def []=(slot, value)
122
+ bounds_check! slot
123
+ instance_variable_set("@slot_#{slot}", value)
124
+ end
125
+
126
+ private
127
+
128
+ def bounds_check!(value)
129
+ 0 <= value && value < order
130
+ end
131
+ end
132
+
133
+ #usage
134
+
135
+ v = Vector.new(2)
136
+ v[0] = :test_value
137
+ v[1] = :please_ignore
138
+
139
+ v[0] #=> :test_value
140
+
141
+ #with kindah
142
+
143
+ parameterized_class :Vector do |size|
144
+ def order
145
+ size
146
+ end
147
+
148
+ def [](slot)
149
+ bounds_check! slot
150
+ instance_variable_get("@slot_#{slot}")
151
+ end
152
+
153
+ def []=(slot, value)
154
+ bounds_check! slot
155
+ instance_variable_set("@slot_#{slot}", value)
156
+ end
157
+
158
+ private
159
+
160
+ def bounds_check!(value)
161
+ 0 <= value && value < order
162
+ end
163
+ end
164
+
165
+ v = Vector(2).new
166
+ v[0] = :test_value
167
+ v[1] = :please_ignore
168
+
169
+ v[0] #=> :test_value
170
+
171
+ So... what's the difference? It's basically the same, arguably the latter is
172
+ more complicated.
173
+
174
+ Well, there are a couple advantages beyond the aesthetic API the latter
175
+ provides. First, in the former, we have to allocate some extra storage for the
176
+ order, whereas in the latter it's hardcoded (via metaprogramming) into the class
177
+ proper. Second, imagine an implementation of dot-product, which requires the two
178
+ vectors to be of the same length. In the former, we must examine orders
179
+ directly, eg:
180
+
181
+ class Vector
182
+ def dot(other)
183
+ raise unless order == other.order
184
+ #impl
185
+ end
186
+ end
187
+
188
+ In the latter case, we can check the class, which is only the same if the two
189
+ instances were created via the same `Vector(n)` function, eg:
190
+
191
+ parameterized_class :Vector do |order|
192
+ def dot(other)
193
+ raise unless other.is_a?(self.class)
194
+ #impl
195
+ end
196
+ end
197
+
198
+ This becomes more valuable when you have a few of these parameters to throw
199
+ around, for instance, imagine a `n x m` matrix class. In pure ruby, if you want
200
+ to add two instances together, you must first ensure that it matches both width
201
+ and height, with a kindah-based parameterized class, you can simply compare that
202
+ the classes are the same. Essentially, it's poor man's type checking.
203
+
204
+ ## Installation
205
+
206
+ Add this line to your application's Gemfile:
207
+
208
+ gem 'kindah'
209
+
210
+ And then execute:
211
+
212
+ $ bundle
213
+
214
+ Or install it yourself as:
215
+
216
+ $ gem install kindah
217
+
218
+ ## Usage
219
+
220
+ TODO: Write usage instructions here
221
+
222
+ ## Contributing
223
+
224
+ 1. Fork it
225
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
226
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
227
+ 4. Push to the branch (`git push origin my-new-feature`)
228
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'crystalline/rake'
data/kindah.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kindah/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "kindah"
8
+ spec.version = Kindah::VERSION
9
+ spec.authors = ["Joe Fredette"]
10
+ spec.email = ["jfredett@gmail.com"]
11
+ spec.description = %q{Kindah is an implementation of Parameterized Classes for Ruby.}
12
+ spec.summary = %q{Kindah is an implementation of Parameterized Classes for Ruby.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "katuv"
22
+ end
@@ -0,0 +1,11 @@
1
+ module Kindah
2
+ class ClassMethods
3
+ include Katuv::Node
4
+
5
+ terminal!
6
+
7
+ def self.name
8
+ 'class_methods'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module Kindah
2
+ class ClassTemplate
3
+ include Katuv::Node
4
+
5
+ terminal ClassMethods
6
+ terminal InstanceMethods
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module Kindah
2
+ class InstanceMethods
3
+ include Katuv::Node
4
+
5
+ terminal!
6
+
7
+ def self.name
8
+ 'instance_methods'
9
+ end
10
+ end
11
+ end
data/lib/kindah/ast.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'kindah/ast/instance_methods'
2
+ require 'kindah/ast/class_methods'
3
+
4
+ require 'kindah/ast/class_template'
@@ -0,0 +1,15 @@
1
+ module Kindah
2
+ class Cache
3
+ def self.[](*args)
4
+ storage[args]
5
+ end
6
+
7
+ def self.[]=(*args, last)
8
+ storage[args] = last
9
+ end
10
+
11
+ def self.storage
12
+ @storage ||= {}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,54 @@
1
+ module Kindah
2
+ class Compiler
3
+ extend Forwardable
4
+
5
+ def initialize(ast)
6
+ @ast = ast
7
+ end
8
+
9
+ def class_methods
10
+ safe_fetch ClassMethods
11
+ end
12
+
13
+ def instance_methods
14
+ safe_fetch InstanceMethods
15
+ end
16
+
17
+ delegate [:arity, :block, :children] => :@ast
18
+ def class_name
19
+ @ast.name
20
+ end
21
+
22
+ def each_parameter
23
+ class_methods.parameters.each.with_index do |(_, name), idx|
24
+ yield name, idx if block_given?
25
+ end
26
+ end
27
+
28
+ def compile!(location = Object)
29
+ compiler = self
30
+
31
+ location.send(:define_method, compiler.class_name) do |*args|
32
+ Kindah::Cache[compiler.class_name, *args] ||= Class.new do
33
+ compiler.each_parameter do |name, idx|
34
+ define_singleton_method(name) { args[idx] }
35
+ define_method(name) { self.class.send(name) }
36
+ end
37
+
38
+ #because ruby is weird...
39
+ instance_eval &compiler.class_methods
40
+ class_eval &compiler.instance_methods
41
+ end
42
+ end
43
+
44
+ nil
45
+ end
46
+
47
+ private
48
+
49
+ def safe_fetch(klass)
50
+ return proc {} unless children.has_key?(klass)
51
+ children[klass].block
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,3 @@
1
+ module Kindah
2
+ VERSION = "0.0.1"
3
+ end
data/lib/kindah.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "kindah/version"
2
+
3
+ require 'katuv'
4
+ require 'forwardable'
5
+ require 'singleton'
6
+
7
+ require 'kindah/ast'
8
+ require 'kindah/cache'
9
+ require 'kindah/compiler'
10
+
11
+ module Kindah
12
+ def self.class_template(name, opts={}, &block)
13
+ Kindah::ClassTemplate.new(name, opts.merge(parent: nil), &block)
14
+ end
15
+
16
+ def self.class_template!(name, opts={}, &block)
17
+ compile! class_template(name, opts, &block)
18
+ end
19
+
20
+ def self.compile!(template)
21
+ Kindah::Compiler.new(template).compile!
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Kindah do
4
+ before :all do
5
+ Kindah.class_template! :Test do
6
+ class_methods do |bar|
7
+ def foo_class
8
+ bar + 1
9
+ end
10
+ end
11
+
12
+ instance_methods do
13
+ def foo_instance
14
+ bar + 1
15
+ end
16
+
17
+ def initialize
18
+ @ivar = 1
19
+ end
20
+
21
+ attr_reader :ivar
22
+ end
23
+ end
24
+ end
25
+
26
+ subject(:test_instance) { Test(1).new }
27
+
28
+ it { should respond_to :bar }
29
+ it { should respond_to :foo_instance }
30
+ it { should respond_to :ivar }
31
+
32
+ its(:ivar) { should == 1 }
33
+
34
+ its(:class) { should respond_to :bar }
35
+ its(:class) { should respond_to :foo_class }
36
+ end
@@ -0,0 +1,13 @@
1
+ require 'crystalline/spec'
2
+
3
+ #include helpers
4
+ Dir["./spec/helpers/*.rb"].each { |file| require file }
5
+
6
+ #include shared examples
7
+ Dir["./spec/shared/*_examples.rb"].each { |file| require file }
8
+
9
+ Coveralls.wear! if ENV['COVERALLS']
10
+
11
+ Crystalline::Spec.install!
12
+
13
+ require 'kindah'
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kindah
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe Fredette
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: katuv
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Kindah is an implementation of Parameterized Classes for Ruby.
28
+ email:
29
+ - jfredett@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .gitignore
35
+ - .ruby-gemset
36
+ - .ruby-version
37
+ - .travis.yml
38
+ - Gemfile
39
+ - LICENSE.txt
40
+ - README.md
41
+ - Rakefile
42
+ - kindah.gemspec
43
+ - lib/kindah.rb
44
+ - lib/kindah/ast.rb
45
+ - lib/kindah/ast/class_methods.rb
46
+ - lib/kindah/ast/class_template.rb
47
+ - lib/kindah/ast/instance_methods.rb
48
+ - lib/kindah/cache.rb
49
+ - lib/kindah/compiler.rb
50
+ - lib/kindah/version.rb
51
+ - spec/integration/parameterized_class_spec.rb
52
+ - spec/spec_helper.rb
53
+ homepage: ''
54
+ licenses:
55
+ - MIT
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.0.6
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Kindah is an implementation of Parameterized Classes for Ruby.
77
+ test_files:
78
+ - spec/integration/parameterized_class_spec.rb
79
+ - spec/spec_helper.rb