Skip to content

Commit

Permalink
trying to fix generic issues with maybe
Browse files Browse the repository at this point in the history
  • Loading branch information
gtrias committed Aug 21, 2024
1 parent 2f220cf commit 85d25ac
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 75 deletions.
157 changes: 83 additions & 74 deletions lib/schema_registry/maybe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,87 +14,96 @@ module Maybe
extend RuntimeGeneric
include Kernel
interface!

# NOTE: Beware of implementing a `Maybe#value` method in the interface so you can call it without
# type safety. >:(

Value = type_member(:out) { { upper: BasicObject } }

sig do
type_parameters(:Key)
.params(input: T::Hash[T.type_parameter(:Key), T.untyped])
.returns(T::Hash[T.type_parameter(:Key), T.untyped])
end
# You can use this method to easily transform a Hash with `Maybe` values into one without them,
# filtering out `Maybe` instances that are empty and unwrapping the present ones.
#
# Given a hash containing `Maybe` instances, returns a hash with only the values that are present.
# It also unwraps the present values.
#
# For convenience, it also recursively serializes nested T::Structs and strips nested hashes,
# arrays and sets.
#
# ```ruby
# Maybe.strip({ a: Maybe.from(1), b: Maybe.empty, c: Maybe.from(3) })
# # => { a: 1, c: 3 }
# ```
def self.strip(input) # rubocop:disable Metrics/PerceivedComplexity
input
.reject { |_key, value| value == Maybe.empty }
.to_h do |key, value|
unwrapped = value.is_a?(Maybe::Present) ? value.value : value
enumerated =
if unwrapped.is_a?(Array) || unwrapped.is_a?(Set)
unwrapped.map { |v| v.is_a?(T::Struct) ? Maybe.strip(v.serialize) : Maybe.strip(v) }
else
unwrapped
end
serialized = enumerated.is_a?(T::Struct) ? enumerated.serialize : enumerated
stripped = serialized.is_a?(Hash) ? Maybe.strip(serialized) : serialized

[key, stripped]
end
end

sig { returns(Absent) }
# Creates an empty instance.
def self.empty
Absent.new
end

sig { returns(Absent) }
# Creates an empty instance.
# Alias for self.empty
def self.none
empty
module ClassMethods
extend T::Sig
extend T::Helpers
abstract!

sig do
type_parameters(:Key)
.params(input: T::Hash[T.type_parameter(:Key), T.untyped])
.returns(T::Hash[T.type_parameter(:Key), T.untyped])
end
# You can use this method to easily transform a Hash with `Maybe` values into one without them,
# filtering out `Maybe` instances that are empty and unwrapping the present ones.
#
# Given a hash containing `Maybe` instances, returns a hash with only the values that are present.
# It also unwraps the present values.
#
# For convenience, it also recursively serializes nested T::Structs and strips nested hashes,
# arrays and sets.
#
# ```ruby
# Maybe.strip({ a: Maybe.from(1), b: Maybe.empty, c: Maybe.from(3) })
# # => { a: 1, c: 3 }
# ```
def strip(input) # rubocop:disable Metrics/PerceivedComplexity
input
.reject { |_key, value| value == empty }
.to_h do |key, value|
unwrapped = value.is_a?(Maybe::Present) ? value.value : value
enumerated =
if unwrapped.is_a?(Array) || unwrapped.is_a?(Set)
unwrapped.map { |v| v.is_a?(T::Struct) ? strip(v.serialize) : strip(v) }
else
unwrapped
end
serialized = enumerated.is_a?(T::Struct) ? enumerated.serialize : enumerated
stripped = serialized.is_a?(Hash) ? strip(serialized) : serialized

[key, stripped]
end
end

# Creates an empty instance.
sig { returns(Absent) }
def empty
Absent.new
end

sig { returns(Absent) }
# Creates an empty instance.
# Alias for self.empty
def none
empty
end

sig { returns(Absent) }
# Creates an empty instance.
# Alias for self.empty
def absent
empty
end

sig do
type_parameters(:Value)
.params(value: T.all(BasicObject, T.type_parameter(:Value)))
.returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))])
end
# Creates an instance containing the specified value.
# Necessary to make this work with sorbet-coerce
def new(value)
from(value)
end

sig do
type_parameters(:Value)
.params(value: T.all(BasicObject, T.type_parameter(:Value)))
.returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))])
end
# Creates an instance containing the specified value.
def from(value)
Present[T.all(BasicObject, T.type_parameter(:Value))].new(value)
end
end

sig { returns(Absent) }
# Creates an empty instance.
# Alias for self.empty
def self.absent
empty
end

sig do
type_parameters(:Value)
.params(value: T.all(BasicObject, T.type_parameter(:Value)))
.returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))])
end
# Creates an instance containing the specified value.
# Necessary to make this work with sorbet-coerce
def self.new(value)
from(value)
end

sig do
type_parameters(:Value)
.params(value: T.all(BasicObject, T.type_parameter(:Value)))
.returns(Maybe[T.all(BasicObject, T.type_parameter(:Value))])
end
# Creates an instance containing the specified value.
def self.from(value)
Present[T.all(BasicObject, T.type_parameter(:Value))].new(value)
end
mixes_in_class_methods(ClassMethods)

sig { abstract.returns(T::Boolean) }
# `true` if this `Maybe` contains a value, `false` otherwise.
Expand Down
5 changes: 4 additions & 1 deletion lib/schema_registry/maybe/present.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class Present

Value = type_member(:out) { { upper: BasicObject } }

requires_ancestor { Object }

sig(:final) { params(value: Value).void }
def initialize(value)
@value = value
Expand Down Expand Up @@ -64,7 +66,7 @@ def when_absent(&_block)
def filter(&_block)
return self if yield value

Absent.new
Absent.new(value)
end

sig(:final) do
Expand All @@ -84,6 +86,7 @@ def map(&_block)
sig(:final) { override.params(other: BasicObject).returns(T::Boolean) }
def ==(other)
return false unless self.class === other # rubocop:disable Style/CaseEquality

value == other.value
end

Expand Down

0 comments on commit 85d25ac

Please sign in to comment.