Move functions into module
This commit is contained in:
		
							parent
							
								
									f4492cf8cd
								
							
						
					
					
						commit
						01b1d4c7e3
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1,4 @@
 | 
			
		||||
*.swp
 | 
			
		||||
*.gem
 | 
			
		||||
.yardoc/
 | 
			
		||||
doc/
 | 
			
		||||
							
								
								
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -1,4 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "code-runner.customCommand": "rake test",
 | 
			
		||||
  "code-runner.ignoreSelection": true,
 | 
			
		||||
  "code-runner.runInTerminal": true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							@ -2,10 +2,12 @@
 | 
			
		||||
 | 
			
		||||
Tail call optimization for mutually (and directly) recursive functions in Ruby.
 | 
			
		||||
 | 
			
		||||
The current desing uses a trampoline. However, it is implemented in a way that still allows a tail recursive function to return a Proc as its terminal value without it being called prematurely by accident. 
 | 
			
		||||
The current design uses a trampoline. However, it is implemented in a way that still allows a tail recursive function to return a Proc as its terminal value.
 | 
			
		||||
 | 
			
		||||
### examples
 | 
			
		||||
```ruby
 | 
			
		||||
require 'mutual_recursion'
 | 
			
		||||
include MutualRecursion
 | 
			
		||||
 | 
			
		||||
def mutual_one(x, y = 0)
 | 
			
		||||
  return terminal_value(y) if x.negative?
 | 
			
		||||
@ -22,6 +24,7 @@ mutual_one(50_000).invoke
 | 
			
		||||
```
 | 
			
		||||
```ruby
 | 
			
		||||
require 'mutual_recursion'
 | 
			
		||||
include MutualRecursion
 | 
			
		||||
 | 
			
		||||
def direct(x, y = 0)
 | 
			
		||||
  return terminal_value(y) if x.negative?
 | 
			
		||||
@ -34,6 +37,7 @@ direct(50_000).invoke
 | 
			
		||||
```
 | 
			
		||||
```ruby
 | 
			
		||||
require 'mutual_recursion'
 | 
			
		||||
include MutualRecursion
 | 
			
		||||
 | 
			
		||||
def proc_returning(x, y = 0)
 | 
			
		||||
  return terminal_value(proc { "|#{y}|" }) if x.negative?
 | 
			
		||||
@ -41,6 +45,19 @@ def proc_returning(x, y = 0)
 | 
			
		||||
  tail_call { proc_returning(x - 1, y + 1) }
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
proc_returning(20).invoke.call
 | 
			
		||||
generated_proc = proc_returning(20).invoke
 | 
			
		||||
generated_proc.call
 | 
			
		||||
# => "|21|"
 | 
			
		||||
```
 | 
			
		||||
