sunaku

sunaku

Hey folks,

I’m pleased to announce StructyRecord, my very first Elixir library! :birthday:

I created this library to improve the usability of Records (which are clunky to use in comparison to the first-class language support for Structs) because my performance-sensitive Elixir applications make heavy use of ETS tables that rely on Records for data representation.

I hope that it’s also useful to you, enjoy! :gift:

Cheers.

Readme

StructyRecord provides a Struct-like interface for your Records.

  • Use your record’s macros in the same module where it is defined!
  • Access and update fields in your record through named macro calls.
  • Create and update records at runtime (not limited to compile time).
  • Calculate 1-based indexes to access record fields in :ets tables.

To get started, see the documentation for StructyRecord.defrecord/3:

iex> h StructyRecord.defrecord

Features

The defined module provides the following guards, macros, and functions.

Guards:

  • is_record/1 to check if argument loosely matches this record’s shape

Macros:

  • record?/1 to check if argument strictly matches this record’s shape
  • record/0 to create a new record with default values for all fields
  • record/1 to create a new record with the given fields and values
  • record/1 to get the zero-based index of the given field in a record
  • record/1 to convert the given record into a Keyword list
  • record/2 to get the value of a given field in a given record
  • record/2 to update an existing record with the given fields and values
  • ${field}/1 to get the value of a specific field in a given record
  • ${field}/2 to set the value of a specific field in a given record
  • keypos/1 to get the 1-based index of the given field in a record

Functions:

  • record!/1 to create a new record at runtime with the given fields and values
  • record!/2 to update an existing record with the given fields and values

Examples

Activate this macro in your environment:

require StructyRecord

Define a structy record for a rectangle:

StructyRecord.defrecord Rectangle, [:width, :height] do
  def area(r=record()) do
    width(r) * height(r)
  end

  def perimeter(record(width: w, height: h)) do
    2 * (w + h)
  end

  def square?(record(width: same, height: same)), do: true
  def square?(_), do: false
end

Activate its macros in your environment:

use Rectangle

Create instances of your structy record:

rect = Rectangle.record()                      #-> {Rectangle, nil, nil}
no_h = Rectangle.record(width: 1)              #-> {Rectangle, 1, nil}
no_w = Rectangle.record(height: 2)             #-> {Rectangle, nil, 2}
wide = Rectangle.record(width: 10, height: 5)  #-> {Rectangle, 10, 5}
tall = Rectangle.record(width:  4, height: 25) #-> {Rectangle, 4, 25}
even = Rectangle.record(width: 10, height: 10) #-> {Rectangle, 10, 10}

Get values of fields in those instances:

tall |> Rectangle.height()            #-> 25
tall |> Rectangle.record(:height)     #-> 25
Rectangle.record(height: h) = tall; h #-> 25

Set values of fields in those instances:

even |> Rectangle.width(1)         #-> {Rectangle, 1, 10}
even |> Rectangle.record(width: 1) #-> {Rectangle, 1, 10}

even |> Rectangle.width(1) |> Rectangle.height(2) #-> {Rectangle, 1, 2}
even |> Rectangle.record(width: 1, height: 2)     #-> {Rectangle, 1, 2}

Use your custom code on those instances:

rect |> Rectangle.area() #-> (ArithmeticError) bad argument in arithmetic expression: nil * nil
no_h |> Rectangle.area() #-> (ArithmeticError) bad argument in arithmetic expression: 1 * nil
no_w |> Rectangle.area() #-> (ArithmeticError) bad argument in arithmetic expression: nil * 2
wide |> Rectangle.area() #-> 50
tall |> Rectangle.area() #-> 100
even |> Rectangle.area() #-> 100

rect |> Rectangle.perimeter() #-> (ArithmeticError) bad argument in arithmetic expression: nil + nil
no_h |> Rectangle.perimeter() #-> (ArithmeticError) bad argument in arithmetic expression: 1 + nil
no_w |> Rectangle.perimeter() #-> (ArithmeticError) bad argument in arithmetic expression: nil + 2
wide |> Rectangle.perimeter() #-> 30
tall |> Rectangle.perimeter() #-> 58
even |> Rectangle.perimeter() #-> 40

rect |> Rectangle.square?() #-> true
no_h |> Rectangle.square?() #-> false
no_w |> Rectangle.square?() #-> false
wide |> Rectangle.square?() #-> false
tall |> Rectangle.square?() #-> false
even |> Rectangle.square?() #-> true

Showing Posts 1 to 5

OvermindDL1

OvermindDL1

Now if only the Elixir built-in Protocol’s supported dispatch based on the first tuple element. ^.^;

Love records though!

sunaku

sunaku OP

Version 0.2.0 (2021-02-18)

This release adds a convenient new shorthand syntax for the record() macro,
renames field accessors to prevent name collisions, clarifies docs, and more.

Incompatible:

  • Rename record!/1 function to from_list/1.

  • Rename record!/2 function to merge/2.

  • Add get_ and put_ prefix to field accessors.

    Field names can no longer conflict with defined macros & functions.

  • Don’t check argument types in Elixiry interface.

    It broke simple macro expansion when used in case/function clauses.

Enhancements:

  • Add Module.{_} syntax for Module.record(_).

    https://stackoverflow.com/a/51313720/120075

  • Add inspect/2 for friendlier inspection.

  • Add to_list/0 to get record’s template.

  • Add to_list/1 alias for record/1 macro.

  • Add index/1 to get field index in tuple.

  • Add get/2 macro as an alias to record/2.

  • Add put/2 macro as an alias to record/2.

  • Define documentation for all macros and functions.

Housekeeping:

  • record?/1 macro: only use pattern matching check.

  • keypos/1 macro: don’t call module being defined.

  • mix.exs: drop application(); use runtime: false.

mudasobwa

mudasobwa

Creator of Cure

You might want to check this blog for the insight of how to handle it differently in guards.

sunaku

sunaku OP

Great article! That unravels the mystery behind how records magically work in pattern matches and behave like normal functions otherwise. I’m eager to apply this knowledge in future releases. Thanks.

sunaku

sunaku OP

Version 0.2.1 (2021-02-20)

Housekeeping:

  • README: runtime: false suggestion broke releases.

    The runtime: false flag in the user’s mix.exs file prevents the
    structy_record application (which bundles module StructyRecord)
    from being included in app releases. This breaks defined macros and
    functions that delegate to the StructyRecord.from_list/3 function
    because the StructyRecord module won’t be included in app releases!

— All posts loaded —

Where Next? Top

Trending in Announcing Top

type1fool
WebAuthnLiveComponent WebAuthnComponents See this post about renaming the package. Passwordless authentication for Phoenix LiveView app...
New
GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
ahamez
Hi everyone, I’ve been working on this protobuf library for 3 years. We use it in the company I work for, EasyMile, to communicate with ...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
kip
I’ll shortly be launching Text, a nascent text analysis library. Current functionality In this early version (not ready for prime time) ...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
CodeSync
:microphone: ElixirConf 2026 - Call for Talks is open! We’re heading to Chicago :united_states: :round_pushpin: In person + virtual :d...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews