Module: Fear::PartialFunction

Included in:
Combined
Defined in:
lib/fear/partial_function.rb,
lib/fear/partial_function/any.rb,
lib/fear/partial_function/empty.rb,
lib/fear/partial_function/guard.rb,
lib/fear/partial_function/lifted.rb,
lib/fear/partial_function/or_else.rb,
lib/fear/partial_function/and_then.rb,
lib/fear/partial_function/combined.rb,
lib/fear/partial_function/guard/or.rb,
lib/fear/partial_function/guard/and.rb,
lib/fear/partial_function/guard/and3.rb

Overview

A partial function is a unary function defined on subset of all possible inputs. The method defined_at? allows to test dynamically if an arg is in the domain of the function.

Even if defined_at? returns true for given arg, calling call may still throw an exception, so the following code is legal:

@example
  Fear.case(->(_) { true }) { 1/0 }

It is the responsibility of the caller to call defined_at? before calling call, because if defined_at? is false, it is not guaranteed call will throw an exception to indicate an error guard. If an exception is not thrown, evaluation may result in an arbitrary arg.

The main distinction between PartialFunction and Proc is that the user of a PartialFunction may choose to do something different with input that is declared to be outside its domain. For example:

The method or_else allows chaining another partial function to handle input outside the declared domain

numbers = sample.map(is_even.or_else(is_odd).to_proc)

Examples:

sample = 1...10

is_even = Fear.case(->(arg) { arg % 2 == 0}) do |arg|
  "#{arg} is even"
end

is_odd = Fear.case(->(arg) { arg % 2 == 1}) do |arg|
  "#{arg} is odd"
end

See Also:

Defined Under Namespace

Classes: Any, Combined, Guard

Constant Summary collapse

EMPTY =
EmptyPartialFunction.new

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.and(*guards, &function) ⇒ Fear::PartialFunction

Creates partial function guarded by several condition. All guards should match.


156
157
158
# File 'lib/fear/partial_function.rb', line 156

def and(*guards, &function)
  PartialFunctionClass.new(Guard.and(guards), &function)
end

.or(*guards, &function) ⇒ Fear::PartialFunction

Creates partial function guarded by several condition. Any condition should match.


165
166
167
# File 'lib/fear/partial_function.rb', line 165

def or(*guards, &function)
  PartialFunctionClass.new(Guard.or(guards), &function)
end

Instance Method Details

#&(other) ⇒ Object

See Also:


140
141
142
# File 'lib/fear/partial_function.rb', line 140

def &(other)
  and_then(other)
end

#and_then(other) ⇒ Fear::PartialFunction #and_then(other) ⇒ Fear::PartialFunction #and_then(&other) ⇒ Fear::PartialFunction

Composes this partial function with a fallback partial function (or Proc) which gets applied where this partial function is not defined.

Overloads:

  • #and_then(other) ⇒ Fear::PartialFunction
    Note:

    calling #defined_at? on the resulting partial function may call the first partial function and execute its side effect. It is highly recommended to call #call_or_else instead of #defined_at?/#call for efficiency.

    Returns a partial function with the same domain as this partial function, which maps argument x to other.(self.call(x)).


129
130
131
132
133
134
135
136
137
# File 'lib/fear/partial_function.rb', line 129

def and_then(other = Utils::UNDEFINED, &block)
  Utils.with_block_or_argument("Fear::PartialFunction#and_then", other, block) do |fun|
    if fun.is_a?(Fear::PartialFunction)
      Combined.new(self, fun)
    else
      AndThen.new(self, &fun)
    end
  end
end

#call(arg) ⇒ any

This method is abstract.

Returns Calls this partial function with the given argument when it is contained in the function domain.

Raises:

  • (MatchError)

    when this partial function is not defined.


# File 'lib/fear/partial_function.rb', line 64


#call_or_else(arg) {|arg| ... } ⇒ Object

Note:

that expression pf.call_or_else(arg, &fallback) is equivalent to pf.defined_at?(arg) ? pf.(arg) : fallback.(arg) except that call_or_else method can be implemented more efficiently to avoid calling defined_at? twice.

Calls this partial function with the given argument when it is contained in the function domain. Calls fallback function where this partial function is not defined.

Yields:

  • (arg)

    if partial function not defined for this arg


88
89
90
91
92
93
94
# File 'lib/fear/partial_function.rb', line 88

