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

wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
handnot2
Samly can be used to enable SAML 2.0 Single Sign On in a Plug/Phoenix application. This library uses Erlang esaml to provide plug enabl...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 11030 135
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
fuelen
Hi all! I want to present a small library which provides a mix task for generating an Entity-Relationship Diagram for Ecto schemas. You...
New
woylie
Phoenix components for pagination, sortable tables and filter forms with Flop and (optionally) Ecto. pagination cursor pagination sorta...
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
mudasobwa
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
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
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
sergio
It’s not that it’s vocabulary is too advanced. It’s something worse. I get lost trying to follow even a paragraph written by Claude. It’...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews