0.04
Low commit activity in last 3 years
A long-lived project that still receives updates
Pseudo type system on struct.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

TypeStruct

Imitating static typed struct.

All type is checked by === method.

Motivation

I don't like Hash

p h #=> {color: 'red', width: 120, height: 200}

No name

What is this a data? We cannot know this name.

Where is this an instance from? How do we grep doing?

Bad syntax

h[:widht] #=> Cannot detect typo
#=> nil
h.dig(:widht) #=> ditto
#=> nil
h.fetch(:widht) #=> Can detect typo, But too long and cannot know suggestion from did_you_mean gem
# KeyError: key not found: :widht

Too freedom

# Where is from `who` key? Is this expected?
p h #=> {color: 'red', width: 120, height: 200, who: 'are you?'}

I like Struct

Grepable Name

Circle = Struct.new(:color, :width, :height)
circle = Circle.new('red', 120, 200)

Good syntax

circle.color
#=> 'red'
circle.widht
# NoMethodError:
Did you mean?  width
               width=

Strictly members

circle.who = "are you?"
# NoMethodError: undefined method `who='

Evolution

  • Can use keyword arguments
  • Add Type system
  • Recursive Mapping

This is the TypeStruct.

Usage

Check type

Sample = TypeStruct.new(
  str: String,
  reg: /exp/,
  num: Integer,
  any: Object,
)

sample = Sample.new(
  str: "instance of String",
  reg: "match to regexp",
  num: 10,
  any: true,
)

p sample
#=> #<Sample str="instance of String", reg="not match to regexp", num=10, any=true>

p sample.to_h
#=> {:str=>"instance of String", :reg=>"not match to regexp", :num=>10, :any=>true}

p sample.str
#=> "instance of String"

sample.string #=> NoMethodError
sample.str = 1 #=> TypeError

Recursive Mapping

Generate object from hash by recursive.

Like JSON package of golang and crystal-lang.

Point = TypeStruct.new(
  x: Integer,
  y: Integer,
)
Color = TypeStruct.new(
  code: /\A#[0-9a-f]{6}\z/i,
)
Line = TypeStruct.new(
  start: Point,
  end: Point,
  color: Color,
)

hash = JSON.parse(%({"start":{"x":3,"y":10},"end":{"x":5,"y":9},"color":{"code":"#CAFE00"}}))
line = Line.from_hash(hash)

p line
#=> #<Line start=#<Point x=3, y=10>, end=#<Point x=5, y=9>, color=#<Color code="#CAFE00">>
p line.start.y
#=> 10
line.stort
#=> NoMethodError

Four special classes

Union

Union is an object express class that some classes as one class like crystal Union.

Union#=== check all object with === method.

Foo = TypeStruct.new(
  bar: TypeStruct::Union.new(TrueClass, FalseClass)
)
p Foo.new(bar: false) #=> #<Foo bar=false>

or add Class#| method by refinements

require "type_struct/ext"
using TypeStruct::Union::Ext
Foo = TypeStruct.new(
  bar: TrueClass | FalseClass,
)

ArrayOf

ArrayOf is an object express array type.

ArrayOf#=== check all item with === method.

Bar = TypeStruct.new(
  baz: TypeStruct::ArrayOf.new(Integer),
)
p Bar.new(baz: [1, 2, 3]) #=> #<Bar baz=[1, 2, 3]>

HashOf

HashOf#=== check all keys and values with === method.

Baz = TypeStruct.new(
  qux: TypeStruct::HashOf.new(String, TypeStruct::ArrayOf.new(Integer))
)
p Baz.new(qux: { "a" => [1, 2, 3] }) #=> #<Baz qux={"a"=>[1, 2, 3]}>
p Baz.from_hash(qux: { "a" => [1, 2, 3] }) #<Baz qux={"a"=>[1, 2, 3]}>
p Baz.new(qux: { :a  => [1, 2, 3] }) #=> TypeError
p Baz.new(qux: { "a" => [1, 2, nil] }) #=> TypeError

Interface

Interface is an object for duck typing like golang interface.

Interface#=== check all method using respond_to?

Foo = TypeStruct.new(
  bar: TypeStruct::Interface.new(:read, :write)
  # or Interface.new(:read, :write) on required 'type_struct/ext'
)
Foo.new(bar: $stdin)
Foo.new(bar: 1) #=> TypeError

Mix

require "type_struct/ext"
using TypeStruct::Union::Ext
Baz = TypeStruct.new(
  qux: ArrayOf(Integer | TrueClass | FalseClass) | NilClass
)
p Baz.new(qux: [1]) #=> #<AAA::Baz qux=[1]>
p Baz.new(qux: [true, false]) #=> #<AAA::Baz qux=[true, false]>
p Baz.new(qux: nil) #=> #<AAA::Baz qux=nil>
p Baz.new(qux: 1) #=> TypeError
p Baz.from_hash(qux: [1, 2, false, true]) #=> #<A::Baz qux=[1, 2, false, true]>

Auto Generator

$ echo '{"posts": [{"number":10491,"name":"ksss"}]}' | ruby -r type_struct/generator/json
Post = TypeStruct.new(
  number: Integer,
  name: String,
)
AutoGeneratedStruct = TypeStruct.new(
  posts: ArrayOf(Post),
)

Installation

Add this line to your application's Gemfile:

gem 'type_struct'

And then execute:

$ bundle

Or install it yourself as:

$ gem install type_struct

License

The gem is available as open source under the terms of the MIT License.