funkr 0.0.2
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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/funkr.gemspec +21 -0
- data/lib/funkr.rb +3 -0
- data/lib/funkr/adt/adt.rb +57 -0
- data/lib/funkr/adt/matcher.rb +33 -0
- data/lib/funkr/categories.rb +5 -0
- data/lib/funkr/categories/alternative.rb +11 -0
- data/lib/funkr/categories/applicative.rb +59 -0
- data/lib/funkr/categories/functor.rb +18 -0
- data/lib/funkr/categories/monad.rb +23 -0
- data/lib/funkr/categories/monoid.rb +20 -0
- data/lib/funkr/extensions/fixnum.rb +13 -0
- data/lib/funkr/types.rb +2 -0
- data/lib/funkr/types/failable.rb +81 -0
- data/lib/funkr/types/maybe.rb +95 -0
- data/lib/funkr/version.rb +3 -0
- data/test/tests.rb +49 -0
- metadata +83 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/funkr.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "funkr/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "funkr"
|
7
|
+
s.version = Funkr::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Paul Rivier"]
|
10
|
+
s.email = ["paul (dot) r (dot) ml (at) gmail (dot) com"]
|
11
|
+
s.homepage = "http://github.com/paul-r-ml/funkr"
|
12
|
+
s.summary = %q{[EXPERIMENTAL] Some functionnal constructs for ruby}
|
13
|
+
s.description = %q{[EXPERIMENTAL] Some functionnal constructs for ruby, like ADT, functors, monads}
|
14
|
+
|
15
|
+
s.rubyforge_project = "funkr"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
data/lib/funkr.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require "funkr/adt/matcher"
|
2
|
+
|
3
|
+
module Funkr
|
4
|
+
class ADT
|
5
|
+
|
6
|
+
def initialize(const, *data)
|
7
|
+
@const, @data = const, data
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.adt(*constructs)
|
11
|
+
build_adt(constructs)
|
12
|
+
build_matcher(constructs)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.match_method=(method)
|
16
|
+
@match_method = method
|
17
|
+
end
|
18
|
+
|
19
|
+
self.match_method = :safe
|
20
|
+
|
21
|
+
def self.matcher; @matcher; end
|
22
|
+
|
23
|
+
def match
|
24
|
+
m = self.class.matcher.new(normal_form)
|
25
|
+
yield m
|
26
|
+
m.run_match
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
format("%s%s%s",
|
31
|
+
@const,
|
32
|
+
@data.any? ? " : " : "",
|
33
|
+
@data.map(&:inspect).join(" ") )
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :const, :data
|
39
|
+
|
40
|
+
def normal_form; [@const, *@data]; end
|
41
|
+
|
42
|
+
def self.build_adt(constructs)
|
43
|
+
constructs.each do |c,*d|
|
44
|
+
define_singleton_method(c) do |*data|
|
45
|
+
self.new(c,*data)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.build_matcher(constructs)
|
51
|
+
@matcher = Class.new(Funkr::Matcher) do
|
52
|
+
build_matchers(constructs)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Funkr
|
2
|
+
class Matcher
|
3
|
+
|
4
|
+
def initialize(match)
|
5
|
+
@const, *@data = match
|
6
|
+
@runner = nil
|
7
|
+
@undefined = self.class.constructs.clone
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.build_matchers(constructs)
|
11
|
+
@constructs = constructs
|
12
|
+
constructs.each do |c|
|
13
|
+
name, *data = c
|
14
|
+
define_method(name) do |&b|
|
15
|
+
@undefined.delete(name)
|
16
|
+
if @const == name then
|
17
|
+
@runner = b
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.constructs; @constructs; end
|
24
|
+
|
25
|
+
def run_match
|
26
|
+
if @undefined.any? then
|
27
|
+
raise "Incomplete match, missing : #{@undefined.join(" ")}"
|
28
|
+
end
|
29
|
+
@runner.call(*@data)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Funkr
|
2
|
+
module Categories
|
3
|
+
module Applicative
|
4
|
+
|
5
|
+
def apply
|
6
|
+
raise "Applicative#apply not implemented"
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def curry_lift_proc(&block)
|
11
|
+
self.pure(block.curry)
|
12
|
+
end
|
13
|
+
|
14
|
+
def full_lift_proc(&block)
|
15
|
+
lambda do |*args|
|
16
|
+
args.inject(curry_lift_proc(&block)) do |a,e|
|
17
|
+
a.apply(e)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def <=>(other)
|
25
|
+
proxy_comp(other){|a,b| a <=> b}
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
proxy_comp(other){|a,b| a == b}
|
30
|
+
end
|
31
|
+
|
32
|
+
def <(other)
|
33
|
+
proxy_comp(other){|a,b| a < b}
|
34
|
+
end
|
35
|
+
|
36
|
+
def <=(other)
|
37
|
+
proxy_comp(other){|a,b| a <= b}
|
38
|
+
end
|
39
|
+
|
40
|
+
def >(other)
|
41
|
+
proxy_comp(other){|a,b| a > b}
|
42
|
+
end
|
43
|
+
|
44
|
+
def >=(other)
|
45
|
+
proxy_comp(other){|a,b| a >= b}
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def proxy_comp(other,&block)
|
52
|
+
self.class.
|
53
|
+
curry_lift_proc(&block).
|
54
|
+
apply(self).apply(other)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Funkr
|
2
|
+
module Categories
|
3
|
+
module Monad
|
4
|
+
|
5
|
+
def bind(&block)
|
6
|
+
raise "Monad#bind not implemented"
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def unit
|
12
|
+
raise "Monad.unit not implemented"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def bind_(&block)
|
18
|
+
self.bind{|*args| yield}
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Funkr
|
2
|
+
module Categories
|
3
|
+
module Monoid
|
4
|
+
|
5
|
+
def mplus
|
6
|
+
raise "Monoid#mplus not implemented"
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def mzero
|
11
|
+
raise "Monoid#mzero not implemented"
|
12
|
+
end
|
13
|
+
|
14
|
+
def mconcat(list)
|
15
|
+
list.inject(mzero){|a,e| a.mplus(e)}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/funkr/types.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'funkr/adt/adt'
|
2
|
+
require 'funkr/categories'
|
3
|
+
|
4
|
+
module Funkr
|
5
|
+
module Types
|
6
|
+
class Failable < ADT
|
7
|
+
|
8
|
+
include Funkr::Categories
|
9
|
+
|
10
|
+
adt :ok, :failed
|
11
|
+
|
12
|
+
class << self
|
13
|
+
alias unit ok
|
14
|
+
alias pure ok
|
15
|
+
end
|
16
|
+
|
17
|
+
include Functor
|
18
|
+
|
19
|
+
def map(&block)
|
20
|
+
case self.const
|
21
|
+
when :ok then
|
22
|
+
Failable.ok(yield(*self.data))
|
23
|
+
else self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
include Applicative
|
28
|
+
|
29
|
+
def apply(to)
|
30
|
+
self.match do |f_on|
|
31
|
+
f_on.ok do |f|
|
32
|
+
to.match do |t_on|
|
33
|
+
t_on.ok {|t| Failable.ok(f.call(t)) }
|
34
|
+
t_on.failed { to }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
f_on.failed { self }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
include Alternative
|
43
|
+
|
44
|
+
def or_else(&block)
|
45
|
+
case self.const
|
46
|
+
when :ok then self
|
47
|
+
else yield
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
include Monoid
|
53
|
+
extend Monoid::ClassMethods
|
54
|
+
|
55
|
+
def mplus(m_y)
|
56
|
+
self.match do |x_on|
|
57
|
+
x_on.failed { m_y }
|
58
|
+
x_on.ok do |x|
|
59
|
+
m_y.match do |y_on|
|
60
|
+
y_on.failed { self }
|
61
|
+
y_on.ok {|y| Failable.ok(x.mplus(y))}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
include Monad
|
69
|
+
extend Monad::ClassMethods
|
70
|
+
|
71
|
+
def bind(&block)
|
72
|
+
case self.const
|
73
|
+
when :ok then yield(*self.data)
|
74
|
+
else self
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'funkr/adt/adt'
|
2
|
+
require 'funkr/categories'
|
3
|
+
|
4
|
+
module Funkr
|
5
|
+
module Types
|
6
|
+
class Maybe < ADT
|
7
|
+
|
8
|
+
include Funkr::Categories
|
9
|
+
|
10
|
+
adt :just, :nothing
|
11
|
+
|
12
|
+
### Categories
|
13
|
+
|
14
|
+
include Functor
|
15
|
+
|
16
|
+
def map(&block)
|
17
|
+
self.match do |on|
|
18
|
+
on.just {|v| Maybe.just(yield(v))}
|
19
|
+
on.nothing { self }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include Applicative
|
24
|
+
extend Applicative::ClassMethods
|
25
|
+
|
26
|
+
def apply(to)
|
27
|
+
self.match do |f_on|
|
28
|
+
f_on.just do |f|
|
29
|
+
to.match do |t_on|
|
30
|
+
t_on.just {|t| Maybe.unit(f.call(t)) }
|
31
|
+
t_on.nothing { to }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
f_on.nothing { self }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
include Alternative
|
39
|
+
|
40
|
+
def or_else(&block)
|
41
|
+
self.match do |on|
|
42
|
+
on.just {|v| self}
|
43
|
+
on.nothing { yield }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
include Monoid
|
49
|
+
extend Monoid::ClassMethods
|
50
|
+
|
51
|
+
def mplus(m_y)
|
52
|
+
self.match do |x_on|
|
53
|
+
x_on.nothing { m_y }
|
54
|
+
x_on.just do |x|
|
55
|
+
m_y.match do |y_on|
|
56
|
+
y_on.nothing { self }
|
57
|
+
y_on.just {|y| Maybe.just(x.mplus(y))}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
include Monad
|
65
|
+
extend Monad::ClassMethods
|
66
|
+
|
67
|
+
def bind(&block)
|
68
|
+
self.match do |on|
|
69
|
+
on.just {|v| yield(v)}
|
70
|
+
on.nothing {self}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
class << self
|
76
|
+
alias unit just
|
77
|
+
alias pure just
|
78
|
+
alias mzero nothing
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.box(value)
|
82
|
+
if value.nil? then self.nothing
|
83
|
+
else self.just(value) end
|
84
|
+
end
|
85
|
+
|
86
|
+
def unbox
|
87
|
+
self.match do |on|
|
88
|
+
on.just {|v| v }
|
89
|
+
on.nothing { nil }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/test/tests.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'funkr/types'
|
3
|
+
|
4
|
+
include Funkr::Types
|
5
|
+
|
6
|
+
m = Maybe.just(5)
|
7
|
+
|
8
|
+
puts(m.match do |on|
|
9
|
+
on.nothing{ Maybe.nothing }
|
10
|
+
on.just{|v| Maybe.just(v + 1) }
|
11
|
+
end.to_s)
|
12
|
+
|
13
|
+
puts(m.map{|v| v+1 })
|
14
|
+
|
15
|
+
|
16
|
+
n = Maybe.nothing
|
17
|
+
|
18
|
+
puts(n.match do |on|
|
19
|
+
on.nothing{ Maybe.nothing }
|
20
|
+
on.just{|v| Maybe.just(v + 1) }
|
21
|
+
end.to_s)
|
22
|
+
|
23
|
+
|
24
|
+
puts "\n> Curry lift"
|
25
|
+
f = Maybe.curry_lift_proc{|x,y| x + y}
|
26
|
+
puts f.apply(m).apply(m)
|
27
|
+
puts f.apply(m).apply(n)
|
28
|
+
puts f.apply(m).apply(n.or_else{Maybe.just(10)})
|
29
|
+
|
30
|
+
puts "\n> Full lift"
|
31
|
+
f = Maybe.full_lift_proc{|x,y| x + y}
|
32
|
+
puts f.call(m,m)
|
33
|
+
puts f.call(m,n)
|
34
|
+
|
35
|
+
# puts Maybe.mconcat([Maybe.just(10),
|
36
|
+
# Maybe.just(20),
|
37
|
+
# Maybe.nothing,
|
38
|
+
# Maybe.just(30)])
|
39
|
+
|
40
|
+
puts(m <=> m)
|
41
|
+
puts(m <=> (m.map{|v| v+1}))
|
42
|
+
puts(m <= (m.map{|v| v+1}))
|
43
|
+
puts(m <=> n)
|
44
|
+
|
45
|
+
puts "\n> Boxing and unboxing"
|
46
|
+
puts m.unbox
|
47
|
+
puts n.unbox
|
48
|
+
puts (Maybe.box(12)).unbox
|
49
|
+
puts (Maybe.box(nil)).unbox
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: funkr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Paul Rivier
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-03-03 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: "[EXPERIMENTAL] Some functionnal constructs for ruby, like ADT, functors, monads"
|
22
|
+
email:
|
23
|
+
- paul (dot) r (dot) ml (at) gmail (dot) com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- Gemfile
|
33
|
+
- Rakefile
|
34
|
+
- funkr.gemspec
|
35
|
+
- lib/funkr.rb
|
36
|
+
- lib/funkr/adt/adt.rb
|
37
|
+
- lib/funkr/adt/matcher.rb
|
38
|
+
- lib/funkr/categories.rb
|
39
|
+
- lib/funkr/categories/alternative.rb
|
40
|
+
- lib/funkr/categories/applicative.rb
|
41
|
+
- lib/funkr/categories/functor.rb
|
42
|
+
- lib/funkr/categories/monad.rb
|
43
|
+
- lib/funkr/categories/monoid.rb
|
44
|
+
- lib/funkr/extensions/fixnum.rb
|
45
|
+
- lib/funkr/types.rb
|
46
|
+
- lib/funkr/types/failable.rb
|
47
|
+
- lib/funkr/types/maybe.rb
|
48
|
+
- lib/funkr/version.rb
|
49
|
+
- test/tests.rb
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://github.com/paul-r-ml/funkr
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project: funkr
|
78
|
+
rubygems_version: 1.3.7
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: "[EXPERIMENTAL] Some functionnal constructs for ruby"
|
82
|
+
test_files: []
|
83
|
+
|