Class: Qrb::AdType

Inherits:
Type
  • Object
show all
Defined in:
lib/qrb/type/ad_type.rb

Overview

The Abtract/Algebraic Data Type is a generator to build user-defined information types in a more abstract way than using sub types. For instance, a Color could be defined as follows:

Color := <Rgb> {r: Byte, g: Byte, b: Byte},
         <Hex> String( s | s =~ /^#[0-9a-f]{6}$/i )

Such a declaration does not define a new type by subtyping but instead declares so-called possible representations for the color. Here, two possible representations are defined: one is a rgb triple through a Tuple type, the other is an hexadecimal notation through a subset of String.

Given an AdType, Q requires special dress/undress function pairs to encode/decode from the type to the concrete representations and vice versa. This pair of functions is called the “information contract”.

This class allows capturing such information types. For instance,

# This is the concrete representation of Q's Color, through a usual
# Ruby ADT (we call it ColorImpl here to avoid confusion, but in
# practice one would simply call it Color).
class ColorImpl
  # ... your usual color implementation
end

# The RGB info type: {r: Byte, g: Byte, b: Byte}
rgb_infotype  = TupleType.new(...)

# The RGB contract converter, an object that responds to `call` to
# convert from a valid Hash[r: Fixnum, ...] to a ColorImpl instance.
rgb_contract = ...

AdType.new(ColorImpl, rgb: [rgb_infotype, rgb_contract], hex: ...)

The ruby ADT class is of course used as concrete representation of the AdType, that is,

R(Color) = ColorImpl

Accordingly, the ‘dress` transformation function has the following signature:

dress :: Alpha  -> Color     throws TypeError
dress :: Object -> ColorImpl throws TypeError

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Type

#name, #name=, #to_s

Constructor Details

#initialize(ruby_type, contracts, name = nil) ⇒ AdType

Returns a new instance of AdType.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/qrb/type/ad_type.rb', line 51

def initialize(ruby_type, contracts, name = nil)
  unless ruby_type.nil? or ruby_type.is_a?(Module)
    raise ArgumentError, "Module expected, got `#{ruby_type}`"
  end
  unless contracts.is_a?(Hash)
    raise ArgumentError, "Hash expected, got `#{contracts}`"
  end
  invalid = contracts.values.reject{|v|
    v.is_a?(Array) and v.size == 3 and v[0].is_a?(Type) and \
    v[1].respond_to?(:call) and v[2].respond_to?(:call)
  }
  unless invalid.empty?
    raise ArgumentError, "Invalid contracts `#{invalid}`"
  end

  super(name)
  @ruby_type = ruby_type
  @contracts = contracts.freeze
end

Instance Attribute Details

#contractsObject (readonly)

Returns the value of attribute contracts.



70
71
72
# File 'lib/qrb/type/ad_type.rb', line 70

def contracts
  @contracts
end

#ruby_typeObject (readonly)

Returns the value of attribute ruby_type.



70
71
72
# File 'lib/qrb/type/ad_type.rb', line 70

def ruby_type
  @ruby_type
end

Instance Method Details

#contract_namesObject



72
73
74
# File 'lib/qrb/type/ad_type.rb', line 72

def contract_names
  contracts.keys
end

#default_nameObject



76
77
78
# File 'lib/qrb/type/ad_type.rb', line 76

def default_name
  (ruby_type && ruby_type.name.to_s) || "Anonymous"
end

#dress(value, handler = DressHelper.new) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/qrb/type/ad_type.rb', line 84

def dress(value, handler = DressHelper.new)
  # Up should be idempotent with respect to the ADT
  return value if ruby_type and value.is_a?(ruby_type)

  # Try each contract in turn. Do nothing on TypeError as
  # the next candidate could be the good one! Return the
  # first successfully dressed.
  contracts.each_pair do |name, (infotype, dresser, _)|

    # First make the dress transformation on the information type
    success, dressed = handler.just_try do
      infotype.dress(value, handler)
    end
    next unless success

    # Seems nice, just try to get one stage higher now
    success, dressed = handler.just_try(StandardError) do
      dresser.call(dressed)
    end
    return dressed if success

  end

  # No one succeeded, just fail
  handler.failed!(self, value)
end

#include?(value) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
# File 'lib/qrb/type/ad_type.rb', line 80

def include?(value)
  ruby_type === value
end