dentaku icon indicating copy to clipboard operation
dentaku copied to clipboard

Lazy loading variables

Open goose3228 opened this issue 2 years ago • 1 comments

Greetings! It would be nice to have a fallback for undefined variables, like that:

calculator.on_missing_var do |var|
  ...
end

The primary use case is circular dependencies - parameters for an expression may be results of evaluating another one. Here is an example:

class A
  def initialize
    @expressions = {}
  end

  def override(method, expression)
    @expressions[method] = expression
  end

  def self.dentaku(name, &block)
    define_method(name) do
      if((exp = @expressions[name]))
        c = Dentaku::Calculator.new
        c.store("one", one)
        c.store("two", two)
        c.store("three", three)
        c.evaluate(exp)
      else
        instance_exec(&block)
      end
    end
  end

  dentaku(:one) do
    1
  end

  dentaku(:two) do
    2
  end

  dentaku(:three) do
    3
  end
end

a = A.new
puts a.three

a.override(:three, "one + two")
puts a.three

This will result in "stack level too deep" error. What i would like to do is the following:

  def self.dentaku(name, &block)
    define_method(name) do
      if((exp = @expressions[name]))
        c = Dentaku::Calculator.new
        c.on_missing_var do |var|
          raise "Unknown variable #{var}" unless ["one", "two", "three"].include?(var)
          send(var)
        end
        c.evaluate(exp)
      else
        instance_exec(&block)
      end
    end
  end

This code can result in the same error too, but only with certain input, which is fine.

I've had a look at the source code, but still can't figure out where to start.

Thanks in advance.

goose3228 avatar Mar 22 '23 19:03 goose3228

I'm not maintainer here, but curious. It's not clear how you want to implement dependency between variables here? Just lazy load already exists in Dentaku, you can pass lambda as variable value and it's executed each time variable is evaluated. You can add to here local caching and that's your solution:

c = Dentaku::Calculator.new
c_cache = {}
c.evaluate!("a + b + a + b", a: ->{c_cache[:a] ||= p(1)}, b: -> { p(2) })
1
2
2
=> 6

kapcod avatar Feb 20 '24 08:02 kapcod

Indeed, that does the job. I couldn't find it in docs though, but could have guessed.

The code can be fixed this way:

require "dentaku"

class A
  def initialize
    @expressions = {}
  end

  def override(method, expression)
    @expressions[method] = expression
  end

  def self.dentaku(name, &block)
    define_method(name) do
      if((exp = @expressions[name]))
        c = Dentaku::Calculator.new
        c.evaluate(exp, one: -> { one }, two: -> { two }, three: -> { three })
      else
        instance_exec(&block)
      end
    end
  end

  dentaku(:one) do
    1
  end

  dentaku(:two) do
    2
  end

  dentaku(:three) do
    3
  end
end

a = A.new
puts a.three

a.override(:three, "one + two")
puts a.three

goose3228 avatar Mar 26 '24 14:03 goose3228