dataclass
case_class
The case_class
macro defines a class whose instances are immutable and provide a natural implementation for the most common methods. It also defines some basic pattern matching functionality, to ease data extraction.
Installation
Add this to your application's shard.yml
:
dependencies:
case_class:
github: lbarasti/case_class
Usage
require "case_class"
Let's define a class with read-only fields
case_class Person{name : String, age : Int = 18}
We can now create instances and access fields
p = Person.new("Rick", 28)
p.name # => "Rick"
p.age # => 28
The equality operator is defined to perform structural comparison
q = Person.new("Rick", 28)
p == q # => true
The hash
method is defined accordingly. This guarantees predictable behaviour with Set and Hash.
visitors = Set(Person).new
visitors << p
visitors << q
visitors.size # => 1
to_s
is also defined to provide a human readable string representation for a case class instance
puts p # prints "Person(Rick, 28)"
Instances of a case class are immutable. A copy
method is provided to build new versions of a given object
p.copy(age: p.age + 1) # => Person(Rick, 29)
Pattern-based parameter extraction
Case classes enable you to extract parameters using some sort of pattern matching. This is powered by a custom definition of the []=
operator on the case class itself.
For example, given the case classes
case_class Person{name : String, age : Int = 18}
case_class Address{line1 : String, postcode : String}
case_class Profile{person : Person, address : Address}
and a Profile
instance profile
profile = Profile.new(Person.new("Alice", 43), Address.new("10 Strand", "EC1"))
the following is supported
age, postcode = nil, nil
Profile[Person[_, age], Address[_, postcode]] = profile
age == profile.person.age # => true
postcode == profile.address.postcode # => true
Note that it is necessary for the variables used in the pattern matching to be initialized before they appear in the pattern.
Skipping the initialization step will produce a compilation error as soon as you try to reuse such variables.
Destructuring assignment
Case classes support destructuring assignment. There is no magic involved here: case classes simply implement the indexing operator #[](idx)
.
person, address = profile
person == profile.person # => true
address == profile.address # => true
The inconvenience with this approach is that the type of both person
and address
at compile time is going to be String | Int32
. This might make your code a bit uglier than it needs to be.
To circumvent this limitation, the to_tuple
method is also provided. This assigns the right type to each extracted parameter even at compile-time
profile.to_tuple # => {Person(...), Address(...)}
person, address = profile.to_tuple
person == profile.person # => true
address == profile.address # => true
The macro also defines a to_named_tuple
method, which provides a natural transformation of your case class instance to NamedTuple
person.to_named_tuple # => {"name": "Alice", "age": 43}
Mind that, by design, both to_tuple
and to_named_tuple
are not recursive - so they will not convert case class fields to tuples / named tuples respectively.
Support for inheritance
The case_class
macro supports inheritance, so the following code is valid
class Vehicle
case_class Car{passengers : Int16} < Vehicle
Known Limitations
- case_class definition must have at least one argument. This is by design. Use
class NoArgClass; end
instead. - trying to inherit from a case class will lead to a compilation error.
case_class A{id : String}
case_class B{id : String, extra : Int32} < A # => won't compile
This is by design. Try defining your case classes so that they inherit from a commmon abstract class instead.
- case_class definitions are body-free. If you want to define additonal methods on a case class, then just re-open the definition:
case_class YourClass{id : String}
class YourClass
# additional methods here
end
Development
To expand the macro
crystal tool expand -c <path/to/file.cr>:<line>:<col> <path/to/file.cr>
Contributing
- Fork it ( https://github.com/lbarasti/case_class/fork )
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
Contributors
- lbarasti - creator, maintainer