Qrb
Q is a language for capturing information structure. Think "JSON/XML schema" but the correct way. For more information about Q itself, see www.q-lang.io
Qrb is the ruby binding of Q. It allows defining Q schemas and validating and coercing data against them in an idiomatic ruby way.
Example
require 'qrb'
require 'json'
# Let load a Q schema
schema = Qrb::DEFAULT_SYSTEM.parse <<-Q
{
name: String( s | s.strip.size > 0 ),
at: DateTime
}
Q
# Let load some JSON document
data = JSON.parse <<-JSON
{ "name": "Q", "at": "20142-03-01" }
JSON
# And try dressing that data
puts schema.dress(data)
ADTs with internal contracts
Qrb tries to provide an idiomatic binding for ruby developers. In particular, it uses a simple convention-over-configuration protocol for information contracts. This protocol is easily described through an example. The following ADT definition:
Color = .Color <rgb> {r: Byte, g: Byte, b: Byte}
expects the following ruby class:
class Color
# Constructor & internal representation
def initialize(r, g, b)
@r, @g, @b = r, g, b
end
attr_reader :r, :g, :b
# Public dresser for the RGB information contract on the class
def self.rgb(tuple)
new(tuple[:r], tuple[:g], tuple[:b])
end
# Public undresser on the instance
def to_rgb
{ r: @r, g: @g, b: @b }
end
# ...
end
ADTs with external contracts
When the scenario above is not possible or not wanted (would require core extensions for instance), Qrb allows defining ADTs with external contracts. The following ADT definition:
Color = .Color <rgb> {r: Byte, g: Byte, b: Byte} ColorContract
expected the following ruby module:
module ColorContract
def self.dress(tuple)
Color.new(tuple[:r], tuple[:g], tuple[:b])
end
def self.undress(color)
{ r: color.r, g: color.g, b: color.b }
end
end
About representations
The Rep
representation function mapping Q types to ruby classes is as
follows:
# Any type is represented by Ruby's Object class
Rep(.) = Object
# Builtins are represented by the corresponding ruby class
Rep(.Builtin) = Builtin
# Sub types are represented by the same representation as the super type
Rep(SuperType( s | ... )) = Rep(SuperType)
# Unions are represented by the corresponding classes. The guaranteed result
# class is thus the least common super class (^) of the corresponding
# representations of candidate types
Rep(T1 | ... | Tn) = Rep(T1) ^ ... ^ Rep(Tn)
# Sequences are represented through ::Array.
Rep([ElmType]) = Array<Rep(ElmType)>
# Sets are represented through ::Set.
Rep({ElmType}) = Set<Rep(ElmType)>
# Tuples are represented through ruby ::Hash. Attribute names are always
# symbolized
Rep({Ai => Ti}) = Hash<Symbol => Rep(Ti)>
# Relations are represented through ruby ::Set of ::Hash.
Rep({{Ai => Ti}}) = Set<Hash<Symbol => Rep(Ti)>>
# Abstract data types are represented through the corresponding class when
# specified. ADTs behave as Union types if no class is bound.
Rep(.Builtin <rep> ...) = Builtin
About the default system
See lib/qrb/Q/default.q
for the precise definition of the default system.
In summary,
- Most ruby native (data) classes are already aliased to avoid explicit use of
builtins. In particular,
Integer
,String
, etc. - A
Boolean
union type also hides the TrueClass and FalseClass distinction. - Date, Time and DateTime ADTs are also provided that perform common conversions from JSON strings, through iso8601.