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.

Parameters:

  • guards (<#===>)
  • function (Proc)

Returns:



163
164
165
# File 'lib/fear/partial_function.rb', line 163

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.

Parameters:

  • guards (<#===>)
  • function (Proc)

Returns:



172
173
174
# File 'lib/fear/partial_function.rb', line 172

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

Instance Method Details

#&(other) ⇒ Object

See Also:



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

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)).

    Parameters:

    Returns:

    • (Fear::PartialFunction)

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

  • #and_then(other) ⇒ Fear::PartialFunction

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

    Parameters:

    • other (Proc)

    Returns:

    • (Fear::PartialFunction)

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

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

    Parameters:

    • other (Proc)

    Returns:



136
137
138
139
140
141
142
143
144
# File 'lib/fear/partial_function.rb', line 136

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.

Parameters:

  • arg (any)

Returns:

  • (any)

    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 65

#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.

Parameters:

  • arg (any)

Yields:

  • (arg)

    if partial function not defined for this arg



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

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

Returns:

  • (#===)


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
170
171
172
173
174
175
176
# File 'lib/fear/partial_function.rb', line 56

module PartialFunction
  # 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+.
  # @example
  #   handle_even = Fear.case(:even?.to_proc) { |x| "#{x} is even" }
  #   handle_odd = Fear.case(:odd?.to_proc) { |x| "#{x} is odd" }
  #   handle_even_or_odd = handle_even.or_else(odd)
  #   handle_even_or_odd.(42) #=> 42 is even
  #   handle_even_or_odd.(42) #=> 21 is odd
  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.

Parameters:

  • arg (any)

Returns:

  • (Boolean)


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

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

#function#call

This method is abstract.

Returns:



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
170
171
172
173
174
175
176
# File 'lib/fear/partial_function.rb', line 56

module PartialFunction
  # 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+.
  # @example
  #   handle_even = Fear.case(:even?.to_proc) { |x| "#{x} is even" }
  #   handle_odd = Fear.case(:odd?.to_proc) { |x| "#{x} is odd" }
  #   handle_even_or_odd = handle_even.or_else(odd)
  #   handle_even_or_odd.(42) #=> 42 is even
  #   handle_even_or_odd.(42) #=> 21 is odd
  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

Returns:



153
154
155
# File 'lib/fear/partial_function.rb', line 153

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.

Examples:

handle_even = Fear.case(:even?.to_proc) { |x| "#{x} is even" }
handle_odd = Fear.case(:odd?.to_proc) { |x| "#{x} is odd" }
handle_even_or_odd = handle_even.or_else(odd)
handle_even_or_odd.(42) #=> 42 is even
handle_even_or_odd.(42) #=> 21 is odd

Parameters:

Returns:

  • (PartialFunction)

    a partial function which has as domain the union of the domains of this partial function and other.



109
110
111
# File 'lib/fear/partial_function.rb', line 109

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

#to_procProc

Converts this partial function to other

Returns:

  • (Proc)


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

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

#|(other) ⇒ Object

See Also:



114
115
116
# File 'lib/fear/partial_function.rb', line 114

def |(other)
  or_else(other)
end