hal

A runtime environment for Haskell applications running on AWS Lambda.

https://github.com/Nike-inc/hal#readme

Version on this page:1.0.1@rev:1
LTS Haskell 23.0:1.1@rev:1
Stackage Nightly 2024-12-12:1.1@rev:1
Latest on Hackage:1.1@rev:1

See all snapshots hal appears in

BSD-3-Clause licensed by Nike, Inc.
Maintained by nikeoss
This version can be pinned in stack with:hal-1.0.1@sha256:25b6a0e28cb89a522173255fce1ab0791eb01f41ae6357b3a4c3ee4868c33694,5142

Stack Haskell Builds Cabal Haskell Builds

hal

A runtime environment for Haskell applications running on AWS Lambda.

Flexible

This library uniquely supports different types of AWS Lambda Handlers for your needs/comfort with advanced Haskell. Instead of exposing a single function that constructs a Lambda, this library exposes many.

For lambdas that are pure and safe, then pureRuntime is ideal. It accepts a handler with the signature (FromJSON a, ToJSON b) => a -> b. This runtime guarantees that side-effects cannot occur.

For advanced use cases mRuntime unlocks the full power of Monad Transformers. It accepts handlers with the signature (MonadCatch m, MonadIO m, FromJSON event, ToJSON result) => (event -> m result) This enables users to add caching logic or expose complex environments.

With numerous options in between these two, developers can choose the right balance of flexibility vs simplicity.

Performant

Measuring lambda performance is tricky, so investigation and optimization is ongoing. Current indications show a warm execution overhead of only ~20% more than the official Rust Runtime (a much lower level language).

Robust

While testing continues, we have executed over 30k test events without error caused by the runtime. Naive approaches lead to error rates well over 10%.

Table of Contents

Supported Platforms / GHC Versions

We currently support this library under the same environment that AWS Lambda supports.

Our CI currently targets the latest three LTS Stackage Versions, the latest three minor versions of GHC under Cabal (e.g. 8.6.x, 8.4.x, and 8.2.x), and GHC-head / Stackage nightly builds.

If you haven’t already, adding docker: { enable: true } to your stack.yaml file will ensure that you’re building a binary that can run in AWS Lambda.

Quick Start

This quick start assumes you have the following tools installed:

Add hal to your stack.yaml’s extra-deps and enable Docker integration so that your binary is automatically compiled in a compatible environment for AWS. Also add hal to your project’s dependency list (either project-name.cabal or package.yaml)

#...
extra-deps:
  - hal-${DESIRED_VERSION}
# ...
docker:
  enable: true
# ...

Then, define your types and handler:

