[erlang-questions] Exporting a record type

Lloyd R. Prentice lloyd@REDACTED
Sun Jul 12 17:06:01 CEST 2015

Hi Jesper, 

I was going to add you to my pantheon of Erlang heroes only to find that you were already there. 

Thank you!  Your explication makes perfect sense. A basic point still mystifies me, however:

> -opaque book() :: #book{}.

Just exactly what is going on here? My naive interpretation is that we are defining a function name as an alias for a record and hiding the composition of the record. But I'm not sure this is correct since In the various code examples I see the xxxx() form used in -spec definitions, but not code.

Also, in your repr/2 code (which is very suggestive of neat things one can do) what is the significance of the View variable? As I see it now, it's simply a tag like thumbnail_overview, but I'm not comfortable that my understanding is correct.

Profound thanks again,


Sent from my iPad

> On Jul 12, 2015, at 8:31 AM, Jesper Louis Andersen <jesper.louis.andersen@REDACTED> wrote:
>> On Fri, Jul 10, 2015 at 6:11 PM, <lloyd@REDACTED> wrote:
>> So, now what syntax would I use in this module to populate the book record?
> So, we have a book record in a database. We want a representation of that book record in the system. In other words, we want to be able to reify a book in the database as a book in the system. Once we have the book in the system, we can manipulate that book, and then reflect the book back into the database for persistence. This is the general idea.
> The problem is that there are two subsystems which need knowledge about books: the database low level module, and the module representing or manipulating the book. One solution, which is the simple solution, is to put a book record into a header file, and then refer to the book record in all modules using it. The problem of this approach is that any user will be tightly coupled to the books representation, so it is harder to manipulate the representation of the book.
> Any other solution must put the representational knowledge in one module only. And a little thought shows that it is a bad idea to let the DB module contain the knowledge. So you construct a "book" module:
> -module(book).
> -opaque book() :: #book{}.
> -spec unmarshal({xml, Doc, Description} | {json, Doc, Description} | {postgres, ...}) -> {ok, book()} | {error, Reason}.
> -spec marshal(Book, xml | json | transit) -> Data.
> unmarshal takes a notion of a reification-location, i.e., xml, together with a document, Doc, and a "Description" which is a symbolic term describing how to obtain the book from the document. The unmarshaler can then utilize such a description in order to reify the book from the document. As a start, you may want to ignore the description and hard-code the notion of e.g., 'get_title(Doc)', but later on it may be necessary to generalize this why it can be nice to describe something about the document you received. A postgres connection would probably just return some abstract '${ Col => Val }' mapping ColNames to Values in a row. and the reification process is the obvious one. The point is: *someone* has to parse the data into a #book{} record, so it may be necessary to have a generic intermediate format inside the system for doing so.
> "unmarshal" is also used to construct a new book for later persistence. One can imagine adding a variant which is {post, Data, Description} for a C-in-CRUD creation, with parsing, input validation and all that fluff.
> One way of thinking of the description could be path-expressions inside the Xml document in XPath notation. So the Description is
> #{
>     title => "a path to the title",
>     author => "a path to the author",
>     ...
> }
> In turn, the DB code knows, and provides, something about the *location* of data inside the document, and the reifier inside 'book' only uses such location-descriptions to pick out stuff. If the position of the title suddenly changes in a later Document version, the DB code can simply supply a different location.
> Continuing, what can we do with books? Maybe we have a thumbnail overview of books. For this, we only need a few entries:
> repr(Book, {msgpack, View}) -> msgpack:encode(repr(Book, View));
> repr(Book, {json, View}) -> jsx:encode(repr(Book, View));
> repr(book#{ title = T, pubDate = PD, smallImageURL = SI }, thumbnail_overview) ->
>     #{ 
>         thumbnail = SI,
>         title = T,
>         pub_date = PD
>     }.
> Note that a caller can't possible know the internal representation. A caller gets a "generic" representation[0], which can then be cast in several forms. Any "update" on the #book{} record has to factor through the module as well.
> Now, a cowboy/webmachine RESTful server would load the book from the db and call unmarshall on it in the "resource_exists" callback (or perhaps earlier in the chain in some situations). And once the content-type is negotiated it would use repr/2 to cast such a generic book record into the desired content-type for the desired output-view.
> [0] In a real system, this will almost invariably get generalized further. Once you have a couple of these modules, there are commonality which can be factored out into a more general framework, I'm sure. The key is to avoid having to come "back" to code and change it whenever some other part of the system changes. And make sure that changes localize to one or a few modules. Beware architectures where you have to edit many modules to add a new "vertical" path in the system. This suggests you should be looking to "transpose" the code path.
> -- 
> J.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20150712/0214c909/attachment.htm>

More information about the erlang-questions mailing list