def call_or_else(arg)
  if defined_at?(arg)
    call(arg)
  else
    yield arg
  end
end

#condition#===

This method is abstract.

describes the domain of partial function


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/fear/partial_function.rb', line 47

module PartialFunction
  autoload :AndThen, "fear/partial_function/and_then"
  autoload :Any, "fear/partial_function/any"
  autoload :Combined, "fear/partial_function/combined"
  autoload :EMPTY, "fear/partial_function/empty"
  autoload :Guard, "fear/partial_function/guard"
  autoload :Lifted, "fear/partial_function/lifted"
  autoload :OrElse, "fear/partial_function/or_else"

  # Checks if a value is contained in the function's domain.
  #
  # @param arg [any]
  # @return [Boolean]
  def defined_at?(arg)
    condition === arg
  end

  # @!method call(arg)
  # @param arg [any]
  # @return [any] Calls this partial function with the given argument when it
  #   is contained in the function domain.
  # @raise [MatchError] when this partial function is not defined.
  # @abstract

  # Converts this partial function to other
  #
  # @return [Proc]
  def to_proc
    proc { |arg| call(arg) }
  end

  # Calls this partial function with the given argument when it is contained in the function domain.
  # Calls fallback function where this partial function is not defined.
  #
  # @param arg [any]
  # @yield [arg] if partial function not defined for this +arg+
  #
  # @note that expression +pf.call_or_else(arg, &fallback)+ is equivalent to
  #   +pf.defined_at?(arg) ? pf.(arg) : fallback.(arg)+
  #   except that +call_or_else+ method can be implemented more efficiently to avoid calling +defined_at?+ twice.
  #
  def call_or_else(arg)
    if defined_at?(arg)
      call(arg)
    else
      yield arg
    end
  end

  # Composes this partial function with a fallback partial function which
  # gets applied where this partial function is not defined.
  #
  # @param other [PartialFunction]
  # @return [PartialFunction] a partial function which has as domain the union of the domains
  #   of this partial function and +other+.
  def or_else(other)
    OrElse.new(self, other)
  end

  # @see or_else
  def |(other)
    or_else(other)
  end

  # Composes this partial function with a fallback partial function (or Proc) which
  # gets applied where this partial function is not defined.
  #
  # @overload and_then(other)
  #   @param other [Fear::PartialFunction]
  #   @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
  #     argument +x+ to +other.(self.call(x))+.
  #   @note calling +#defined_at?+ on the resulting partial function may call the first
  #     partial function and execute its side effect. It is highly recommended to call +#call_or_else+
  #     instead of +#defined_at?+/+#call+ for efficiency.
  # @overload and_then(other)
  #   @param other [Proc]
  #   @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
  #     argument +x+ to +other.(self.call(x))+.
  # @overload and_then(&other)
  #   @param other [Proc]
  #   @return [Fear::PartialFunction]
  #
  def and_then(other = Utils::UNDEFINED, &block)
    Utils.with_block_or_argument("Fear::PartialFunction#and_then", other, block) do |fun|
      if fun.is_a?(Fear::PartialFunction)
        Combined.new(self, fun)
      else
        AndThen.new(self, &fun)
      end
    end
  end

  # @see and_then
  def &(other)
    and_then(other)
  end

  # Turns this partial function in Proc-like object, returning +Option+
  # @return [#call]
  def lift
    Lifted.new(self)
  end

  class << self
    # Creates partial function guarded by several condition.
    # All guards should match.
    # @param guards [<#===>]
    # @param function [Proc]
    # @return [Fear::PartialFunction]
    def and(*guards, &function)
      PartialFunctionClass.new(Guard.and(guards), &function)
    end

    # Creates partial function guarded by several condition.
    # Any condition should match.
    # @param guards [<#===>]
    # @param function [Proc]
    # @return [Fear::PartialFunction]
    def or(*guards, &function)
      PartialFunctionClass.new(Guard.or(guards), &function)
    end
  end
end

#defined_at?(arg) ⇒ Boolean

Checks if a value is contained in the function's domain.


60
61
62
# File 'lib/fear/partial_function.rb', line 60

def defined_at?(arg)
  condition === arg
end

#function#call

This method is abstract.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/fear/partial_function.rb', line 47

