YAML не работает с перечислителями - PullRequest
2 голосов
/ 05 мая 2020

Я пишу библиотеку на 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 сохраняется, а не преобразовывается в массив значений, которые он производит, я был бы весьма признателен.


Приношу извинения за любую путаницу или недоразумения из моего первого черновика. Я надеюсь, что предоставление реального рабочего кода прояснит вопрос.

...