As astute followers of this blog will know, I have been working on several ATS packages for Haskell; here I would like to present the fruits of these labors: the ability to use Haskell's Dhall library in ATS.

# Motivation: Dhall in ATS

There are two reasons one would want to write Dhall bindings for ATS. The first is that ATS is a relatively obscure language and thus has relatively few libraries available. Bindings C libraries is a common way to write ATS, but as ATS is an effective systems programming language on its own, we can really bind to anything we want.

The second is that ATS has a relatively advanced type system. In practice, any type expressible in Haskell will be expressible in ATS (though not conversely). This is not the case with Rust, which does not have universally quantified types. It is well worth exploring what systems programming looks like with modern techniques, in particular passing algebraic data types between programs.

# Example

Our example will not provide the full generality library bindings would. However, as ATS does not have generics, it will end up suiting our needs quite well (with the minor downside of a polyglot project).

We will proceed as follows:

• Write Haskell for the data type we wish to read in

• Allow Dhall to generate an Interpret instance.

• Allow ats-storable to generate a Storable instance.

• Allow hs2ats to generate ATS types based on the Haskell we wrote.

• Write the boilerplate for memory management and testing in ATS.

This is no doubt less pleasant than using a library in a language such as Rust, but those familiar with ATS will note that this is pretty standard; offloading the hard work to generics requires us to use an entirely different language. Moreover, neither Rust nor Scala bindings for Dhall are complete, so this is in fact unique to the ATS ecosystem.

Let's first examine the Dhall expression to be parsed:

let Option = < Some : { _1 : { first : Integer, second : Integer } } | None : {} >
in
let None = < Some : { _1 : { first : Integer, second : Integer } } | None = {=} >
in
let Some = λ(x : { first : Integer, second : Integer }) → < Some = { _1 = x } | None : {} >
in
let p = { first = 1, second = 6 }
inSome p

This is a bit abstruse, but it becomes a little clearer when we look at the Haskell:

data Option a = Some a
| None
deriving (Generic, Interpret, Functor, ATSStorable, Data)data Pair a b = Pair { first :: a, second :: b }
deriving (Generic, Interpret, ATSStorable, Data)

The Haskell is pleasantly compact, though mostly trivial:

{-# LANGUAGE DeriveAnyClass           #-}
{-# LANGUAGE DeriveDataTypeable       #-}
{-# LANGUAGE DeriveFunctor            #-}
{-# LANGUAGE DeriveGeneric            #-}
{-# LANGUAGE FlexibleInstances        #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE OverloadedStrings        #-}module Foreign whereimport           Data.Bifunctor
import           Data.Data
import qualified Data.Text.Lazy       as TL
import           Dhall
import           Foreign.C.String
import           Foreign.C.Types
import           Foreign.Ptr
import           Foreign.Storable.ATSdata Option a = Some a
| None
deriving (Generic, Interpret, Functor, ATSStorable, Data)data Pair a b = Pair { first :: a, second :: b }
deriving (Generic, Interpret, ATSStorable, Data)instance Bifunctor Pair where
bimap f g (Pair x y) = Pair (f x) (g y)type Product = Pair CInt CIntreadDhall :: FilePath -> IO (Option Product)
readDhall p = fmap (bimap g g) <$> input auto (TL.pack p) where g :: Integer -> CInt g = fromIntegralread_dhall :: CString -> IO (Ptr (Option Product)) read_dhall cStr = do str <- peekCString cStr x <- readDhall str writePtr xforeign export ccall read_dhall :: CString -> IO (Ptr (Option Product)) On the ATS side of things, we get the following generated code (from hs2ats): datavtype option(a: vt@ype) = | Some of a | Nonevtypedef pair(a: vt@ype, b: vt@ype) = @{ first = a, second = b } vtypedef product = pair(int, int) This will be our handwritten code: %{^ #define STUB_H "hs/Foreign_stub.h" #define STG_INIT __stginit_Foreign %}#include "$PATSHOMELOCS/hs-bind-0.4.1/runtime.dats"
staload "gen/types.sats"fun free_option(x : option(pair(int,int))) : void =
case+ x of
| ~Some (x) => ()
| ~None() => ()fun tostring_option_pair(x : !option(pair(int,int))) : string =
case+ x of
| None() => "None"
| Some (x) => let
var f = x.first
var s = x.second
in
string_append5( "Some (Pair { first = "
, tostring_int(f)
, ", second = "
, tostring_int(s)
, " })"
)
endextern
{
val _ = hs_init(argc, argv)
val s = tostring_option_pair(x)
val _ = println!(s)
val _ = free_option(x)
val _ = hs_exit()
}

And finally our configuration (first in Dhall for atspkg, then a .cabal file):

let pkg = https://raw.githubusercontent.com/vmchale/atspkg/master/ats-pkg/dhall/default.dhall
in
let dbin = https://raw.githubusercontent.com/vmchale/atspkg/master/ats-pkg/dhall/default-bin.dhallin pkg //
{ test =
[
dbin //
{ src = "src/dhall-ats.dats"
, target = "target/dhall-ats"
, hsDeps = [ { cabalFile = "hs/foreign.cabal", objectFile = "hs/Foreign.o", projectFile = ([] : Optional Text ) } ]
, hs2ats = [ { hs = "hs/Foreign.hs", ats = "gen/types.sats", cpphs = False } ]
}
]
, dependencies = [ "hs-bind" ]
, ccompiler = "ghc"
, cflags = ["hs/Foreign"]
}
name: foreign
version: 0.1.0.0
cabal-version: >= 1.2
build-type: Simplelibrary
build-depends: base < 5
, dhall
, filepath
, ats-storable
, text
exposed-modules: Foreign
hs-source-dirs: .
ghc-options: -Wall -Werror -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat

This of course assumes you name your Haskell source file Foreign.hs and your ATS source src/dhall-ats.dats. You can see here for the source code if you have any trouble getting this up and running.

You should be able to now run

atspkg test

and see the result! If all went well, you should see

Some (Pair { first = 1, second = 6 })

indicating that our FFI is working correctly.

According to polyglot, our project contents are as follows:

-------------------------------------------------------------------------------
Language             Files       Lines         Code     Comments       Blanks
-------------------------------------------------------------------------------
ATS                      1          45           40            0            5
Cabal                    1          14           13            0            1
Dhall                    1          18           17            0            1
Haskell                  1          44           35            0            9
-------------------------------------------------------------------------------
Total                    4         121          105            0           16
-------------------------------------------------------------------------------

which is certainly less pleasant than the few lines of Haskell, but nonetheless unburdensome compared to other solutions in ATS.