[mercury-users] Maths and contexts.

Michael Richter ttmrichter at gmail.com
Fri Apr 20 15:25:42 AEST 2012


I'm trying to implement the General Decimal
Arithmetic<http://speleotrove.com/decimal> (henceforth
GDA) specification in Mercury as a learning project.  So far it's been a
lot of fun and I've been learning quite a bit (as well as getting
frequently humbled by Boney and ski in #mercury as they gently suggest
obvious ways to improve my code).  There is one problem I've been facing
that I can't resolve, however.  Boney suggested the use of type classes to
solve it, but I just can't see how they'd help.

The problem is this: all calculations in GDA are done in a "context".  This
context specifies the precision (in decimal digits) of the calculation, the
rounding strategy in use when values have to be truncated and a set of
flags for deaing with various error conditions.  The resulting type
declarations look like this:

:- type rounding_strategy
    ---> round_down       % mandatory strategies
       ; round_half_up
       ; round_half_even
       ; round_ceiling
       ; round_floor
       ; round_half_down  % optional strategies
       ; round_up
       ; round_05up.

:- type signal
    ---> signal( flag         :: bool
               , trap_enabler :: bool ).

:- type context
    ---> context( precision         :: integer
                , rounding          :: rounding_strategy
                , clamped           :: signal
                , division_by_zero  :: signal
                , inexact           :: signal
                , invalid_operation :: signal
                , overflow          :: signal
                , rounded           :: signal
                , subnormal         :: signal
                , underflow         :: signal ).

:- type sign
    ---> positive
       ; negative.

:- type decimal
    ---> number( sign        :: sign
               , coefficient :: integer
               , exponent    :: integer )
       ; infinity(sign)
       ; quiet_nan(sign)
       ; signalling_nan(sign).

:- type dec == decimal.


>From this point on the API is straightforward.  Some example function
declarations:

:- func basic_default_context          = (context::out) is det.
:- func extended_default_context       = (context::out) is det.
:- func to_scientific_string(dec::in)  = (string::out)  is det.
:- func to_engineering_string(dec::in) = (string::out)  is det.
:- func to_number(string::in)          = (dec::out)     is det.
:- func abs(dec::in)                   = (dec::out)     is det.
:- func add(dec::in, dec::in)          = (dec::out)     is semidet.


Here's where the problem exists.  *All* of the above functions after the
first two are executed in a context that restricts precision, that dictates
signalling policy and that specifies how rounding is to be applied.  If I
implement, say, divide/2 (:- func divide(dec::in, dec::in) = (dec::out) is
semidet.), and divide 1 by 3, it is the associated context that stops the
division from going on forever.

And here's the problem: how do I associate a given calculation with a
calculation context?  Just passing it in as a parameter is a non-starter
because these are the implementation functions.  The exposed API will be,
shockingly, "+" for add/2, "/" for divide/2, etc.  So while it would be
easy to do divide(1, 3, MyContext) in code, that's not really an acceptable
solution because I can't do 1 / 3 (MyContext) (or whatever).  "/" (and
other mathematical operations' predicates/functions) is militantly
outfitted with an arity of 2.

One solution I thought of is to pass the context in whenever constructing a
number.  So when I go number(positive, 1, 0) I'd have to add a fourth
parameter to the constructor so it's number(positive, 1, 0,
basic_default_context) or something equivalent.  This solves the problem of
association, however it does so at the price of adding a lot of boilerplate
to each number introduced into the system and into adding a lot of
boilerplate for handing collisions of contexts in operations.  (Consider if
one number operates under round_up rules and another operates under
round_to_even.  Which one holds sway, if any?)

What I'd really like to do is be able to associate the context with the
calculations implicity, perhaps in some kind of framing block or
equivalent.  Boney's suggestion of using type classes seemed plausible, but
upon investigation I can't seem to get them to work that way because the
type variables in the type classes are all related to the
predicate/function declarations (at least by my reading) and here the only
difference is down in the predicate/function implementation.  The kind of
thing I'd ideally like to see in place is something like the following
pseudo-Mercury:

…
using_context(MyContex) :
    That = This + 3,
    Result = That * TheOther.


I just can't see any way of doing this (or something equivalent to this)
short of abusing DCG notation or using global state, neither of which would
really be acceptable.

-- 
"Perhaps people don't believe this, but throughout all of the discussions
of entering China our focus has really been what's best for the Chinese
people. It's not been about our revenue or profit or whatnot."
--Sergey Brin, demonstrating the emptiness of the "don't be evil" mantra.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.mercurylang.org/archives/users/attachments/20120420/39566e82/attachment.html>


More information about the users mailing list