An Erlang 101 question

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

An Erlang 101 question

Lloyd R. Prentice-2
Hello,

I have several functions that look like this:

get_contact(Email) ->
  open_db(),
  Result = dets:lookup(?contacts, Email),
  close_db(),
  Result.

The implication is that the function returns and empty list, [], if it fails to find a record, otherwise it returns a list containing the record.

This makes it easy to distinguish the failure-to-return a record condition, but means that every function that depends up on get_contact/1 needs to do so and know what to do in that case. But that begins to feel like defensive programming.

I can see several ways of dealing with this problem, but it occurs to me that there must be a conventional approach. What might be... what?

Thanks,

LRP



_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: An Erlang 101 question

Robert Raschke

This depends a bit in what you're trying to achieve. Do you need a valid result? Or would some kind of default value be sufficient? In the later case, you could then also provide a get_contact(Email, Default) that returns the default value if none was found in the db.

If you require a valid result to appear in your db, then it could still depend on the rest of your app. Is that something normal you need to be able to react on? If yes, well, then you need to code for it. If not, then you might want to consider it an "exceptional" occurrence and simply crash the process and let the supervisor clean things up.

HTH,
Robby

On Jul 16, 2015 9:03 PM, <[hidden email]> wrote:
Hello,

I have several functions that look like this:

get_contact(Email) ->
  open_db(),
  Result = dets:lookup(?contacts, Email),
  close_db(),
  Result.

The implication is that the function returns and empty list, [], if it fails to find a record, otherwise it returns a list containing the record.

This makes it easy to distinguish the failure-to-return a record condition, but means that every function that depends up on get_contact/1 needs to do so and know what to do in that case. But that begins to feel like defensive programming.

I can see several ways of dealing with this problem, but it occurs to me that there must be a conventional approach. What might be... what?

Thanks,

LRP



_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions

_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: An Erlang 101 question

Garrett Smith-5
In reply to this post by Lloyd R. Prentice-2
Hey, I'm all for a direct solution, but this opening and closing of
the db for each read feels a little cavalier, even for me ;)

I would use a gen_server (e2 service) to maintain the open db,
flushing on each write to guard against data loss if the VM is
shutdown abruptly.

But to answer your question, a more canonical interface here would be
to return either {ok, Contact} or error (mapped from [Contact] or []
respectively) - or just return Contact and generate an exception if
the contact doesn't exists. The form depends on what you want this
function to do. See dict for an example of an interface that provide
both interfaces - and lets the user decide.

You're right though, exposing the dets interface here is wrong,
morally speaking.

On Thu, Jul 16, 2015 at 2:02 PM,  <[hidden email]> wrote:

> Hello,
>
> I have several functions that look like this:
>
> get_contact(Email) ->
>   open_db(),
>   Result = dets:lookup(?contacts, Email),
>   close_db(),
>   Result.
>
> The implication is that the function returns and empty list, [], if it fails to find a record, otherwise it returns a list containing the record.
>
> This makes it easy to distinguish the failure-to-return a record condition, but means that every function that depends up on get_contact/1 needs to do so and know what to do in that case. But that begins to feel like defensive programming.
>
> I can see several ways of dealing with this problem, but it occurs to me that there must be a conventional approach. What might be... what?
>
> Thanks,
>
> LRP
>
>
>
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: An Erlang 101 question

Martin Karlsson-2
In reply to this post by Lloyd R. Prentice-2
It boils down to what the caller wants to do with the result and if it
makes sense to handle it at all.

For database stuff I usually do:
{ok, Result} = query().

and return something else for errors, or others, such as:

{error, not_found} = query().

This way those who don't care about stuff going wrong do:
{ok, Result} = query().
knowing that Result will always contain something useful.

And those who do care:

case query() of
  {ok, Result} -> 'hello world!'.
  {error, not_found} -> ooops
end.

If you expect your queries to always success (i.e the errors are not
part of the normal flow) you can even throw on errors and just return
the result.

Result = query()

and if you need to check it:

try query() of
   Result -> Result
catch
    {throw, not_found} -> oops
end


Your current case is just a variant of the first option but with less
information. It is harder to pattern match meaning you'd basically
always need:

case query() of
   [] -> not_found;
   Result -> 'hello world!'
end


mnesia is a funny application which uses both of the above approaches
depending on if you are using mnesia:transaction (returns {atomic,
Result} or {aborted, Reason}}) vs mnesia:activity which throws on
errors.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: An Erlang 101 question

