funkr 0.0.23 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,13 @@
1
+ Funkr brings some common functional programming constructs to ruby.
2
+
3
+ In particular, it offers a simple mechanism to create Algebraïc Data
4
+ Types and do pattern matching on them. For an exemple
5
+ implementation, {Funkr::Types see provided classes}.
6
+
7
+ It also provide modules for common categories (Monoid, Monad,
8
+ Functor, Applicative ...), and extends common types to support
9
+ categories they belongs to (Array, Hash ...). Categories can also be
10
+ used with custom types, {Funkr::Types see provided classes}.
11
+
12
+ To get started, we recommand you to read the tests, and get feets wet
13
+ with provided Algebraic Data Types (like Maybe).
@@ -1,8 +1,18 @@
1
+ # -*- coding: utf-8 -*-
1
2
  if RUBY_VERSION.split(".")[0..1] == ["1","8"] then
2
3
  require 'funkr/compat/1.8'
3
4
  end
4
5
 
5
- # Funkr brings some common functional programming constructs to ruby
6
+ # Funkr brings some common functional programming constructs to ruby.
7
+ #
8
+ # In particular, it offers a simple mechanism to create Algebraïc Data
9
+ # Types and do pattern matching on them. For an exemple
10
+ # implementation, {Funkr::Types see provided classes}.
11
+ #
12
+ # It also provide modules for common categories (Monoid, Monad,
13
+ # Functor, Applicative ...), and extends common types to support
14
+ # categories they belongs to (Array, Hash ...). Categories can also be
15
+ # used with custom types, {Funkr::Types see provided classes}.
6
16
  module Funkr
7
-
17
+
8
18
  end
@@ -6,6 +6,8 @@ module Funkr
6
6
  # declare constructors with #adt
7
7
  class ADT
8
8
 
9
+ MATCHER = Funkr::Matchers::SafeMatcher
10
+
9
11
  def initialize(const, *data)
10
12
  @const, @data = const, data
11
13
  end
@@ -25,12 +27,13 @@ module Funkr
25
27
  # on.just{|x| puts x}
26
28
  # on.nothing{ }
27
29
  # end
28
- def match
29
- m = self.class.matcher.new(normal_form)
30
- yield m
31
- m.run_match
30
+ def match(&block)
31
+ self.class.matcher.match_with(normal_form, &block)
32
32
  end
33
-
33
+
34
+ def unsafe_const; @const; end
35
+ def unsafe_data; @data; end
36
+
34
37
  def to_s
