Class: Object

Inherits:
BasicObject
Defined in:
lib/funtools/cons.rb,
lib/funtools/types.rb,
lib/funtools/recursion.rb,
lib/funtools/composition.rb,
lib/funtools/pattern-matching.rb

Instance Method Summary collapse

Instance Method Details

#compose(*f) ⇒ Object

Public: Compose a series of functions.

f - Any number of Procs, Methods, or Symbols which should be composed. If

functions f, g, and h are composed in that order, the result will be
f . g . h -> f(g(h(...)))

Returns a composed Proc.



33
34
35
36
37
# File 'lib/funtools/composition.rb', line 33

def compose(*f)
  f.map do |g|
    g.is_a?(Array) ? ->(n) { g.first.to_proc[n,*g.drop(1)] } : g.to_proc
  end.reduce(&:*)
end

#cons(left, right) ⇒ Object

Public: Wrap Cons.new to construct a new Cons cell.

left - Any Object to be the left element of the cell. right - Any Object to be the right element of the cell.

Returns a Cons cell.



8
9
10
# File 'lib/funtools/cons.rb', line 8

def cons(left, right)
  Cons.new(left, right)
end

#defpattern(sym) ⇒ Object

Public: Define a method in the current scope which allow pattern matching function declaration to be used.

sym - Symbol defining the name of the method to be created. block - Block containing the logic for the function to be created.

Returns nothing.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/funtools/pattern-matching.rb', line 9

def defpattern(sym)
  match = ->(a, b) do
    if([a,b].map { |o| o.is_a?(Enumerable) && a.class == b.class }.all?)
      raise ArgumentError unless a.length == b.length

      zipped = a.is_a?(Hash) ? a.sort.zip(b.sort) : a.zip(b)

      zipped.reduce(true) do |c, e|
        c && match.(*e)
      end
    else
      a.nil? || a == b
    end
  end

  old_method = self.class.method(sym) if self.class.method_defined?(:sym)
  patterns = []
  self.class.send(:define_method, sym) do |*l, &b|
    patterns << ->(*n) do
      ->(*m) do
        if m.length == n.length
          e = m.zip(n)
          raise NoMatch if e.reject { |e| match.(*e) }.any?
          instance_exec(*n, &b)
        end
      end.(*l)
    end
  end

  yield

  if old_method
    self.class.send(:define_method, sym, &old_method)
  else
    self.class.send(:remove_method, sym)
  end

  message   = :define_method if respond_to?(:define_method, true)
  message ||= :define_singleton_method
  self.send(message, sym) do |*args|
    patterns.each do |pattern|
      begin
        return instance_exec(*args, &pattern)
      rescue NoMatch
      end
    end
    instance_exec { raise NoMatch }
  end
end

#deftail(sym, &block) ⇒ Object

Public: Define a method in the current scope which will execute a given block recursively until a fixpoint is reached.

sym - Symbol defining the name of the method to be created. block - Block containing the logic for the function to be created.

Returns nothing.



9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/funtools/recursion.rb', line 9

def deftail(sym, &block)
  message   = :define_method if respond_to?(:define_method, true)
  message ||= :define_singleton_method
  self.send(message, sym) do |*n|
    last = nil
    [1].cycle.reduce(n) do |c, e|
      break c if last == c
      last = c
      instance_exec(c, &block)
    end
  end
end

#list(first, second = nil, *rest) ⇒ Object

Public: Construct a list of nested Cons cells.

first - Any Object to be the leftmost element of the list. second - Any Object to be the second element of the list (default: nil). rest - Any number of Objects to serve as elements in the list.

Returns a list (nested Cons cells).



19
20
21
22
# File 'lib/funtools/cons.rb', line 19

def list(first, second = nil, *rest)
  set = (rest.empty? && second.nil?) ? [] : rest + [nil]
  ([first, second] + set).reverse.reduce { |c, e| Cons.new(e, c) }
end

#pl(data, *f) ⇒ Object

Public: Mimic the -> macro in Clojure.

data - Any data to be passed to the composed function. f - Any number of Procs, Methods, or Symbols which should be composed.

If functions f, g, and h are composed in that order, the result will
be f . g . h -> f(g(h(...)))

Returns the result of the composed functions, called with data as the argument.



48
49
50
# File 'lib/funtools/composition.rb', line 48

def pl(data, *f)
  compose(*f.reverse)[*[data].flatten]
end

#settype(sym, *args, ret) ⇒ Object

Public: Define a method in the current scope which will execute a given block recursively until a fixpoint is reached.

sym - Symbol defining the name of the method to be created. block - Block containing the logic for the function to be created.

Returns nothing.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/funtools/types.rb', line 9

def settype(sym, *args, ret)
  message    = :define_method if respond_to?(:define_method, true)
  message  ||= :define_singleton_method
  old_method = self.method(sym)
  params     = old_method.parameters

  typedefs = old_method.parameters.select do |kind,_|
    kind != :block
  end.each_with_index.map do |req,index|
    [req.first, args[index]]
  end

  check_type = ->(value, type) do
    case type
    when Class, Module
      unless value.is_a?(type)
        raise(TypeError, "Expected #{type}; got #{value.class}")
      end
    when Enumerable
      unless type.map { |kind| value.is_a?(kind) }.any?
        raise(TypeError, "Expected one of: #{type.join(', ')}; got #{value.class}")
      end
    else
      raise(TypeError, "Unable to test type against #{type.class}")
    end
    value
  end

  align_types = ->(a) do
    pivot = typedefs.index(typedefs.select { |k,n| k == :rest }.flatten)

    if pivot
      types_min = pivot + 1
      args_num  = a.length - types_min
      rest_min  = a.length - (args_num + 1)

      typedefs[0...pivot].to_a.zip(a[0...pivot].to_a) +
      a[pivot, args_num].to_a.zip([typedefs[pivot]].cycle).map(&:reverse) +
      typedefs[types_min..-1].to_a.zip(a[rest_min..-1].to_a)
    else
      typedefs.zip(a)
    end.reject { |t, v| v.nil? && t.first == :opt }.map { |t, v| [v, t.last] }
  end

  self.send(message, sym) do |*n, &b|
    align_types.(n).each { |pair| check_type.(*pair) }
    check_type.(old_method.(*n, &b), ret)
  end
end