Lloyd R. Prentice-2
In reply to this post by Garrett Smith-5
Hi Garrett and Martin,

Yes, I'm using dets temporarily until I decide what database to use. All the dets-exposed functions are going through an api, e.g.:

contacts:get_contact(Email) ->
   contacts_api:get_contact(Email).

contacts_api:get_contact(Email) ->
   dets_contacts:get_contact(Email).

get_contact(Email) ->
   open_db(),
   Result = dets:lookup(?contacts, Email),
   close_db(),
   Result.

Sort of in the spirit of your suggestion to get something working then sweat the edge cases.

I'm not confident that the second level of indirection is necessary, but it reflects my current understanding of how to isolate record structures to one module only. I'm using Jesper's repr/2 code to access values.

Re: the idea of exposing db calls through a gen_server or e2 service, would this apply only to dbs such as dets that don't have transactions? Or is it good practice regardless of of the db back-end?
   
As my last few posts may have hinted, with the help of Jesper and others I'm struggling to understand conventional Erlang practices that I, at least, have not found well documented. That's the problem of being a one-man-band. I can't call in my supervisor when I'm stuck. But folks have been extraordinarily generous with tips and information for which I'm profoundly grateful.

Martin--- thanks for your suggestion. I'll adopt it.

Thanks to all,

Lloyd
   

-----Original Message-----
From: "Garrett Smith" <[hidden email]>
Sent: Thursday, July 16, 2015 5:37pm
To: "Lloyd Prentice" <[hidden email]>
Cc: "erlang-questions" <[hidden email]>
Subject: Re: [erlang-questions] An Erlang 101 question

Hey, I'm all for a direct solution, but this opening and closing of
the db for each read feels a little cavalier, even for me ;)

I would use a gen_server (e2 service) to maintain the open db,
flushing on each write to guard against data loss if the VM is
shutdown abruptly.

But to answer your question, a more canonical interface here would be
to return either {ok, Contact} or error (mapped from [Contact] or []
respectively) - or just return Contact and generate an exception if
the contact doesn't exists. The form depends on what you want this
function to do. See dict for an example of an interface that provide
both interfaces - and lets the user decide.

You're right though, exposing the dets interface here is wrong,
morally speaking.

On Thu, Jul 16, 2015 at 2:02 PM,  <[hidden email]> wrote:

> Hello,
>
> I have several functions that look like this:
>
> get_contact(Email) ->
>   open_db(),
>   Result = dets:lookup(?contacts, Email),
>   close_db(),
>   Result.
>
> The implication is that the function returns and empty list, [], if it fails to find a record, otherwise it returns a list containing the record.
>
> This makes it easy to distinguish the failure-to-return a record condition, but means that every function that depends up on get_contact/1 needs to do so and know what to do in that case. But that begins to feel like defensive programming.
>
> I can see several ways of dealing with this problem, but it occurs to me that there must be a conventional approach. What might be... what?
>
> Thanks,
>
> LRP
>
>
>
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://erlang.org/mailman/listinfo/erlang-questions


_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|

Re: An Erlang 101 question

Garrett Smith-5
A few observations...

I think the problem you're looking at resembles the data server in the
e2 tutorial:

http://e2project.org/tutorial.html

In the tutorial, you start out creating a super simple data interface
for reading and writing values:

http://e2project.org/tutorial.html#data-access-api

Later, to make that interface available to the system, you wrap it in
a registered process (e2 service):

http://e2project.org/tutorial.html#data-server

That's an indirection, but the step serves an important role: to
implement a lifecycle for the data access layer. By lifecycle I mean,
what do you need to do start the data facilities? What do you need to
access them? The indirection gives you process isolation as well and a
chance to recover from failure. It serves as the "data service" to
your app, wrapping the details that consumers don't care about and
working to make the facility as available as possible.

In your case, you could simply rename the modules from the tutorial this way:

    mydb_db -> contacts_dets
    mydb_data -> contacts

So call to contacts:get_contact/1 is a service front-end, which calls
e2_service:call/2 (or gen_server:call/2 if you want to type more
code). That sends a message from the client to the service process.
The message handler (handle_msg/3 in e2, or handle_call/3 for
gen_server) is called on the service process side and just passes the
Db reference (managed by the service) and the additional args
(provided by the client call) to contacts_dets, which implements the
dets specific backend.

Imagine this for your contact operations and you've got it!