```ruby
 | 
			
		||||
require 'mutual_recursion'
 | 
			
		||||
 | 
			
		||||
def without_include(x, y = 0)
 | 
			
		||||
  return MutualRecursion.terminal_value(y) if x.negative?
 | 
			
		||||
 | 
			
		||||
  MutualRecursion.tail_call { without_include(x - 1, y + 1) }
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
without_include(50_000).invoke
 | 
			
		||||
# => 50001
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,6 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module MutualRecursion
 | 
			
		||||
  UNEXPECTED_TYPE = 'expected tail_call or terminal_value'
 | 
			
		||||
 | 
			
		||||
  class TailCall
 | 
			
		||||
    attr_reader :value, :block
 | 
			
		||||
 | 
			
		||||
@ -11,23 +9,49 @@ module MutualRecursion
 | 
			
		||||
      @block = block
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    ##
 | 
			
		||||
    # Invoke this tail call. Only the returned value from the initial call to
 | 
			
		||||
    # the recursive function should be invoked.
 | 
			
		||||
    #
 | 
			
		||||
    # @return [Object] the terminal_value of this tail call
 | 
			
		||||
 | 
			
		||||
    def invoke
 | 
			
		||||
      self.then do |tail|
 | 
			
		||||
        while tail.block
 | 
			
		||||
          tail = tail.block.call
 | 
			
		||||
          raise TypeError, UNEXPECTED_TYPE unless tail.is_a?(TailCall)
 | 
			
		||||
          raise MissingTailCallError unless tail.is_a?(TailCall)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        tail.value
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
def tail_call
 | 
			
		||||
  MutualRecursion::TailCall.new { yield }
 | 
			
		||||
end
 | 
			
		||||
  class MissingTailCallError < StandardError
 | 
			
		||||
    def initialize(msg = 'expected a tail call')
 | 
			
		||||
      super
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
def terminal_value(value)
 | 
			
		||||
  MutualRecursion::TailCall.new(value)
 | 
			
		||||
  module_function
 | 
			
		||||
 | 
			
		||||
  ##
 | 
			
		||||
  # Make a direct or indirect recursive call in tail position.
 | 
			
		||||
  #
 | 
			
		||||
  # @yieldreturn [MutualRecursion::TailCall] a tail call
 | 
			
		||||
  # @return [MutualRecursion::TailCall] a non terminal tail call
 | 
			
		||||
 | 
			
		||||
  def tail_call
 | 
			
		||||
    TailCall.new { yield }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  ##
 | 
			
		||||
  # Indicates that the recursion has ended with the provided value.
 | 
			
		||||
  #
 | 
			
		||||
  # @param [Object] value the terminal value
 | 
			
		||||
  # @return [MutualRecursion::TailCall] a terminal tail call that will return the given value
 | 
			
		||||
 | 
			
		||||
  def terminal_value(value)
 | 
			
		||||
    TailCall.new(value)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ Gem::Specification.new do |s|
 | 
			
		||||
  s.version     = '0.0.1'
 | 
			
		||||
  s.date        = '2019-01-26'
 | 
			
		||||
  s.summary     = 'Mutual Recursion for Ruby'
 | 
			
		||||
  s.description = 'Tail call optimization for mutually (and directly) recursive functions using a trampoline.'
 | 
			
		||||
  s.description = 'Tail call optimization for mutually (and directly) recursive functions.'
 | 
			
		||||
  s.authors     = ['Mike']
 | 
			
		||||
  s.files       = ['lib/mutual_recursion.rb']
 | 
			
		||||
  s.homepage    = 'https://gitlab.com/mike-cifelli/mutual_recursion'
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,8 @@ require 'minitest/pride'
 | 
			
		||||
require_relative '../lib/mutual_recursion'
 | 
			
		||||
 | 
			
		||||
class InventoryTest < Minitest::Test
 | 
			
		||||
  include MutualRecursion
 | 
			
		||||
 | 
			
		||||
  def test_terminal_value
 | 
			
		||||
    tail = terminal_value(42)
 | 
			
		||||
    assert_equal(42, tail.invoke)
 | 
			
		||||
@ -43,16 +45,12 @@ class InventoryTest < Minitest::Test
 | 
			
		||||
 | 
			
		||||
  def test_non_tail_call_detected
 | 
			
		||||
    tail = bad_return
 | 
			
		||||
    assert_raises(TypeError) { tail.invoke }
 | 
			
		||||
    assert_raises(MissingTailCallError) { tail.invoke }
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
class TailCallDecoy
 | 
			
		||||
  attr_reader :value, :block
 | 
			
		||||
 | 
			
		||||
  def initialize
 | 
			
		||||
    @value = 99
 | 
			
		||||
    @block = proc { 25 }
 | 
			
		||||
  def test_module_functions
 | 
			
		||||
    tail = MutualRecursion.tail_call { MutualRecursion.terminal_value(42) }
 | 
			
		||||
    assert_equal(42, tail.invoke)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -77,11 +75,20 @@ def lambda_returning
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
def proc_returning(x, y = 0)
 | 
			
		||||
  return terminal_value(proc { puts "|#{y}|" }) if x.negative?
 | 
			
		||||
  return terminal_value(proc { "|#{y}|" }) if x.negative?
 | 
			
		||||
 | 
			
		||||
  tail_call { proc_returning(x - 1, y + 1) }
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
def bad_return
 | 
			
		||||
  tail_call { TailCallDecoy.new }
 | 
			
		||||
  tail_call do
 | 
			
		||||
    Class.new do
 | 
			
		||||
      attr_reader :value, :block
 | 
			
		||||
 | 
			
		||||
      def initialize
 | 
			
		||||
        @value = 99
 | 
			
		||||
        @block = proc { 25 }
 | 
			
		||||
      end
 | 
			
		||||
    end.new
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user