Skip to content

Instantly share code, notes, and snippets.

@estum
Last active May 30, 2023 05:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save estum/fe5c83ea06fa39fce95f5a84689da503 to your computer and use it in GitHub Desktop.
Save estum/fe5c83ea06fa39fce95f5a84689da503 to your computer and use it in GitHub Desktop.
Dry::Types::Atomic
# frozen_string_literal: true
require 'concurrent/atomic/atomic_reference'
module Dry
module Types
# Atomic types denote references in advance to target ones due to help achieve a cross-reference.
#
# @example
# module T
# extend Dry::Container::Mixin
# include Dry.Types()
#
# AnyKeyval = Atomic()
#
# Simple = Dry.Struct(key: String, value: String)
# Complex = Dry.Struct(key: String, value: AnyKeyval)
# end
#
# begin
# summarize = -> alt, old { old ? (old | alt) : alt }.curry(2)
# T::AnyKeyval.try_update!(&summarize[T::Simple])
# T::AnyKeyval.try_update!(&summarize[T::Complex])
# end
#
# begin
# input = { key: 'topmost',
# value: { key: 'complex',
# value: { key: 'inner',
# value: 'simple' } } }
# foo = T::AnyKeyval[input]
# foo.class # => T::Complex
# foo.value.class # => T::Complex
# foo.value.value.class # => T::Simple
# end
#
class Atomic < Concurrent::AtomicReference
include Type
include Decorator
# include Builder
include Printable
undef :type
send :include, Dry.Equalizer(:type, inspect: false, immutable: false)
# @param [Type] placeholder
# initial type
def initialize(placeholder = Undefined, *, **)
super
remove_instance_variable(:@type)
Undefined.map(placeholder) { set(placeholder) }
end
def type
get
end
# @return [::String]
# @api private
def to_s
format("#<Dry::Types[Atomic<%s>]>", PRINTER.(get) { get.to_s })
end
alias_method :inspect, :to_s
private
# @return [false]
# @api private
def decorate?(*)
false
end
# @!method set(target)
# Sets the reference
# @param [Type] target type
# @see Concurrent::AtomicReference#set
# @!method get()
# Resolves the reference
# @return [Type] target type
# @see Concurrent::AtomicReference#get
# @!method update(&block)
# Pass the current value to the given block, replacing it with the block's result. May retry.
# @yield [Type] Calculate a new value for the atomic reference using given (old) value
# @yieldparam [Object] old_value the starting value of the atomic reference
# @return [Type]
# @see Concurrent::AtomicReference#update
# @!method try_update!(&block)
# @yield [Type] Calculate a new value for the atomic reference using given (old) value
# @yieldparam [Object] old_value the starting value of the atomic reference
# @return [Type]
# @raise [Concurrent::ConcurrentUpdateError] if the update fails
# @see Concurrent::AtomicReference#try_update!
end
module BuilderMethods
# @return [Atomic]
def Atomic(placeholder = Undefined)
Atomic.new(placeholder)
end
end
end
end
module Dry
module Types
RSpec.describe Atomic do
let!(:summarize) { -> alt, old { old ? (old | alt) : alt }.curry(2) }
subject(:atomic) { t.Atomic(simple_struct) }
it { is_expected.to be_instance_of(described_class) }
let(:t) { stub_const('T', Dry::Types()) }
let(:simple_struct) { stub_const('Simple', Dry.Struct(key: t::String, value: t::String)) }
let(:complex_struct) { stub_const('Complex', Dry.Struct(key: t::String, value: atomic)) }
let(:sample_input) do
{ key: 'topmost',
value: { key: 'complex',
value: { key: 'inner',
value: 'simple' } } }
end
before { allow(simple_struct).to receive(:|).and_call_original }
context 'on referenced value' do
subject(:ref) { atomic.get }
it { is_expected.to eq(simple_struct) }
context 'after the second update' do
before { atomic.try_update!(&summarize[complex_struct]) }
context 'on simple struct' do
subject { simple_struct }
it { is_expected.to have_received(:|).once }
end
it { is_expected.to be_kind_of(Dry::Struct::Sum) }
end
end
context 'on atomic reference' do
before { atomic.try_update!(&summarize[complex_struct]) }
let(:expected_attrs) do
{ type: be_kind_of(Dry::Struct::Sum).and(have_attributes(left: simple_struct, right: complex_struct)) }
end
it { is_expected.to have_attributes(expected_attrs) }
context 'on the result of calling on a sample input' do
subject(:result) { atomic[sample_input] }
it { is_expected.to be_kind_of(complex_struct) }
context 'on the value of the topmost object' do
subject(:topmost_value) { result.value }
it { is_expected.to be_kind_of(complex_struct) }
context 'on the value of the bottom object' do
subject(:bottom_value) { topmost_value.value }
it { is_expected.to be_kind_of(simple_struct) }
end
end
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment