Cure Status Update

I am back, working on Cure.

The license (Apache2, same as Elixir) is added. To run examples do make clean && make all in the project folder and then ./run_cure.sh examples/14_pipe.cure. The handy compilation/running is pending.

What’s There

Pipes are monadic (see cure-lang/examples/14_pipe.cure at main · am-kantox/cure-lang · GitHub ). 5 |> double() would return Ok(10) assuming def double(x: Int): Int = x * 2, Ok(5) |> double() too. Error(“Nah”) as LHO would not attempts to call RHO and would be propagated.
Peano arithmetics Nat declared as Succ(Succ(…(Zero))) is there, although I am not sure I am to keep it. Int and Float are to be [most likely] ditched in favor of Number.
List and Vector are fully functional.
Ok(T), Error(E), Result(T, E), etc.

Questions

▸ Shall I create kinda google group in addition to GH Discussions? I am not a big fan of groups myself.
▸ Shall I continue with typeclasses which are half-done or there is something more expected?
▸ What kind of tooling should come first (now the compilation is awkward.)
▸ Shall I make LSP fully robust before anything else (including SMT solver intergration)?

Thanks.

1 Like

Why on earth would you “ditch” Int and Float “in Favour of Number”? Integers and floats do not satisfy the same laws. For example, addition of integers is associative, while addition of floats most definitely isn’t. X -Y + Y always gives X back in integer arithmetic, not in floating-point arithmetic. For Cure’s purposes, it would make sense to eliminate Float, but not to eliminate trustworthy arithmetic.

1 Like

I should have said Decimal not Number (andalso I am thinking about Rational.) I meant I want the trustworthy arithmetic only.

But what does Decimal mean? Does it mean scaled decimal arithmetic (n x 10**s)?
I wrote and tried to get published a verified specification of scaled
decimal arithmetic (there being no such thing in the LIA standards),
and it’s a bit less simple to reason about than one might have hoped.
For example, there are infinitely many distinguishable zeros and of
course infinitely many pairs X Y where X-Y is zero but X and Y
are distinguishable nonzero numbers.
For what it’s worth, having implemented both scaled decimal and
rational arithmetic in more than one language,
rational arithmetic seems to be somewhat easier to implement and a lot
easier to reason about.

1 Like

That’s why I hesitate to just jump in. I thought about ISO/IEC 60559:2020 as a starting point, or even Decimal v2.3.0 — Documentation .

That was my gut feeling, but I frankly didn’t dig into to be able to argue about.

The only thing I can tell is I find it hostile to the 99.(9)% of users to keep in mind all the drawbacks of floating point arithmetics and to decide whether, say, currency exchange rates coming from the external source must be List(Result(Int, _)) or List(Result(Float, _)).

ISO 60599 is precisely what it says on the cover: a standard for FLOATING-POINT arithmetic.
In fact it is a standard that covers both binary and decimal floating-point.
Decimal floats have all the quirks of binary floats and add some of their own.

There are standards for programming languages with binary and decimal fixed-point
arithmetic, notably COBOL, PL/I, SQL, and Ada. Several computers have had
instruction-set-level support for decimal arithmetic, including the VAX computers
of fond memory and the IBM System/360 series, whose latest incarnation, z/Series,
is still with us.

We can classify fixed-point systems in several ways. One way is

  • bounds and scale are part of the type (COBOL, PL/I, SQL, Ada).
    The size is known at compile time so variables can be stack-allocated or
    statically allocated like bounded integers. Arithmetic operations are
    loop-free. The scale adjustments needed for addition, subtraction, and
    comparison are known at compile time.
  • bounds are not part of the type but the scale is (this can be done easily
    in any language with dependent types, like Agda and in fact Haskell and C++
    are just powerful enough to do it). Values have to be heap-allocated, and
    operations are not loop-free, but scale adjustments can be done at
    compile time.
  • scale is an independent part of the value. That is, a number is (n / 10**s)
    with no restrictions on n or s so that multiple (n,s) pairs represent
    numerically equal but distinguishable values.
  • scale is a derived part of the value. That is, a number is still (n / 10**s)
    but this is constrained in one way.
    Alternative II: n is 0 and s is 0 or n is not divisible by 10.
    Alternative I: s is 0 or (n is not zero, s > 0, and n is not divisible by 10).
    Alternative I allows a fixed-point value that is integral to be that integer.

