koenhandekyn/functions

View on GitHub
meta/meta_programming.rb

Summary

Maintainability
A
1 hr
Test Coverage
module Functions

  module PreludeMeta

    def self.included(klass)
      klass.extend ClassMethods
    end

    module ClassMethods

      def create_proc_method(c, f, i=:f)
        if Proc === f
          define_method("#{c}_#{i}_proc", f)
          return "#{c}_#{i}_proc"
        else
          return f
        end
      end

      def define_v1(c, definition)
        key, value = definition[:as].first
        self.send(key, c, *value)
      end

      def define_v2(definition, name)
        key, value = definition
        self.send(key, name, *value)
      end

      def define(*params)
        if Symbol === params[0]
          define_v1(*params)
        else
          name = params[0].delete(:as)
          define_v2(*params[0], name)
        end
      end

      def compose(c, f, g)
        m = create_proc_method(c, f, :f)
        n = create_proc_method(c, g, :g)
        code = %Q{
        def #{c}(x)
          #{m}(#{n}(x))
        end
      }
        module_eval(code)
      end

      #alias :def_compose :compose

      def after(c, f, g)
        compose(c, g, f)
      end

      #alias :def_after :after

      def parallel(c, f, g)
        m = create_proc_method(c, f, :f)
        n = create_proc_method(c, g, :g)
        code = %Q{
        def #{c}(x)
          [#{m}(x),#{n}(x)]
        end
      }
        module_eval(code)
      end

      #alias :def_parallel :parallel

      def foldl(c, f, i)
        m = create_proc_method(c, f)
        code = %Q{
        def #{c}(arr)
          arr.inject(#{i}) { |r, x| #{m}(r,x) }
        end
      }
        module_eval(code)
      end

      #alias :def_foldl :foldl

      def reduce_left(c, f)
        m = create_proc_method(c, f)
        code = %Q{
        def #{c}(arr)
          arr.inject { |r, x| #{m}(r,x) }
        end
      }
        module_eval(code)
      end

      #alias :def_reduce_left :reduce_left

      def foldr(c, f, i)
        m = create_proc_method(c, f)
        code = %Q{
        def #{c}(arr)
          arr.reverse.inject(#{i}) { |r, x| #{m}(r,x) }
        end
      }
        module_eval(code)
      end

      #alias :def_foldr :foldr

      def reduce_right(c, f)
        m = create_proc_method(c, f)
        code = %Q{
        def #{c}(arr)
          arr.reverse.inject { |r, x| #{m}(r,x) }
        end
      }
        module_eval(code)
      end

      #alias :def_reduce_right :reduce_right

      def map(c, f)
        m = create_proc_method(c, f)
        code = %Q{
        def #{c}(arr)
          arr.map { |x| #{m}(x) }
        end
      }
        module_eval(code)
      end

      #alias :def_map :map

      def filter(c, f)
        m = create_proc_method(c, f)
        code = %Q{
        def #{c}(arr)
          arr.select { |x| #{m}(x) }
        end
      }
        module_eval(code)
      end

      #alias :def_filter :filter

      def method(m)
        code = %Q{
        def #{m}(o)
          o.#{m}
        end
      }
        module_eval(code)
      end

      alias :def_method :method

    end

    def m(method)
      lambda(&method(method))
    end

    def f(f)
      Symbol === f ? f = m(f) : f
    end

    def id(x)
      x
    end

    def const(c, x)
      c
    end

    def split_in(xs, n)
      xs.each_slice((xs.length+1)/n).to_a
    end

    def split_in_half(xs)
      split_in(xs, 2)
    end

    def merge(xs, ys, &by)

      return xs if ys.empty?
      return ys if xs.empty?

      x, *xt = xs
      y, *yt = ys

      return merge(xt, ys, &by) >> x if block_given? ? by.(x) <= by.(y) : x <= y
      return merge(xs, yt, &by) >> y

    end

    def merge_sort(xs, &by)
      return xs if xs.length <= 1 # stopcondition
      left, right = split_in_half(xs)
      merge(merge_sort(left, &by), merge_sort(right, &by), &by)
    end

    def quick_sort(list)
      return [] if list.size == 0
      pivot, *xs = *list
      less, more = xs.partition { |y| y < pivot }
      quick_sort(less) + [pivot] + quick_sort(more)
    end

    def compose(f, g, x)
      f = f(f)
      g = f(g)
      f.(g.(x))
    end

    def after(f, g, x)
      f = f(f)
      g = f(g)
      return g.(par(*f, x)) if Array === f && Proc === g
      return par(*g, f.(x)) if Proc === f && Array === g
      return par(*g, par(*f, x)) if Array === f && Array === g
      return g.(f.(x)) # else
    end

    extend ClassMethods

    def_method :flatten

    # @return calls the length method on the object
    def_method :length

    # @param object the target object
    # @return calls the reverse method on the object
    def_method :reverse

    # @param object [#min] the object to call the min method on
    # @return calls the min method on the Enumerable
    def_method :min

    # @param [#max] the object to call the max method on
    # @return [Integer] the maximum
    def_method :max


    def foldl(f, i, a)
      a.inject(i) { |r, x| f(f).(r, x) }
    end

    def foldr(f, i, a)
      foldl(f(f), i, a.reverse)
    end

    def zip(a, b) a.zip(b) end

    def map(a, f)
      a.map { |x| f(f).(x) }
    end

    def filter(a, f)
      a.select { |x| f(f).(x) }
    end

    def sum(a)
      a.inject(0, :+)
    end

    def parallel(f, g, x)
      [f(f).(x), f(g).(x)]
    end

    def par(*fs, x)
      fs.map { |f| f(f).(x) }
    end

    def_method :even?

    def_method :odd?

    def divide(arr)
      arr.inject { |a,b| a/b }
    end

    define :sum_length, as: { parallel: [:sum, :length] }

    define :average, as: { :after => [ :sum_length, :divide ] }

    define :intersect, as: {reduce_left: ->(a, b) { a&b }} # longest

    # creates a range
    #
    # param from [Fixnum] the start of the range
    # param to [Fixnum] the end of the range
    def from_to(from, to)
      Range.new(from, to)
    end

    def from_one_to(to)
      Range.new(1, to)
    end

  end

end