Я пишу библиотеку на Ruby, которая использует случайные числа. Я написал компоненты так, что rand
является базовым генератором по умолчанию, но опытные пользователи могут захотеть предоставить свою собственную случайную реализацию. Любую реализацию легко обернуть в Enumerator
, который дает общий интерфейс, использующий .next
для доступа к генератору, независимо от того, чей он. (Обратите внимание, что rand
изначально не предоставляет next
, следовательно, оболочку.) В Ruby 2.7:
# File my_module.rb
module Demo
U_GEN = Enumerator.produce(rand) { rand } # wrap rand as enum
class SomeThing
def initialize(scale:, rng: U_GEN)
@scale = scale
@rng = rng
end
def triangle1
@scale * Math.sqrt(@rng.next)
end
def triangle2
@scale * (1.0 - Math.sqrt(@rng.next))
end
end
end
Большинство пользователей, вероятно, просто создадут SomeThing.new(scale: whatever)
, но опытные пользователи, которым что-то нужно кроме rand
, можно выбрать SomeThing.new(scale: whatever, rng: custom_generator)
.
Для больших продуктов я хочу иметь возможность управлять конфигурацией с помощью YAML. Моя проблема в том, что YAML.dump
объекта SomeThing
, похоже, хочет сгенерировать полный набор значений для Enumerator
, как показано здесь:
require 'yaml'
require_relative 'my_module'
finite_enum = (1..9).map { |x| x / 10.0 }.each
this_works = Demo::SomeThing.new(scale: 42, rng: finite_enum)
puts "#{this_works.triangle1}\n" # => 13.281566172707194
this_works_yaml = YAML.dump(this_works) # can dump with a finite enum, but...
puts this_works_yaml
# Output...
#
# --- !ruby/object:Demo::SomeThing
# scale: 42
# rng:
# - 0.1
# - 0.2
# - 0.3
# - 0.4
# - 0.5
# - 0.6
# - 0.7
# - 0.8
# - 0.9
this_fails = YAML.load(this_works_yaml)
p this_fails
# Output...
#
# <Demo::SomeThing:0x00007fb01e88b0c8 @scale=42,
# @rng=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]>
# rng is now an array, not an enumerator, so the following gives an
# "undefined method `next'" error trying to get the next random number
puts "#{this_fails.triangle1}"
С бесконечным генератором, таким как U_GEN
, вы даже не можете заставить дамп работать:
require 'yaml'
require_relative 'my_module'
actual_use_case = Demo::SomeThing.new(scale: 42) # use default U_GEN for rng
puts "#{actual_use_case.triangle1}\n"
# If uncommented, the following goes to infinity and beyond!
# actual_use_yaml = YAML.dump(actual_use_case)
Если кто-нибудь знает магию c заклинание для сброса / загрузки объекта, имеющего Enumerator
в качестве переменной экземпляра, чтобы Enumerator
сохраняется, а не преобразовывается в массив значений, которые он производит, я был бы весьма признателен.
Приношу извинения за любую путаницу или недоразумения из моего первого черновика. Я надеюсь, что предоставление реального рабочего кода прояснит вопрос.