LambdaBuffers to Haskell

Let's take a look at how LambdaBuffers modules map into Haskell modules and how LambdaBuffers type definitions map into Haskell type definitions.

We'll use the lbf-prelude-to-haskell CLI tool which is just a convenient wrapper over the raw lbf CLI. We can get this tool by either loading the LambdaBuffers Nix environment that comes packaged with all the CLI tools:

$ nix develop github:mlabs-haskell/lambda-buffers#lb
$ lbf<tab>
lbf                        lbf-plutus-to-purescript   lbf-prelude-to-purescript
lbf-plutus-to-haskell      lbf-prelude-to-haskell

Or we can simply just refer directly to the lbf-prelude-to-haskell CLI by nix run github:mlabs-haskell/lambda-buffers#lbf-prelude-to-haskell.

In this chapter, we're going to use the latter option.

Let's now use lbf-prelude-to-haskell to process the Document.lbf schema.

module Document

-- Importing types
import Prelude (Text, List, Set, Bytes)

-- Author
sum Author = Ivan | Jovan | Savo

-- Reviewer
sum Reviewer = Bob | Alice

-- Document
record Document a = {
  author : Author,
  reviewers : Set Reviewer,
  content : Chapter a
 }

-- Chapter
record Chapter a = {
  content : a,
  subChapters : List (Chapter a)
 }

-- Some actual content
sum RichContent = Image Bytes | Gif Bytes | Text Text

-- Rich document
prod RichDocument = (Document RichContent)
$ nix run github:mlabs-haskell/lambda-buffers#lbf-prelude-to-haskell -- Document.lbf
$ find autogen/
autogen/
autogen/LambdaBuffers
autogen/LambdaBuffers/Document.hs
autogen/build.json

As we can see the autogen directory has been created that contains the generated Haskell modules. Note the autogen/build.json file as it contains all the necessary Hackage dependencies the generated module needs in order to be properly compiled by GHC.

The outputted Haskell module in autogen/LambdaBuffers/Document.hs:

module LambdaBuffers.Document (Author(..)
                              , Chapter(..)
                              , Document(..)
                              , Reviewer(..)
                              , RichContent(..)
                              , RichDocument(..)) where

import qualified LambdaBuffers.Prelude
import qualified Prelude


data Author = Author'Ivan  | Author'Jovan  | Author'Savo  deriving Prelude.Show

data Chapter a = Chapter { chapter'content :: a
                         , chapter'subChapters :: LambdaBuffers.Prelude.List (Chapter a)} deriving Prelude.Show

data Document a = Document { document'author :: Author
                           , document'reviewers :: LambdaBuffers.Prelude.Set Reviewer
                           , document'content :: Chapter a} deriving Prelude.Show

data Reviewer = Reviewer'Bob  | Reviewer'Alice  deriving Prelude.Show

data RichContent = RichContent'Image LambdaBuffers.Prelude.Bytes
                    | RichContent'Gif LambdaBuffers.Prelude.Bytes
                    | RichContent'Text LambdaBuffers.Prelude.Text deriving Prelude.Show

newtype RichDocument = RichDocument (Document RichContent) deriving Prelude.Show

We can compile the code with the following commands. Note the dev shell dev-prelude-haskell as it includes the LambdaBuffers.Prelude dependency.

$ nix develop github:mlabs-haskell/lambda-buffers#dev-prelude-haskell
$ ghc autogen/LambdaBuffers/Document.hs
[1 of 1] Compiling LambdaBuffers.Document ( autogen/LambdaBuffers/Document.hs, autogen/LambdaBuffers/Document.o )

Sum types

The types Author, Reviewer, and RichContent have been declared as sum types in the LambdaBuffers schema using the sum keyword.

As we can see, nothing too surprising here, all the sum types become data in Haskell.

The only thing to notice is that the type name was prepended with ' (single quote) to the defined constructor names as to make sure they are unique.

Product types

The type RichDocument have been declared as a product type in the LambdaBuffers schema using the prod keyword.

They become Haskell newtype if they have a single type in their body, otherwise they are data.

Note that the constructor has the same name as the type.

Record types

The types Document and Chapter have been declared as record types in the LambdaBuffers schema using the record keyword.

Like with product types, they become Haskell newtype if they have a single type in their body, otherwise they are data.

Also like with product types, the constructor has the same name as the type.

The field names, similar to sum constructor names, are prepended with the lowercased name of the type with a single quote (') to maintain uniqueness.