module PartialFunction
  autoload :AndThen, "fear/partial_function/and_then"
  autoload :Any, "fear/partial_function/any"
  autoload :Combined, "fear/partial_function/combined"
  autoload :EMPTY, "fear/partial_function/empty"
  autoload :Guard, "fear/partial_function/guard"
  autoload :Lifted, "fear/partial_function/lifted"
  autoload :OrElse, "fear/partial_function/or_else"

  # Checks if a value is contained in the function's domain.
  #
  # @param arg [any]
  # @return [Boolean]
  def defined_at?(arg)
    condition === arg
  end

  # @!method call(arg)
  # @param arg [any]
  # @return [any] Calls this partial function with the given argument when it
  #   is contained in the function domain.
  # @raise [MatchError] when this partial function is not defined.
  # @abstract

  # Converts this partial function to other
  #
  # @return [Proc]
  def to_proc
    proc { |arg| call(arg) }
  end

  # Calls this partial function with the given argument when it is contained in the function domain.
  # Calls fallback function where this partial function is not defined.
  #
  # @param arg [any]
  # @yield [arg] if partial function not defined for this +arg+
  #
  # @note that expression +pf.call_or_else(arg, &fallback)+ is equivalent to
  #   +pf.defined_at?(arg) ? pf.(arg) : fallback.(arg)+
  #   except that +call_or_else+ method can be implemented more efficiently to avoid calling +defined_at?+ twice.
  #
  def call_or_else(arg)
    if defined_at?(arg)
      call(arg)
    else
      yield arg
    end
  end

  # Composes this partial function with a fallback partial function which
  # gets applied where this partial function is not defined.
  #
  # @param other [PartialFunction]
  # @return [PartialFunction] a partial function which has as domain the union of the domains
  #   of this partial function and +other+.
  def or_else(other)
    OrElse.new(self, other)
  end

  # @see or_else
  def |(other)
    or_else(other)
  end

  # Composes this partial function with a fallback partial function (or Proc) which
  # gets applied where this partial function is not defined.
  #
  # @overload and_then(other)
  #   @param other [Fear::PartialFunction]
  #   @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
  #     argument +x+ to +other.(self.call(x))+.
  #   @note calling +#defined_at?+ on the resulting partial function may call the first
  #     partial function and execute its side effect. It is highly recommended to call +#call_or_else+
  #     instead of +#defined_at?+/+#call+ for efficiency.
  # @overload and_then(other)
  #   @param other [Proc]
  #   @return [Fear::PartialFunction] a partial function with the same domain as this partial function, which maps
  #     argument +x+ to +other.(self.call(x))+.
  # @overload and_then(&other)
  #   @param other [Proc]
  #   @return [Fear::PartialFunction]
  #
  def and_then(other = Utils::UNDEFINED, &block)
    Utils.with_block_or_argument("Fear::PartialFunction#and_then", other, block) do |fun|
      if fun.is_a?(Fear::PartialFunction)
        Combined.new(self, fun)
      else
        AndThen.new(self, &fun)
      end
    end
  end

  # @see and_then
  def &(other)
    and_then(other)
  end

  # Turns this partial function in Proc-like object, returning +Option+
  # @return [#call]
  def lift
    Lifted.new(self)
  end

  class << self
    # Creates partial function guarded by several condition.
    # All guards should match.
    # @param guards [<#===>]
    # @param function [Proc]
    # @return [Fear::PartialFunction]
    def and(*guards, &function)
      PartialFunctionClass.new(Guard.and(guards), &function)
    end

    # Creates partial function guarded by several condition.
    # Any condition should match.
    # @param guards [<#===>]
    # @param function [Proc]
    # @return [Fear::PartialFunction]
    def or(*guards, &function)
      PartialFunctionClass.new(Guard.or(guards), &function)
    end
  end
end

#lift#call

Turns this partial function in Proc-like object, returning Option


146
147
148
# File 'lib/fear/partial_function.rb', line 146

def lift
  Lifted.new(self)
end

#or_else(other) ⇒ PartialFunction

Composes this partial function with a fallback partial function which gets applied where this partial function is not defined.


102
103
104
# File 'lib/fear/partial_function.rb', line 102

def or_else(other)
  OrElse.new(self, other)
end

#to_procProc

Converts this partial function to other


74
75
76
# File 'lib/fear/partial_function.rb', line 74

def to_proc
  proc { |arg| call(arg) }
end

#|(other) ⇒ Object

See Also:


107
108
109
# File 'lib/fear/partial_function.rb', line 107

def |(other)
  or_else(other)
end