{-# LANGUAGE DeriveGeneric  #-}
{-# LANGUAGE NamedFieldPuns #-}

module Main where

import AWS.Lambda.Runtime (pureRuntime)
import Data.Aeson         (FromJSON, ToJSON)
import GHC.Generics       (Generic)

data IdEvent  = IdEvent { input   :: String } deriving Generic
instance FromJSON IdEvent

data IdResult = IdResult { output :: String } deriving Generic
instance ToJSON IdResult

handler :: IdEvent -> IdResult
handler IdEvent { input } = IdResult { output = input }

main :: IO ()
main = pureRuntime handler

Your binary should be called bootstrap in order for the custom runtime to execute properly:

# Example snippet of package.yaml
# ...
executables:
  bootstrap:
    source-dirs: src
    main: Main.hs  # e.g. {project root}/src/Main.hs
# ...

You’ll need to either build on a compatible linux host or inside a compatible docker container (or some other mechanism like nix). Note that current Stack LTS images are not compatible. If you see an error message that contains “version ‘GLIBC_X.XX’ not found” when running (hosted or locally), then your build environment is not compatible.

Enable stack’s docker integration and define an optional image within stack.yaml:

# file: stack.yaml
docker:
  enabled: true
  # If omitted, this defaults to fpco/stack-build:lts-${YOUR_LTS_VERSION}
  image: ${BUILD_IMAGE}

Don’t forget to define your CloudFormation stack:

# file: template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: Test for the Haskell Runtime.
Resources:
  HelloWorldApp:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: NOT_USED
      Runtime: provided
      # CodeUri is a relative path from the directory that this CloudFormation
      # file is defined.
      CodeUri: .stack-work/docker/_home/.local/bin/
      Description: My Haskell runtime.
      MemorySize: 128
      Timeout: 3

Finally, build, upload and test your lambda!

# Build the binary, make sure your executable is named `bootstrap`
stack build --copy-bins

# Create your function package
aws cloudformation package \
  --template-file template.yaml \
  --s3-bucket your-existing-bucket > \
  deployment_stack.yaml

# Deploy your function
aws cloudformation deploy \
  --stack-name "hello-world-haskell" \
  --region us-west-2 \
  --capabilities CAPABILITY_IAM \
  --template-file deployment_stack.yaml

# Take it for a spin!
aws lambda invoke \
  --function-name your-function-name \
  --region us-west-2 \
  --payload '{"input": "foo"}' \
  output.txt

Local Testing

Dependencies

Build

docker pull fpco/stack-build:lts-{version} # First build only, find the latest version in stack.yaml
stack build --copy-bins

Execute w/ Docker

echo '{ "accountId": "byebye" }' | docker run -i --rm \
    -e DOCKER_LAMBDA_USE_STDIN=1 \
    -v ${PWD}/.stack-work/docker/_home/.local/bin/:/var/task \
    lambci/lambda:provided

Execute w/ SAM Local

Note that hal currently only supports aws-sam-cli on versions <1.0.

echo '{ "accountId": "byebye" }' | sam local invoke --region us-east-1

Changes

Change log

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Haskell’s Package Versioning Policy

1.0.1 - 2023-12-15

  • Add support for aeson 2.2. Users can opt in by setting the use-aeson-2-2 flag to true, which for many users will be set automatically. In a future breaking change release, this flag will default to true.

1.0.0.1 - 2022-09-10

  • Support GHC 9.4 by eliminating compiler errors

1.0.0 - 2022-05-21

  • Remove deprecated functions and classes:

    • AWS.Lambda.Combinators.withFallibleInterface
    • AWS.Lambda.Combinators.withIOInterface
    • AWS.Lambda.Combinators.withPureInterface
    • AWS.Lambda.Context.HasLambdaContext (class)
    • AWS.Lambda.Context.runReaderTLambdaContext
    • AWS.Lambda.Runtime.Value.mRuntimeWithContext
  • Rename mRuntimeWithContext' to mRuntimeWithContext in the following modules:

    • AWS.Lambda.Runtime
    • AWS.Lambda.Runtime.Value
  • Remove dependency on envy

0.4.10.1 - 2022-03-28

  • instance FromJSON AWS.Lambda.Events.EventBridge.Detail.SSM.ParameterStoreChange.Operation now accepts arbitrary Text instead of the four currently-known operations.

0.4.10 - 2022-03-22

  • Add AWS.Lambda.Events.EventBridge.EventBridgeEvent type for subscribing Lambda functions to AWS EventBridge Events
  • Add AWS.Lambda.Events.EventBridge.Detail.SSM.ParameterStoreChange type for parsing AWS Systems Manager Parameter Store Change events delivered via AWS EventBridge.
  • When the runtime encounters an error, write it out to the Cloudwatch logs (via stderr), and do so in a format that can be guaranteed and later extended.
  • Eliminate redundant import compiler warnings

0.4.9 - 2022-02-28

  • Add KafkaEvent type for subscribing Lambda functions to Kafka topics
  • Support aeson ^>=2.0.0.0
  • Drop official support for GHC 8.0.2
  • Expand hedgehog bounds to include 1.1 to keep ahead of Stack nightly

0.4.8 - 2021-05-07

  • Add ToJSON instances for ProxyRequest types, and test round-tripping as much as possible
  • Add FromJSON instance for ProxyResponse, and test round-tripping as much as possible

0.4.7 - 2021-04-12

  • Drop http-conduit as a dependency for potentially smaller binaries
  • Add more detailed error messages to context parsing failures
  • Increase compatibility by loosening envy version bounds
  • Add mRuntimeWithContext’ and mRuntime
  • Deprecate withIoInterface, withFallibleInterface, withPureInterface, HasLambdaContext, runRaderTLambdaContext, mRuntimeWithContext

0.4.2 - 2020-07-23

  • Fix an issue where errors from the runtime were not retried

0.4.1 - 2020-01-22

  • Constrain the envy version for correctly building with Cabal
  • Fix documentation examples for Runtime and Combinators

0.4.0 - 2020-01-22

  • Add support for Value-based runtimes for handling JSON conversion errors explicitly

0.3.1 - 2020-07-23

  • Fix an issue where errors from the runtime were not retried

0.3.0 - 2019-02-26

  • Add support for triggered S3 events

0.2.1 - 2020-07-23

  • Fix an issue where errors from the runtime were not retried

0.2.0 - 2019-02-07

  • Expose AWS.Lambda.Combinators package for building other runtimes (e.g. to support Lambda Event triggers)

0.1.3 - 2020-07-23

  • Fix an issue where errors from the runtime were not retried

0.1.2 - 2019-01-10

  • Fix PVP bounds when building/uploading the package

0.1.1 - 2019-01-08

Fixed

  • Incorrect error message when getting events from AWS

0.1.0 - 2018-12-20

Added

  • Initial release!