Latest and greatest Haskell<->PureScript serialization

Hi all,

I’m writing a Haskell web service and PureScript frontend. I’m researching techniques that can serialize to/from Haskell and PureScript data types. So far I’m struggling to find anything that isn’t worrying.

Here’s my survey so far. Hopefully it will be of used to others to avoid spending the time I’ve spent trawling for answers.

  • :skull_and_crossbones: purescript-bridge - this package generates
    PureScript code, but it states that “For compatible JSON
    representations you should be using aeson’s generic
    encoding/decoding with default options and encodeJson and
    decodeJson from “Data.Argonaut.Generic.Aeson” in purescript-argonaut-generic-codecs.” However, that package hasn’t been touched since 2017. It has funny parse issues like this, and apparently is unmaintained, and doesn’t use Generics.Rep: “I am no longer a PureScript user and did not know about this. But I will definitely merge PRs, making this library fit for future PureScript releases!” purescript-bridge is maintained by the same author.
  • :skull_and_crossbones: purescript-argonaut-aeson-generic supports
    the new generics: “It is using Data.Generic.Rep hence it will
    work with purescirpt-0.12 , unlike
    purescript-argonaut-generic-codec which at the moment is based on
    Data.Generic for which generic deriving has been removed from the
    purescript compiler in this commit.” But this is also apparently buggy, and has an issue that has been reported on this, and not responded to since 2018.
  • purescript-foreign-generic - is seemingly maintained, as it was last touched in 2019, although it has three open pull requests. There’s some discussion on aeson compatibility, but it’s left unclear and is from last year. So the compatibility with aeson is at best a big question-mark.
  • purescript-interop is a 5 years old untouched attempt at Haskell-PS interop, but seems dead.
  • purescript-iso is an undocumented but possible Haskell-PS bridge
    last touched in 2019.

(I originally put links to all the things I was referring to, but
Discord rejected my comment saying I can only put 2 links in a post
because I’m “new”. So you’ll have to just find the things yourself.)

There’s a pattern here. There are lots of alternatives, and they are all either out of date or unmaintained or buggy, or all three.

Ideally, I would just use aeson’s encoding and PureScript’s aeson-compatible encoding to ferry types between Haskell and PureScript, but my faith is wavering in any libraries providing this in a stable non-buggy way.

My options are:

  • I pick up and maintain one of these in my project.
  • I find another one that someone else out there is using for real work and doesn’t have blatant outstanding encoding errors.
  • I write my own thing.

I’ve been wanting to version my API’s schema in a tightly-controlled way (hashing all types), so I might end up writing template Haskell anyway and generate Data.Argonaut.Core to decode on the PureScript side. :man_shrugging:

6 Likes

We use purescript-foreign-generic extensively in production at Lumi, together with a Haskell backend using aeson, and it works fine.

4 Likes

I’m going to help by providing you with some context and connection to theory, which will give more ways to approach this problem. If you people starting the thread think this is too generic or far away from practical solution, a distraction, let me know and I’ll move it up to my blog off your way without becoming offended. Theory-wise, we are talking about isomorphisms when we talk about this kind of serialization bridges.

To illustrate what an isomorphism is, I give you few examples of isomorphisms, later below we’ll prove the first one to show how it works:

(Tuple a b) ≅ (Tuple b a)
(Either Unit a) ≅ a
(Pair a b → c) ≅ a → b → c

Isomorphism between two types, X ≅ Y means that there are two functions.

f :: X → Y
g :: Y → X

These functions are satisfying the following equations, these equations mean that going between the two representations keeps the value equal:

(f >>> g) = (\x -> x)
(g >>> f) = (\y -> y)

Isomorphic mappings like these are very important for several reasons.

  1. They allow to automate bridging of languages. This is important because it is a very hard roadbump to throw away or reconnect everything every time when we find out more to improve in programming language development. Theory can connect this together. I’d wish the work on Haskell, Purescript, Even C and Javascript would all remain accessible as we keep getting better functional programming languages.
  2. The allow to bridge things inside the language together. Those different representations for different things neatly connect together and become compatible.

Ok, now to explaining why they’re isomorphisms that I gave as an example. This is very similar to proving they’re isomorphisms. For each one we find two functions and see that they satisfy the equations.

(Tuple a b) ≅ (Tuple b a)
f = swap
g = swap
Refl :: (f >>> g) = id
Refl :: (g >>> f) = id

Swap flips the parameters around. Flipping the parameters around twice just does nothing. The equations are proved by reflection, as the both sides reduce to same expression.

There’s a thing I’m not sure about and it should be mentioned here. The order of evaluation does affect isomorphic mappings. To illustrate think about mapping an infinite list between the bridges. This connects to well-formedness of recursion. It’s the data/codata -thing, or catamorphisms/anamorphisms -thing.

I believe some of you already know these subject, and possibly much better than me. I will gladly listen you. I’ll try to make these more accessible for everybody as we keep going. Also, if you got more questions, I’ll answer those too. I’ll be available to discuss this.

1 Like

OK, if you’ve been using it in production for a while then I’ll give it a try. Thanks.

1 Like

It seems like you’re looking mostly at stringy typeclass-based serialization, but if you want to do binary non-typeclass serialization between Haskell and Purescript, then this combination of libraries works pretty well.

https://pursuit.purescript.org/packages/purescript-parsing-dataview

https://pursuit.purescript.org/packages/purescript-arraybuffer-builder

3 Likes

It actually seems like purescript-bridge is a lot more modern than it seems; the hackage version is just really old. In v0.14 (just a few months old)

  • support for Generics.Rep
  • support for foreign-generic

I’ll probably start updating the rest of purescript-bridge for modern purescript. Looks like it could become a really nice tool.

EDIT: To use generics-rep and foreign-generic, use these settings:

import Language.PureScript.Bridge.CodeGenSwitches (useGenRep, genForeign)

 writePSTypesWith (useGenRep <> genForeign (ForeignOptions False))
1 Like

As of 2020-09-18, there is now a Protocol Buffers implementation for Purescript https://github.com/xc-jp/purescript-protobuf

I wrote it specifically for serializing data between Purescript and Haskell.

4 Likes