View Source Data Structure Agnostic Library for Erlang
Introducing a simple library tailored for Erlang's common data structures (currently supporting proplists, dicts, and maps). With a universal interface, seamlessly convert between structures and harness additional functionalities not natively found in some, or even any, of these data types. Just "do the damn thing" without sweating about the underlying structure.
Contemplating an upgrade for a legacy app from proplists to maps? This tool's
got your back. With ds, maintain consistent calls for a smooth, hassle-free
transition.
key-points-to-remember
Key Points to Remember:
- The central module is
ds. For instance, to fetch values, useds:get. - Calls are designed for brevity. Hence,
ds:getis favored overproplist:get_value. The choice of usingdswith theDandSkeys adjacent to eachother is fully intentional for typing speed. - The supported structures are
maps,
dicts, and
{Key,Value}tuple lists, the latter generally referred in this document as proplists, although{Key,Value}tuple lists are technically only a subset of proplists. - Calls are structure-agnostic with the supported types - meaning the calls will work regardless of the data structure type (as long as the type is supported).
- Functions that return a modified version of the provided data, such as
ds:set(Obj, Key, Value), ensure the returned structure matches the input. For example, ifObjis a map, the output is also a map. - Typically,
Obj(the data structure we're working with) is the firstargument, contrasting the common Erlang practice of making it the last argument. - In most cases, when a
Defaultvalue exists, it's positioned as the function's last argument. E.g.,ds:get(Obj, Key, DefaultValue). - Discussion Point: The default return for missing values is either an
empty string (
"") or an empty list ([]). If this isn't favorable, we're open to making it configurable.
function-reference
Function Reference
Reference Preface 1: When we mention "object" or Obj, we're referring to
the provided data structure, even if it doesn't align with the traditional OO
paradigm.
Reference Preface 2: Optional Arguments are denoted by [Brackets]
getting-setting-and-deleting-one-value
Getting, Setting, and Deleting (one value)
ds:get(Obj, Key, [Default]): Retrieve the value associated withKeyinObj. If missing,Defaultis returned. WithoutDefault, it returns"".ds:set(Obj, Key, Value): SetsKeyinObjtoValueand returns the updated object.
getting-and-setting-multiple-values
Getting and Setting (multiple values)
ds:get_list(Obj, ListOfKeys, [Default]): Fetches values associated with each key inListOfKeys. If a value is missing, it returns either theDefaultor""for that value. Returns a list of values in the same order as the keys inListOfKeys. Example:[A,B] = ds:get_list(Obj, [a,b])ds:set(Obj, ListOfKeyValues): Here,ListOfKeyValuesis a set of{Key,Value}tuples. This function updatesObjin bulk with these values.
deleting-and-filtering-values
Deleting and Filtering Values
ds:delete(Obj, Key)- Remove the entry associated withKeyfromObj. Returns the modified object.ds:delete_list(Obj, ListOfKeys)- Removes all the entries associated with eachKeyinListOfKeysfromObj. Returns the modified object.ds:keep(Obj, ListOfKeys)- Removes all entries fromObjexcept for those whose keys are listed inListOfKeys. Returns the modified object.ds:filter(Obj, Fun)- Iterates overObjand retains only those entries for whichFun(Key, Value)returnstrue. Returns the modified object.
working-with-keys
Working with Keys
ds:has_key(Obj, Key): Checks ifObjcontains an entry forKey. Returnstrueif found, otherwisefalse.ds:rekey(Obj, OldKey, NewKey): RenamesOldKeytoNewKeyinObj. IfNewKeyalready exists, its value is overwritten. Returns the modified object.ds:rekey(Obj, ListOfOldAndNewKeys):ListOfOldAndNewKeysis a list of{OldKey, NewKey}tuples. Renames eachOldKeyto its correspondingNewKey. If aNewKeyalready exists, its value is overwritten. Returns the modified object.
mass-updating
Mass Updating
ds:boolize(Obj, ListOfKeys)- Convert the values inObjassociated with eachKeyinListOfKeysto a booleands:atomize(Obj, ListOfKeys)- Convert the values inObjassociated with eachKeyinListOfKeysto an atom.ds:map(Obj, Fun)- RunFunon every entry inObjand set each entry's new value to the return value fromFun.Funcan be defined as eitherFun(Value) -> NewValueorFun(Key, Value) -> NewValue, either way, the value of each entry will be set toNewValue.ds:update(Obj, ListOfKeys, Fun)- Update the values inObjassociated with eachKeyinListOfKeysby running each associated value through the providedFun(Value). For example, to convert a handful of values to their integer forms (orundefinedif not parseable), you could implement it like this:ds:update(Obj, ListOfKeys, fun(X) -> try list_to_integer(X) catch _:_ -> undefined end)
transforming-updating-on-steroids
Transforming: Updating on Steroids
ds:transform(Obj, TransformList)- Run many different updates onObj.TransformListis a list of{Operation, ListOfKeys}tuples.Operationcan be a function that takes a singleValueand returns a newValue, orOperationcan be any of the following terms:atomize,boolize,date,unixtime,now,{date, DateFormat}.ListOfKeysis a list of keys to convert. Returns theObjwith all the updates applied. Bear in mind, the date and time related functions all assume qdate (@hex.pm) is installed.qdate, however is not an automatic dependency because the rest of the module works without it.
conversion-and-type-checking
Conversion and Type-Checking
ds:type(Obj)- returns the type of data structure (map,dict, orlist).ds:to_list(Obj)- ConvertObjto a proplist. IfObjis already a list, it returns it unchanged.ds:to_map(Obj): ConvertObjto a map. IfObjis already a map, returns it unchanged.ds:to_dict(Obj): ConvertObjto a dict. IfObjis already a dict, returns it unchanged.
comparison-helpers-for-sorting-lists-of-objects
Comparison Helpers for Sorting lists of Objects
ds:compare(ObjA, ObjB, SortCriteria)-SortCriteriacan be a singleKeyor{Operation, Key}tuple, or it can be a list ofKeyor{Operation, Key}tuples.Operationcan be the atoms<orasc(for ascending) or the atoms>ordesc(for descending).Operationcan also be a function of arity 2 (meaning, it takes 2 arguments). These arguments will be the values associated withKeyof fromObjAandObjB. If noOperationis provided (that is, if an item inSortCriteriais not a 2-tuple, it's treated as aKeyandascis used (sort in ascending order).
merging-objects
Merging Objects
ds:merge(ListOfObjects)- Merge the list of objects together. Returns{Merged, Unmerged}. Blank values ("",<<>>,undefined) will be replaced by any other non-blank items. If there are conflicts (where several objects have values for the same key), then those values will be assembled into a list of values and returnedds:merge(ObjA, ObjB)- A shortcut fords:merge([ObjA, ObjB)]). It just merges two objects.ds:guess_merge(ListOfObjects)- This works similarly tods:mergeexcept that this will return a single fully-merged object. Unlikeds:merge, if this encounters a conflict, it goes with the first non-blank value encountered.ds:guess_merge(ObjA, ObjB)- A shortcut fords:guess_merge([ObjA, ObjB)]). It just merges two objects.
add-to-your-rebar-config-s-deps-section
Add to your rebar.config's deps section
{deps, [
erlang_ds
]}.
origins-philosophy
Origins & Philosophy
Erlang DS emerged from the need for a unified module with concise function calls tailored for proplists, especially before Erlang's introduction of the map structure. Although maps have become a preferred choice, legacy code still utilizes proplists or dicts, or even a mix of the trio.
For a few examples of the motivation to create this:
- While
mapsanddictboth support merging,proplistsdoes not. - While
ds:transformandds:rekeycan both be implemented withModule:map,mapis clunky for both. - While
mapsupports getting multiple things per line with pattern matching (#{key:=Value, key2:=Value2} = Map), this is not supported by proplists or dicts. Also, the map matching syntax above will crash if one of the values isn't present, necessitating themaps:get/3function anyway. - The inconsistencies of between the data structure modules makes me constantly
forget which module does it which way.
getandset, for example, which are the most common operations in any of my codebases (and I suspect the most common in yours as well) are implemented inmapsasgetandput, indictasfindandstore, inproplistsasget_valueand not implemented (basically, just delete the old value and prepend the new{Key,Value}tuple), or with thelistsmodule askeyfindandkeystore.
None of these comments is to criticize the Erlang team - they are incredible, and what they've built is incredibly powerful. But no one can be everything to everyone, and tailor their development to every one of their users' requirements or idiocyncracies. Hence, this library was born to bridge such gaps and offer utility.
Constructive feedback and pull requests are heartily welcomed.
PULL REQUESTS ARE WELCOMED
additional-notes
Additional Notes
Formerly known as sigma_proplist, Erlang DS originated from Sigma Star
Systems. After nearly a decade in production, it was
refashioned to support more data structures, resulting in the Erlang DS we have
today.
about
About
Author: Jesse Gumm
Copyright 2013-2023
MIT License