35
38
  format("{%s%s%s}",
36
39
  @const,
@@ -56,7 +59,7 @@ module Funkr
56
59
  end
57
60
 
58
61
  def self.build_matcher(constructs)
59
- @matcher = Class.new(Funkr::Matcher) do
62
+ @matcher = Class.new(MATCHER) do
60
63
  build_matchers(constructs)
61
64
  end
62
65
  end
@@ -1,34 +1,45 @@
1
1
  module Funkr
2
+
2
3
  # Should not be used directly
3
- class Matcher
4
-
5
- def initialize(match)
6
- @const, *@data = match
7
- @runner = nil
8
- @undefined = self.class.constructs.clone
9
- end
10
-
11
- def self.build_matchers(constructs)
12
- @constructs = constructs
13
- constructs.each do |c|
14
- name, *data = c
15
- define_method(name) do |&b|
16
- @undefined.delete(name)
17
- if @const == name then
18
- @runner = b
4
+ module Matchers
5
+
6
+ class SafeMatcher
7
+
8
+ def initialize(match)
9
+ @const, *@data = match
10
+ @runner = nil
11
+ @undefined = self.class.constructs.clone
12
+ end
13
+
14
+ def self.build_matchers(constructs)
15
+ @constructs = constructs
16
+ constructs.each do |c|
17
+ name, *data = c
18
+ define_method(name) do |&b|
19
+ @undefined.delete(name)
20
+ if @const == name then
21
+ @runner = b
22
+ end
19
23
  end
20
24
  end
21
25
  end
22
- end
23
-
24
- def self.constructs; @constructs; end
25
-
26
- def run_match
27
- if @undefined.any? then
28
- raise "Incomplete match, missing : #{@undefined.join(" ")}"
26
+
27
+ def self.constructs; @constructs; end
28
+
29
+ def self.match_with(normal_form) # &block
30
+ m = self.new(normal_form)
31
+ yield m
32
+ m.run_match
33
+ end
34
+
35
+ def run_match
36
+ if @undefined.any? then
37
+ raise "Incomplete match, missing : #{@undefined.join(" ")}"
38
+ end
39
+ @runner.call(*@data)
29
40
  end
30
- @runner.call(*@data)
41
+
31
42
  end
32
-
43
+
33
44
  end
34
45
  end
@@ -1,7 +1,11 @@
1
1
  module Funkr
2
2
  module Categories
3
+
4
+ # Functors for which alternative (OR) behaviour can be defined
3
5
  module Alternative
4
6
 
7
+ # Provide an alternative. The type must be as follow :
8
+ # Functor(A).or_else{ Functor(A) } : Functor(A)
5
9
  def or_else
6
10
  raise "Alternative#or_else not implemented"
7
11
  end
@@ -1,16 +1,25 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module Funkr
2
3
  module Categories
4
+
5
+ # Functors that can contain a function and be applied to functors
6
+ # containing parameters for the function
3
7
  module Applicative
4
-
8
+
9
+ # Apply the function living inside the functor . The type must be as follow :
10
+ # Functor(λ(A) : B).apply(Functor(A)) : Functor(B)
5
11
  def apply
6
12
  raise "Applicative#apply not implemented"
7
13
  end
8
14
 
9
15
  module ClassMethods
16
+ # Curryfy the lambda block, and lift it into the functor
10
17
  def curry_lift_proc(&block)
11
18
  self.pure(block.curry)
12
19
  end
13
20
 
21
+ # Curryfy the lambda block over N parameter, lifting it to
22
+ # a lambda over N functors
14
23
  def full_lift_proc(&block)
15
24
  lambda do |*args|
16
25
  args.inject(curry_lift_proc(&block)) do |a,e|
@@ -19,6 +28,7 @@ module Funkr
19
28
  end
20
29
  end
21
30
 
31
+ # Lift the block and call parameters on it.
22
32
  def lift_with(*args, &block)
23
33
  full_lift_proc(&block).call(*args)
24
34
  end
@@ -1,7 +1,12 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module Funkr
2
3
  module Categories
4
+
5
+ # A functor is a container that can be mapped over
3
6
  module Functor
4
-
7
+
8
+ # Map over the constructor. The type must be as follow :
9
+ # Functor(A).map{|A| λ(A) : B} : Functor(B)
5
10
  def map
6
11
  raise "Functor#map not implemented"
7
12
  end
@@ -1,7 +1,21 @@
1
+ # -*- coding: utf-8 -*-
1
2
  module Funkr
2
3
  module Categories
4
+
5
+ # A functor can also be made an instance of Monad if you can
6
+ # define a bind operation on it. The bind operation must follow
7
+ # the monads laws :
8
+ # - Functor.unit(x).bind(f) == f.call(X)
9
+ # - monad.bind{|x| Functor.unit(x)} == monad
10
+ # - monad.bind{|x| f(monad).bind(g)} == monad.bind{|x| f(x)}.bind{|x| g(x)}
11
+ #
12
+ # Usually you will want your type to be a monad if you need to
13
+ # chain functions returning your type, and want to implement
14
+ # chaining logic once and for all (in bind).
3
15
  module Monad
4
-
16
+
17
+ # Bind operation on monads. The type must be as follow :
18
+ # Monad(A).bind{|A| λ(A) : Monad(B)} : Monad(B)
5
19
  def bind(&block)
6
20
  raise "Monad#bind not implemented"
7
21
  end
@@ -1,7 +1,8 @@
1
1
  module Funkr
2
2
  module Categories
3
+ # Monoids are types that can be added and have a zero element.
3
4
  module Monoid
4
-
5
+
5
6
  def mplus
6
7
  raise "Monoid#mplus not implemented"
7
8
  end
@@ -1,8 +1,19 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'funkr/adt/adt'
2
3
  require 'funkr/categories'
3
4
 
4
5
  module Funkr
5
6
  module Types
7
+
8
+ # Algebraïc Data Type representing the possibility of a missing
9
+ # value : nothing. It cleanly replace the 'nil' paradigm often
10
+ # found in ruby code, and often leading to bugs. The Maybe type
11
+ # belongs to multiple categories, making it extremly expressive to
12
+ # use (functor, applicative, alternative, monad, monoid ... !).
13
+ #
14
+ # You will get maximum performance and maximum expressiveness if
15
+ # you can use provided high level functions (map, apply, or_else,
16
+ # ...) instead of pattern-matching yourself.
6
17
  class Maybe < ADT
7
18
 
8
19
  include Funkr::Categories
@@ -13,13 +24,24 @@ module Funkr
13
24
 
14
25
  include Functor
15
26
 
27
+ # Maybe.nothing.map{|x| something(x)} # => nothing
28
+ # Maybe.just(x).map{|x| something(x)} # => just something(x)
29
+ #
30
+ # {Funkr::Categories::Functor#map see functor map}
16
31
  def map(&block)
17
- self.match do |on|
18
- on.just {|v| self.class.just(yield(v))}
19
- on.nothing { self }
20
- end
32
+ # This implementation isn't safe but is a bit faster than the
33
+ # safe one. A safe implementation would be as follow :
34
+ # self.match do |on|
35
+ # on.just {|v| self.class.just(yield(v))}
36
+ # on.nothing { self }
37
+ # end
38
+ if self.just? then self.class.just(yield(unsafe_content))
39
+ else self end
21
40
  end
22
41
 
42
+ include Applicative
43
+ extend Applicative::ClassMethods
44
+
23
45
  # Maybe can be made an applicative functor, for example :
24
46
  # f = Maybe.curry_lift_proc{|x,y| x + y}
25
47
  # a = Maybe.just(3)
@@ -27,23 +49,28 @@ module Funkr
27
49
  # c = Maybe.nothing
28
50
  # f.apply(a).apply(b) => Just 7
29
51
  # f.apply(a).apply(c) => Nothing
30
- include Applicative
31
- extend Applicative::ClassMethods
32
-
52
+ #
53
+ # {Funkr::Categories::Applicative#apply see applicative apply}
33
54
  def apply(to)
34
- self.match do |f_on|
35
- f_on.just do |f|
36
- to.match do |t_on|
37
- t_on.just {|t| self.class.unit(f.call(t)) }
38
- t_on.nothing { to }
39
- end
40
- end
41
- f_on.nothing { self }
42
- end
55
+ # This implementation isn't safe but is a bit faster than the
56
+ # safe one. A safe implementation would be as follow :
57
+ # self.match do |f_on|
58
+ # f_on.just do |f|
59
+ # to.match do |t_on|
60
+ # t_on.just {|t| self.class.unit(f.call(t)) }
61
+ # t_on.nothing { to }
62
+ # end
63
+ # end
64
+ # f_on.nothing { self }
65
+ # end
66
+ if self.just? and to.just? then
67
+ self.class.unit(self.unsafe_content.call(to.unsafe_content))
68
+ else self.class.nothing end
43
69
  end
44
70
 
45
71
  include Alternative
46
72
 
73
+ # {Funkr::Categories::Alternative#or_else see alternative or_else}
47
74
  def or_else(&block)
48
75
  self.match do |on|
49
76
  on.just {|v| self}
@@ -55,6 +82,7 @@ module Funkr
55
82
  include Monoid
56
83
  extend Monoid::ClassMethods
57
84
 
85
+ # {Funkr::Categories::Monoid#mplus see monoid mplus}
58
86
  def mplus(m_y)
59
87
  self.match do |x_on|
60
88
  x_on.nothing { m_y }
@@ -71,13 +99,24 @@ module Funkr
71
99
  include Monad
72
100
  extend Monad::ClassMethods
73
101
 
102
+ # {Funkr::Categories::Monad#bind see monad bind}
74
103
  def bind(&block)
75
- self.match do |on|
76
- on.just {|v| yield(v)}
77
- on.nothing {self}
78
- end
104
+ # This implementation isn't safe but is a bit faster than the
105
+ # safe one. A safe implementation would be as follow :
106
+ # self.match do |on|
107
+ # on.just {|v| yield(v)}
108
+ # on.nothing {self}
109
+ # end
110
+ if self.just? then yield(self.unsafe_content)
111
+ else self end
79
112
  end
80
113
 
114
+ # Unbox a maybe value. You must provide a default in case of
115
+ # nothing. This method is not as safe as the others, as it will
116
+ # escape the content from the Maybe type safety.
117
+ #
118
+ # Maybe.just(5).unbox(:foobar) # => 5
119
+ # Maybe.nothing.unbox(:foobar) # => :foobar
81
120
  def unbox(default=nil)
82
121
  self.match do |on|
83
122
  on.just {|v| v }
@@ -91,6 +130,10 @@ module Funkr
91
130
  alias mzero nothing
92
131
  end
93
132
 
133
+ # unsafe access to content, for performance purpose only
134
+ def unsafe_content; self.unsafe_data.first; end
135
+
136
+ # Box nil as nothing, and the rest as just x
94
137
  def self.box(value)
95
138
  if value.nil? then self.nothing
96
139
  else self.just(value) end
@@ -1,16 +1,27 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  module Funkr
3
3
  module Types
4
-
5
- class SimpleRecord < Array
6
- ### usage : r = SimpleRecord.new(Hash), then r.field
7
- ### r = SimpleRecord.new( name: "Paul", age: 27 )
8
- ### r.name => "Paul" ; r.age => 27
9
- ### name, age = r
10
4
 
11
- ### other usage :
12
- ### class Person < SimpleRecord; fields :name, :age; end
13
- ### Person.new( name: Paul ) => Error, missing :age
5
+ # Simple records are a simple way to create records with named AND
6
+ # positional fields. Records can be updated on a per-field basis
7
+ # and pattern-matched as arrays. SimpleRecord has the following
8
+ # advantages above plain Hash :
9
+ #
10
+ # - fields are strict, you can't update an unexisting field by
11
+ # mistyping it or by combining it with a different structure
12
+ # - you have easy access to fields : named AND positional
13
+ #
14
+ # usage : r = SimpleRecord.new(Hash), then r.field
15
+ # r = SimpleRecord.new( name: "Paul", age: 27 )
16
+ # r.name # => "Paul"
17
+ # r.age # => 27
18
+ # name, age = r # => [ "Paul", 27 ]
19
+ # r.with(age: 29) # => [ "Paul", 29 ]
20
+ #
21
+ # other usage :
22
+ # class Person < SimpleRecord; fields :name, :age; end
23
+ # Person.new( name: Paul ) => Error, missing :age
24
+ class SimpleRecord < Array
14
25
 
15
26
  class << self; attr_accessor :fields_list; end
16
27
  @fields_list = nil
@@ -25,6 +36,8 @@ module Funkr
25
36
  end
26
37
  end
27
38
 
39
+ # Create a new SimpleRecord. Pass a Hash of keys and associated
40
+ # values if you want an inline record.
28
41
  def initialize(key_vals)
29
42
  fields = self.class.fields_list
30
43
  if not fields.nil? then # record paramétré
@@ -46,11 +59,13 @@ module Funkr
46
59
  end
47
60
  end
48
61
 
62
+ # Update a simple record non-destructively
49
63
  def with(new_key_vals)
50
64
  check_keys(new_key_vals.keys)
51
65
  self.class.new(@key_vals.merge(new_key_vals))
52
66
  end
53
67
 
68
+ # Update a simple record destructively !!
54
69
  def update!(new_key_vals)
55
70
  check_keys(new_key_vals.keys)
56
71
  @key_vals.merge!(new_key_vals)
@@ -1,3 +1,3 @@
1
1
  module Funkr
2
- VERSION = "0.0.23"
2
+ VERSION = "0.0.24"
3
3
  end
@@ -28,7 +28,8 @@ class TestMaybe < Test::Unit::TestCase
28
28
  def test_or_else
29
29
  assert_equal(j(10), n.or_else{j(10)})
30
30
  assert_equal(j(4), j(4).or_else{j(2)})
31
- # assert_nothing_raised(j(2).or_else{raise 'should not be raised'})
31
+ assert_nothing_raised{ j(5).or_else{raise 'should not be raised'} }
32
+ assert_raise(RuntimeError){ n.or_else{raise 'should not be raised'} }
32
33
  end
33
34
 
34
35
  def test_full_lift
metadata CHANGED
@@ -1,38 +1,51 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: funkr
3
- version: !ruby/object:Gem::Version
4
- version: 0.0.23
5
- prerelease:
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 24
9
+ version: 0.0.24
6
10
  platform: ruby
7
- authors:
11
+ authors:
8
12
  - Paul Rivier
9
13
  autorequire:
10
14
  bindir: bin
11
15
  cert_chain: []
12
- date: 2011-10-21 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
16
+
17
+ date: 2011-12-06 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
15
21
  name: rake
16
- requirement: &18284740 !ruby/object:Gem::Requirement
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
17
24
  none: false
18
- requirements:
25
+ requirements:
19
26
  - - ~>
20
- - !ruby/object:Gem::Version
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 9
31
+ - 2
21
32
  version: 0.9.2
22
33
  type: :development
23
- prerelease: false
24
- version_requirements: *18284740
25
- description: ! '[EXPERIMENTAL] Some functionnal constructs for ruby, like ADT, functors,
26
- monads'
27
- email:
34
+ version_requirements: *id001
35
+ description: "[EXPERIMENTAL] Some functionnal constructs for ruby, like ADT, functors, monads"
36
+ email:
28
37
  - paul (dot) r (dot) ml (at) gmail (dot) com
29
38
  executables: []
39
+
30
40
  extensions: []
41
+
31
42
  extra_rdoc_files: []
32
- files:
43
+
44
+ files:
33
45
  - .gitignore
34
46
  - .travis.yml
35
47
  - Gemfile
48
+ - README
36
49
  - Rakefile
37
50
  - funkr.gemspec
38
51
  - lib/funkr.rb
@@ -61,28 +74,37 @@ files:
61
74
  - test/test_hash.rb
62
75
  - test/test_maybe.rb
63
76
  - test/test_simple_records.rb
77
+ has_rdoc: true
64
78
  homepage: http://github.com/paul-r-ml/funkr
65
79
  licenses: []
80
+
66
81
  post_install_message:
67
82
  rdoc_options: []
68
- require_paths:
83
+
84
+ require_paths:
69
85
  - lib
70
- required_ruby_version: !ruby/object:Gem::Requirement
86
+ required_ruby_version: !ruby/object:Gem::Requirement
71
87
  none: false
72
- requirements:
73
- - - ! '>='
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
95
  none: false
78
- requirements:
79
- - - ! '>='
80
- - !ruby/object:Gem::Version
81
- version: '0'
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ segments:
100
+ - 0
101
+ version: "0"
82
102
  requirements: []
103
+
83
104
  rubyforge_project: funkr
84
- rubygems_version: 1.8.10
105
+ rubygems_version: 1.3.7
85
106
  signing_key:
86
107
  specification_version: 3
87
- summary: ! '[EXPERIMENTAL] Some functionnal constructs for ruby'
108
+ summary: "[EXPERIMENTAL] Some functionnal constructs for ruby"
88
109
  test_files: []
110
+