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

First 5 of 5 Posts Switch mode

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

bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
387 14960 120
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
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

Other Trending Topics Top

type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New
bjorng
We want to introduce a new native datatype to Erlang: native records. Although replacing all tuple records with native records is not our...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New

We're in Beta

About us Mission Statement