Skip to content

Instantly share code, notes, and snippets.

@noughtmare
Last active February 4, 2026 22:19
Show Gist options
  • Select an option

  • Save noughtmare/f900a9c84230cb66413c126c8792fa1a to your computer and use it in GitHub Desktop.

Select an option

Save noughtmare/f900a9c84230cb66413c126c8792fa1a to your computer and use it in GitHub Desktop.
A safe interface to HOAS by selectively hiding constructors.
{-# LANGUAGE PatternSynonyms, ViewPatterns, DataKinds #-}
module SafeHoas (BuildExp, app, lam, MatchExp (Var, App, Lam), shift) where
import Data.Coerce
type Name = String
data Exp = Var' Name | App' Exp Exp | Lam' (Exp -> Exp)
newtype BuildExp (s :: ()) = BuildExp Exp
app :: BuildExp s -> BuildExp s -> BuildExp s
app = coerce App'
lam :: (BuildExp s -> BuildExp s) -> BuildExp s
lam = coerce Lam'
newtype MatchExp = MatchExp Exp
pattern Var :: Name -> MatchExp
pattern Var v <- (coerce -> Var' v)
pattern App :: MatchExp -> MatchExp -> MatchExp
pattern App x y <- (coerce -> App' (coerce -> x) (coerce -> y))
pattern Lam :: (Name -> MatchExp) -> MatchExp
pattern Lam x <- (coerce -> Lam' ((\f -> coerce . f . Var') -> x))
-- Thanks to Zhixuan Yang for the idea of using the 's' parameter for this!
shift :: (forall s. BuildExp s) -> MatchExp
shift x = coerce x
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment