I started work on my ATS math library recently; though I'm not sure it will be a wholehearted success, there were parts that deserve attention.

Let us first consider the following Haskell function to check primality:

hsIsPrime :: Int -> Bool
hsIsPrime 1 = False
hsIsPrime x = all ((/=0) . (x `mod`)) [2..m]
    where m = floor (sqrt (fromIntegral x :: Float))

This is actually quite nice, but it is also far from "as fast as C". We can rewrite this in ATS as follows:

\#include "share/atspre\_staload.hats"

staload "libats/libc/SATS/math.sats"
staload UN = "prelude/SATS/unsafe.sats"

fun sqrt\_int(k : intGt(0)) : [ m : nat ] int(m) =
  let
    var pre\_bound: int = g0float2int(sqrt\_float(g0int2float\_int\_float(k)))
    var bound: [ m : nat ] int(m) = $UN.cast(pre\_bound)
  in
    bound
  end

fn is\_prime(k : intGt(0)) : bool =
  case+ k of
    | 1 => false
    | k => 
      begin
        let
          fun loop {n : nat}{m : nat} .&lt;max(0,m-n)&gt;. (i : int(n), bound : int(m)) :<> bool =
            if i < bound then
              if k % i = 0 then
                false
              else
                loop(i + 1, bound)
            else
              if i = bound then
                if k % i = 0 then
                  false
                else
                  true
              else
                true
        in
          loop(2, sqrt\_int(k))
        end
      end

As can be seen, this is a bit more involved. Some of this is to be expected as we prove termination, but some of it is simply due to using a lower-level language.

Note first the rather involved termination metric here - we use .<max(0,m-n)>. to ensure that it is well-founded. I learned this trick reading the ATS manual, and I actually encourage prospective learners to do this as well; it can be hard to get your hands on code samples.

Next, note the use of :<> in the type signature of loop. This is how ATS manages effects. You can have :<!ntm> for a function that may not terminate, :<!exn> for a function that could throw an exception, etc. So :<> simply means that the function is pure and total (that is, terminating).

Finally, note the use of an existential type in the declaration var bound: .... Existential types are a feature special to ATS; they are not present in any mainstream languages or even Idris. The type [ m : nat ] int(m) can then be passed as an argument to loop, though loop takes the universally quantified {m : nat} ... bound: int(m). Hence, we have a powerful technique that allows us to take a function on an ordinary int and nonetheless prove termination.

At this point, we can write a small Haskell wrapper. This is slightly dry, but I think it will be informative.

\#define ATS\_MAINATSFLAG 1

extern
fun is\_prime\_ats { n : nat | n > 0 } : int(n) -> bool =
  "mac#"

implement is\_prime\_ats (n) =
  is\_prime(n)

This is the necessary code on the ATS side of things; it simply exposes the function in the compiled library. We can compile it to C (placing it in cbits per Haskell packaging convention) with:

 $ mkdir -p cbits
 $ patsopt -dd prime.dats -cc > cbits/prime.c

On the Haskell side we have the following:

module Numeric.NumberTheory where

import           Data.Word
import           Foreign.C

foreign import ccall unsafe is\_prime\_ats :: CInt -> CBool

convertBool :: CBool -> Bool
convertBool = go . fromIntegral
    where
        go :: Word8 -> Bool
        go 0 = False
        go 1 = True
        go \_ = False

isPrime :: Int -> Bool
isPrime = convertBool . is\_prime\_ats . fromIntegral

Our .cabal file will be a bit simple-minded. We simply add the following to our library stanza:

c-sources:           cbits/primes.c
include-dirs:        /usr/local/lib/ats2-postiats-0.3.8/ccomp/runtime/
                   , /usr/local/lib/ats2-postiats-0.3.8/
ghc-options:         -optc-mtune=native -optc-flto -optc-O3

Finally, we can benchmark both functions alongside one another:

Task Language Time
isPrime 2017 ATS 118.9 ns
isPrime 2017 Haskell 497.3 ns

This is exactly what we wanted - a 5x speedup compared to pure Haskell, with even more static guarantees to boot! With C, you can get the same speed (or perhaps even faster), but it always feels like an uncanny compromise. ATS allows us to write the functional code we want (with attendant safety guarantees) and still get the speed we need.