record4s

Extensible Records

Record Construction

In record4s, % is used for a signature of extensible records. First, import % to use them.

import com.github.tarao.record4s.%

Then use % as a constructor to instantiate a record.

val person = %(name = "tarao", age = 3)
// person: % {
//   val name: String
//   val age: Int
// } = %(name = tarao, age = 3)

In addition, you can omit field names when they are the same as variable names.

val name = "tarao"
// name: String = "tarao"
val age = 3
// age: Int = 3

%(name, age)
// res0: % {
//   val name: String
//   val age: Int
// } = %(name = tarao, age = 3)

Field Access

You can access to fields in records in a type-safe manner.

person.name
// res1: String = "tarao"

person.age
// res2: Int = 3

Accessing an undefined field is statically rejected.

person.email
// error: 
// value email is not a member of com.github.tarao.record4s.%{val name: String; val age: Int} - did you mean person.wait?

Extending Records

You can extend records by + or updated.

val personWithEmail = person + (email = "tarao@example.com")
// personWithEmail: % {
//   val name: String
//   val age: Int
//   val email: String
// } = %(name = tarao, age = 3, email = tarao@example.com)

It is possible to extend a record with multiple fields.

person + (email = "tarao@example.com", occupation = "engineer")
// res4: % {
//   val name: String
//   val age: Int
//   val email: String
//   val occupation: String
// } = %(name = tarao, age = 3, email = tarao@example.com, occupation = engineer)

Record Concatenation

You can concatenate two records by ++ or concat.

val email = %(email = "tarao@example.com")
// email: % {
//   val email: String
// } = %(email = tarao@example.com)

person ++ email
// res5: % {
//   val name: String
//   val age: Int
//   val email: String
// } = %(name = tarao, age = 3, email = tarao@example.com)

Field Update

If you extend a record by existing field label, then the field value is overridden. The type of the field may differ from the existing one.

person + (age = person.age + 1)
// res6: % {
//   val name: String
//   val age: Int
// } = %(name = tarao, age = 4)

personWithEmail + (email = %(user = "tarao", domain = "example.com"))
// res7: % {
//   val name: String
//   val age: Int
//   val email: % {
//     val user: String
//     val domain: String
//   }
// } = %(name = tarao, age = 3, email = %(user = tarao, domain = example.com))

This also applies to concatenation. The semantics is that "the latter one wins" like duplicate keys in Map construction or concatenation.