Create a Guide for porting Haskell Numbers to PureScript Numbers?

I was wondering whether there are any guides that show how to port Haskell numbers to PureScript numbers.

Haskell’s numeric type class hierarchy differs greatly from PureScript’s one. Thus, when porting Haskell libraries to PureScript, it’s difficult to know how to change a Haskell one (e.g. RealFrac) into a corresponding type class constraint for PureScript.

Could anyone provide a brief description as to how each numeric type class in Haskell would map to one in PureScript? I believe this issue is partly complicated by what a library is trying to achieve and thus how it uses such numbers to accomplish that goal (e.g. hedgehog).

3 Likes

Good idea! Many of the Haskell classes don’t really have PureScript analogues.

Num

I think Haskell’s Num is closest to our Ring. For instance, from the Haskell docs:

The Haskell Report defines no laws for Num . However, ‘(+)’ and ‘(*)’ are customarily expected to define a ring and have the following properties: […]

and the properties they list are more or less the same as the laws on Ring. The differences are:

  • PureScript’s Ring defines members zero and one for the identities of addition and multiplication, whereas Num is a bit more general and defines a fromInteger member which subsumes both (since fromInteger 0 is expected to be the additive identity, and fromInteger 1 is expected to be the multiplicative identity).
  • Haskell’s Num includes abs and signum, but PureScript’s Ring doesn’t. The abs and signum functions are a little bit harder to justify from a mathematical perspective (they aren’t part of the definition of a ring).

The omission of fromInteger doesn’t harm us too much, since conversion from any integer type to a ring can be reconstructed fairly easily with what PureScript’s Ring gives you:

import Data.Monoid (power)
import Data.Monoid.Additive (Additive(..))

fromInt :: forall a. Ring a => Int -> a
fromInt x =
  if x >= 0
    then runAdditive (power (Additive one) x)
    else negate (fromInt (negate x))

Real

I am not sure why this class is called Real. I think a better name would be ToRational, because all it does is define a mapping to Rational. There is no type class for this in PureScript; you’ll have to use monomorphic functions if you want to convert some number type to Rational.

Integral

Integral numbers, supporting integer division. The Haskell Report defines no laws for Integral . However, Integral instances are customarily expected to define a Euclidean domain and have the following properties for the ‘div’/‘mod’ and ‘quot’/‘rem’ pairs, given suitable Euclidean functions f and g: […]

This is based on the same idea as PureScript’s EuclideanRing. Differences:

  • Haskell puts two pairs of functions into the class, div/mod and quot/rem. PureScript only puts one pair of functions in, div/mod. Note that some types which have a EuclideanRing instance admit more than one law-abiding EuclideanRing instance, and in particular Int and BigInt both do; see my post Different kinds of integer division for details. If you care about what kind of division you’re using then Int and BigInt provide monomorphic versions of quot and rem, or alternatively you could define a newtype wrapper.
  • Haskell includes a toInteger in this class, which limits its utility a little, since there are quite a few examples of Euclidean rings which can’t sensibly be converted to integers. One example is polynomials with coefficients in a field (such as the rationals).

Fractional

This is basically the same as our DivisionRing:

The Haskell Report defines no laws for Fractional . However, ‘(+)’ and ‘(*)’ are customarily expected to define a division ring and have the following properties: […]

Unlike PureScript’s DivisionRing, Haskell’s Fractional also provides a fromRational. However, we can reconstruct fromRational fairly easily with what DivisionRing gives us, by converting the numerator and denominator (which will be integers) to our type, and then dividing them with the division given by our type.

Floating

Functions like exp, sin, asin. We don’t really have much need for this class since there is only one floating-point type in PureScript, and that’s Number. If you’re porting a Haskell function which uses a Floating constraint then you probably want to make it monomorphic and use the functions from Math.

RealFrac

Doesn’t exist in PureScript. You’ll probably have to write your own if you need any of these.

RealFloat

Doesn’t exist in PureScript. You’ll probably have to write your own if you need any of these.

6 Likes

Thanks for the detailed write-up! I also got to learn something new as I didn’t know about the three different kinds of integer division.

1 Like