bolshakov/fear

View on GitHub
Rakefile

Summary

Maintainability
Test Coverage
# frozen_string_literal: true

require "bundler/gem_tasks"
require "benchmark/ips"
require "dry/monads"
require_relative "lib/fear"

include Dry::Monads[:maybe]

namespace :perf do
  # Contains benchmarking against Dry-rb
  namespace :dry do
    task :some_fmap_vs_fear_some_map do
      dry = Some(42)
      fear = Fear.some(42)

      Benchmark.ips do |x|
        x.report("Dry") { dry.fmap(&:itself) }

        x.report("Fear") { fear.map(&:itself) }

        x.compare!
      end
    end

    task :do_vs_fear_for do
      require "dry/monads/do"

      class Operation
        include Dry::Monads::Do.for(:call)

        def call
          m1 = Some(1)
          m2 = Some(2)

          one = yield m1
          two = yield m2

          Some(one + two)
        end
      end

      op = Operation.new

      Benchmark.ips do |x|
        x.report("Dry") { op.() }

        x.report("Fear") do |_n|
          Fear.for(Fear.some(1), Fear.some(2)) do |one, two|
            one + two
          end
        end

        x.compare!
      end
    end
  end

  # Contains internal benchmarking to if optimization works
  namespace :fear do
    namespace :guard do
      task :and1_vs_new do
        iterations = 1_000

        Benchmark.ips do |x|
          x.report("Guard.new") do |n|
            iterations.times do
              Fear::PartialFunction::Guard.new(Integer) === n
            end
          end

          x.report("Guard.and1") do |n|
            iterations.times do
              Fear::PartialFunction::Guard.and1(Integer) === n
            end
          end

          x.compare!
        end
      end

      task :and2_vs_and do
        iterations = 1_000
        first = Integer
        second = ->(x) { x > 2 }

        and2 = Fear::PartialFunction::Guard.and2(first, second)
        and_and = Fear::PartialFunction::Guard.new(first).and(Fear::PartialFunction::Guard.new(second))

        Benchmark.ips do |x|
          x.report("and2") do |n|
            iterations.times { and2 === n }
          end

          x.report("Guard#and") do |n|
            iterations.times { and_and === n }
          end

          x.compare!
        end
      end

      task :and3_vs_and_and do
        iterations = 1_000
        first = Integer
        second = ->(x) { x > 2 }
        third = ->(x) { x < 10 }

        and3 = Fear::PartialFunction::Guard.and3(first, second, third)

        and_and_and = Fear::PartialFunction::Guard.new(first)
          .and(Fear::PartialFunction::Guard.new(second))
          .and(Fear::PartialFunction::Guard.new(third))

        Benchmark.ips do |x|
          x.report("Guard.and3") do |n|
            iterations.times { and3 === n }
          end

          x.report("Guard#and") do |n|
            iterations.times { and_and_and === n }
          end

          x.compare!
        end
      end
    end

    task :pattern_matching_construction_vs_execution do
      matcher = Fear::PatternMatch.new do |m|
        m.case(Integer) { |x| x * 2 }
        m.case(String) { |x| x.to_i(10) * 2 }
      end

      Benchmark.ips do |x|
        x.report("construction") do
          Fear::PatternMatch.new do |m|
            m.case(Integer) { |y| y * 2 }
            m.case(String) { |y| y.to_i(10) * 2 }
          end
        end

        x.report("execution") do
          matcher.(42)
        end

        x.compare!
      end
    end

    task :option_match_vs_native_pattern_match do
      some = Fear.some(42)

      Benchmark.ips do |x|
        matcher = Fear::Option.matcher do |m|
          m.some(41, &:itself)
          m.some(42, &:itself)
          m.some(45, &:itself)
        end

        x.report("case ... in ...") do
          case some
          in Fear::Some(41 => x)
            x
          in Fear::Some(42 => x)
            x
          in Fear::Some(43 => x)
            x
          end
        end

        x.report("Option#match") do
          some.match do |m|
            m.some(41, &:itself)
            m.some(42, &:itself)
            m.some(45, &:itself)
          end
        end

        x.report("Option#matcher") do
          matcher.(some)
        end

        x.compare!
      end
    end
  end

  namespace :pattern_matching do
    require "dry/matcher"

    task :dry_vs_fear_try do
      success_case = Dry::Matcher::Case.new(
        match: ->(try, *pattern) {
          try.is_a?(Fear::Success) && pattern.all? { |p| p === try.get }
        },
        resolve: ->(try) { try.get },
      )

      failure_case = Dry::Matcher::Case.new(
        match: ->(try, *pattern) {
          try.is_a?(Fear::Failure) && pattern.all? { |p| p === try.exception }
        },
        resolve: ->(value) { value.exception },
      )

      # Build the matcher
      matcher = Dry::Matcher.new(success: success_case, failure: failure_case)

      success = Fear::Success.new(4)

      Benchmark.ips do |x|
        x.report("Fear") do
          success.match do |m|
            m.failure(&:itself)
            m.success(Integer, ->(y) { y % 5 == 0 }, &:itself)
            m.success { "else" }
          end
        end

        x.report("Dr::Matcher") do
          matcher.(success) do |m|
            m.failure(&:itself)
            m.success(Integer, ->(y) { y % 5 == 0 }, &:itself)
            m.success { "else" }
          end
        end

        x.compare!
      end
    end

    task :factorial do
      factorial_proc = proc do |n|
        if n <= 1
          1
        else
          n * factorial_proc.(n - 1)
        end
      end

      factorial_pm = Fear.matcher do |m|
        m.case(1, &:itself)
        m.case(0, &:itself)
        m.else { |n| n * factorial_pm.(n - 1) }
      end

      Benchmark.ips do |x|
        x.report("Proc") do
          factorial_proc.(100)
        end

        x.report("Fear") do
          factorial_pm.(100)
        end

        x.compare!
      end
    end
  end
end