I came into this area by way of Smalltalk. I wanted to implement the ANSI Smalltalk
standard, and that includes a ScaledDecinal class. The standard appeals to the LIA
standard for the semantics, which is unfortunate, because no part of the LIA standard
has ever had anything to say about it, and the result is that different Smalltalk
systems do different things.

  • One Smalltalk bases its decimal arithmetic on IBM/360 hardware: numbers have up to
    31 decimal digits, a sign, and a scale.
  • Two Smalltalk systems say “a ScaledDecimal number is really a rational number,
    any rational number, even one like 22/7, and the scale has no influence on how
    calculations are done but only on how values are printed”, which still has me shaking
    my head in disbelief that anyone ever thought that was a good idea.
  • My Smalltalk follows the independent-part-of-the-value model, which seems forced by
    the ANSI interface, with the consequence that 0.00s2 and 0.000s3 are numerically equal
    but do not behave the same way.

As for the Elixir Decimal you linked to,

  • having THREE numbers in the representation instead of two seems wasteful;
    worse than that, it creates a +0 vs -0 distinction, in the representation.
  • the example

D.div(1, 3)
Decimal.new(“0.3333333333333333333333333333”)
came as a very nasty shock. There is simply no way to justify this.
If x and y are decimal numbers, then x/y is a rational number, NOT a fixed point
number, and if there is any coherent rule for how many decimal places the
quotient should have, nobody seems to have found one.

  • Decimal has something called a “context” which is stored in the
    process dictionary, meaning that its arithmetic is state-dependent ,
    which is NOT something you want to have to include in your reasoning.
    This is because Decimal is yet another FLOATING-POINT arithmetic.

Note that I am not saying that Decimal isn’t useful or that ISO 60559 isn’t useful.
What I’m saying is that they are floating-point systems and as such messy and
tricky to reason about.

Of course it depends on what calculations you need to reason about.
Addition, subtraction, negation, absolute value, sign, multiplication,
any of several variants of quotient and remainder to a specified precision,
even square and cube roots to a specified precision, and comparison,
these all fit exact scaled decimal arithmetic very well, especially using
alternative I. If you want trig functions, hyperbolic functions, Bessel functions,
gamma, beta, exponentials and logarithms etc, then you must be satisfied with
inexact approximations and decimal floats may be a good choice.
If you want financial calculations, where thanks to compound interest
many functions have exponential or logarithmic aspects, a hybrid
approach where calculations are either exact or have a degree of
approximation explicit in the function call, may be the right
approach.

As for currency exchange rates, it is always the case that you can
find a positive integer for each currency such that
Nx units of currency Cx convert to Ny units of currency Cy.
There is no reason to involve floating-point numbers.
(Why yes, my Smalltalk does have currency conversion support.
How ever did you guess?)

1 Like

Thank you a ton for the insight, I cannot express how valuable it is for me. Can I take a look at your implementation or it’s closed source?—I would be extremely grateful.

Well, 10 years in FinTech taught me there are also weird, non-documented and possibly not widely-spread circumstances, when you want to divide an amount in three equal shares and then (past years) join them back to the whole without any shenanigans, which brings Rationals.

Correct, but I was under [possibly wrong] impression there is a good solution that does not require a dedicated code explicitly for currency exchange.

In any case, I am still positive this particular topic deserves much more brain effort that it usually receives, that’s why I postponed it until I have a capacity to figure it out thoroughly. Your advice will be then of a great help, thanks again!