As for these ideas about hiding record definitions, I think for your
case, right now, I would just forget everything there. It's not
helping you. It's really not a problem to let a record propagate
throughout your application. It might be a problem in time, but it's
not now. It might never be.

That said, working with records as a data structure IMO is a bit
painful and I'd be _tempted_ to use maps for attribute data in and out
of your contacts_dets module. By "attribute data" here I mean non key
values that tend to change over time. So adding a contact might look
like this:

    contacts_dets:update_contact(Key, Attrs)

Attrs here is a map, which contacts_dets can coerce into whatever
record it's using.

But this takes work and code and what does it get you? It's nice that
you don't have a gnarly record definition in use everywhere, but the
map code will look similar. Is it easier to maintain? I doubt it. If I
were you right now, I'd just let this record leak everywhere. You have
more interesting problems to solve.

So quick summary of my thinking:

- Copy the e2 tutorial code and rename the modules as per above

- Replace the get, set, and del functions with the operations that you
want for contacts

- Happily use this facility in your application for all your contact
related needs!

On Thu, Jul 16, 2015 at 5:46 PM,  <[hidden email]> wrote:

> Hi Garrett and Martin,
>
> Yes, I'm using dets temporarily until I decide what database to use. All the dets-exposed functions are going through an api, e.g.:
>
> contacts:get_contact(Email) ->
>    contacts_api:get_contact(Email).
>
> contacts_api:get_contact(Email) ->
>    dets_contacts:get_contact(Email).
>
> get_contact(Email) ->
>    open_db(),
>    Result = dets:lookup(?contacts, Email),
>    close_db(),
>    Result.
>
> Sort of in the spirit of your suggestion to get something working then sweat the edge cases.
>
> I'm not confident that the second level of indirection is necessary, but it reflects my current understanding of how to isolate record structures to one module only. I'm using Jesper's repr/2 code to access values.
>
> Re: the idea of exposing db calls through a gen_server or e2 service, would this apply only to dbs such as dets that don't have transactions? Or is it good practice regardless of of the db back-end?
>
> As my last few posts may have hinted, with the help of Jesper and others I'm struggling to understand conventional Erlang practices that I, at least, have not found well documented. That's the problem of being a one-man-band. I can't call in my supervisor when I'm stuck. But folks have been extraordinarily generous with tips and information for which I'm profoundly grateful.
>
> Martin--- thanks for your suggestion. I'll adopt it.
>
> Thanks to all,
>
> Lloyd
>
>
> -----Original Message-----
> From: "Garrett Smith" <[hidden email]>
> Sent: Thursday, July 16, 2015 5:37pm
> To: "Lloyd Prentice" <[hidden email]>
> Cc: "erlang-questions" <[hidden email]>
> Subject: Re: [erlang-questions] An Erlang 101 question
>
> Hey, I'm all for a direct solution, but this opening and closing of
> the db for each read feels a little cavalier, even for me ;)
>
> I would use a gen_server (e2 service) to maintain the open db,
> flushing on each write to guard against data loss if the VM is
> shutdown abruptly.
>
> But to answer your question, a more canonical interface here would be
> to return either {ok, Contact} or error (mapped from [Contact] or []
> respectively) - or just return Contact and generate an exception if
> the contact doesn't exists. The form depends on what you want this
> function to do. See dict for an example of an interface that provide
> both interfaces - and lets the user decide.
>
> You're right though, exposing the dets interface here is wrong,
> morally speaking.
>
> On Thu, Jul 16, 2015 at 2:02 PM,  <[hidden email]> wrote:
>> Hello,
>>
>> I have several functions that look like this:
>>
>> get_contact(Email) ->
>>   open_db(),
>>   Result = dets:lookup(?contacts, Email),
>>   close_db(),
>>   Result.
>>
>> The implication is that the function returns and empty list, [], if it fails to find a record, otherwise it returns a list containing the record.
>>
>> This makes it easy to distinguish the failure-to-return a record condition, but means that every function that depends up on get_contact/1 needs to do so and know what to do in that case. But that begins to feel like defensive programming.
>>
>> I can see several ways of dealing with this problem, but it occurs to me that there must be a conventional approach. What might be... what?
>>
>> Thanks,
>>
>> LRP
>>
>>
>>
>> _______________________________________________
>> erlang-questions mailing list
>> [hidden email]
>> http://erlang.org/mailman/listinfo/erlang-questions
>
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://erlang.org/mailman/listinfo/erlang-questions