Skip to content

Commit

Permalink
Generate Haskell code for TickNonce
Browse files Browse the repository at this point in the history
  • Loading branch information
javierdiaz72 committed Nov 21, 2024
1 parent 7aeafae commit 4a4a962
Show file tree
Hide file tree
Showing 19 changed files with 1,096 additions and 5 deletions.
82 changes: 82 additions & 0 deletions docs/agda-spec/src/Foreign/Convertible.agda
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
module Foreign.Convertible where

open import Ledger.Prelude
open import Foreign.HaskellTypes

record Convertible (A B : Type) : Type where
field to : A B
from : B A
open Convertible ⦃...⦄ public

HsConvertible : (A : Set) ⦃ HasHsType A ⦄ Set
HsConvertible A = Convertible A (HsType A)

Convertible-Refl : {A} Convertible A A
Convertible-Refl = λ where .to id; .from id

Convertible₁ : (Type Type) (Type Type) Type₁
Convertible₁ T U = {A B} ⦃ Convertible A B ⦄ Convertible (T A) (U B)

Convertible₂ : (Type Type Type) (Type Type Type) Type₁
Convertible₂ T U = {A B} ⦃ Convertible A B ⦄ Convertible₁ (T A) (U B)

Functor⇒Convertible : {F : Type↑} ⦃ Functor F ⦄ Convertible₁ F F
Functor⇒Convertible = λ where
.to map to
.from map from

Bifunctor⇒Convertible : {F} ⦃ Bifunctor F ⦄ Convertible₂ F F
Bifunctor⇒Convertible = λ where
.to bimap to to
.from bimap from from

_⨾_ : {A B C} Convertible A B Convertible B C Convertible A C
(c ⨾ c') .to = c' .to ∘ c .to
(c ⨾ c') .from = c .from ∘ c' .from

-- ** instances

open import Foreign.Haskell
open import Foreign.Haskell.Coerce using (coerce)

instance
Convertible-Maybe : Convertible₁ Maybe Maybe
Convertible-Maybe = Functor⇒Convertible

Convertible-× : Convertible₂ _×_ _×_
Convertible-× = Bifunctor⇒Convertible

Convertible-Pair : Convertible₂ _×_ Pair
Convertible-Pair = λ where
.to coerce ∘ bimap to to
.from bimap from from ∘ coerce

Convertible-⊎ : Convertible₂ _⊎_ _⊎_
Convertible-⊎ = Bifunctor⇒Convertible

Convertible-Either : Convertible₂ _⊎_ Either
Convertible-Either = λ where
.to coerce ∘ bimap to to
.from bimap from from ∘ coerce

Convertible-FinSet : Convertible₁ ℙ_ List
Convertible-FinSet = λ where
.to map to ∘ setToList
.from fromList ∘ map from

Convertible-Map : {K K' V V'} ⦃ DecEq K ⦄
⦃ Convertible K K' ⦄ ⦃ Convertible V V' ⦄
Convertible (K ⇀ V) (List $ Pair K' V')
Convertible-Map = λ where
.to to ∘ proj₁
.from fromListᵐ ∘ map from

Convertible-List : Convertible₁ List List
Convertible-List = λ where
.to map to
.from map from

Convertible-Fun : {A A' B B'} ⦃ Convertible A A' ⦄ ⦃ Convertible B B' ⦄ Convertible (A B) (A' B')
Convertible-Fun = λ where
.to λ f to ∘ f ∘ from
.from λ f from ∘ f ∘ to
172 changes: 172 additions & 0 deletions docs/agda-spec/src/Foreign/Convertible/Deriving.agda
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
module Foreign.Convertible.Deriving where

open import Level
open import MetaPrelude
open import Meta

import Data.List as L
import Data.List.NonEmpty as NE
import Data.String as S
import Data.Product as P

open import Relation.Nullary
open import Relation.Nullary.Decidable

open import Reflection.Tactic
open import Reflection.AST.Term
open import Reflection.AST.DeBruijn
import Reflection.TCM as R
open import Reflection.Utils
open import Reflection.Utils.TCI
import Function.Identity.Effectful as Identity

open import Class.DecEq
open import Class.DecEq
open import Class.Functor
open import Class.Monad
open import Class.MonadTC.Instances
open import Class.Traversable
open import Class.Show
open import Class.MonadReader

open import Tactic.Substitute
open import Foreign.Convertible
open import Foreign.HaskellTypes
open import Foreign.HaskellTypes.Deriving

private instance
_ = Functor-M {TC}

-- TODO: move to agda-stdlib-meta
liftTC : {a} {A : Set a} R.TC A TC A
liftTC m _ = m

private

open MonadReader ⦃...⦄

variable
A B C : Set

TyViewTel = List (Abs (Arg Type))

substTel : Subst TyViewTel
substTel x s [] = []
substTel x s (abs z t ∷ tel) = abs z (substArg x s t) ∷ (substTel (ℕ.suc x) s tel)
-- Note: `abs` is abused in TyViewTel and doesn't actually scope over the `t`

-- Substitute leading level parameters with lzero
smashLevels : TyViewTel ℕ × TyViewTel
smashLevels (abs s (arg i (def (quote Level) [])) ∷ tel) =
P.map ℕ.suc (substTel 0 (quote 0ℓ ∙)) $ smashLevels tel
smashLevels tel = (0 , tel)

tyViewToTel : TyViewTel Telescope
tyViewToTel = L.map λ where (abs s a) s , a

hideTyView : Abs (Arg A) Abs (Arg A)
hideTyView (abs s (arg (arg-info _ m) x)) = abs s (arg (arg-info hidden m) x)

-- The type of a Convertible instance. For parameterised types adds the appropriate instance
-- arguments and instantiates level arguments to lzero. For instance,
-- instanceType _⊎_ Hs.Either = {A B : Set} {a b : Set} → ⦃ Convertible A a ⦄ → ⦃ Convertible B b ⦄
-- Convertible (A ⊎ B) (Hs.Either a b)
instanceType : (agdaName hsName : Name) TC TypeView
instanceType agdaName hsName = do
aLvls , agdaParams smashLevels <$> getParamsAndIndices agdaName
hLvls , hsParams smashLevels <$> getParamsAndIndices hsName
true return (length agdaParams == length hsParams)
where false liftTC $ R.typeErrorFmt "%q and %q have different number of parameters" agdaName hsName
let n = length agdaParams
l₀ = quote 0ℓ ∙
agdaTy applyWithVisibility agdaName $ L.replicate aLvls l₀ ++ L.map ♯ (take n (downFrom (n + n)))
hsTy applyWithVisibility hsName $ L.replicate hLvls l₀ ++ L.map ♯ (downFrom n)
let instHead = weaken n $ quote Convertible ∙⟦ agdaTy ∣ hsTy ⟧
tel = L.map hideTyView (agdaParams ++ hsParams) ++
L.replicate n (abs "_" (iArg (quote Convertible ∙⟦ ♯ (n + n ∸ 1) ∣ ♯ (n ∸ 1) ⟧)))
return $ tel , instHead

-- Compute one clause of the Convertible instance. For instance,
-- conversionClause Convertible.to to ((c₁ , _) , (c₂ , _)) generates
-- .to (c₁ x₁ .. xn) = c₂ (to x₁) .. (to xn)
-- where the xi are the visible constructor arguments.
conversionClause : Name Name (Name × Type) × (Name × Type) TC Clause
conversionClause prjP prjE ((fromC , fromTy) , (toC , toTy)) = do
let isVis = λ { (abs _ (arg (arg-info visible _) _)) true; _ false }
fromTel = L.filterᵇ isVis (proj₁ (viewTy fromTy))
toTel = L.filterᵇ isVis (proj₁ (viewTy toTy))
n = length fromTel
mkCon c mkArg = Term.con c $ L.map (vArg ∘ mkArg ∘ ♯) (downFrom n)
mkConP c mkArg = Pattern.con c $ L.map (vArg ∘ mkArg ∘ `_) (downFrom n)
true return (n == length toTel)
where false liftTC $ R.typeErrorFmt "%q and %q have different number of arguments" fromC toC
return $ clause (tyViewToTel $ L.map (λ where (abs x (arg i _)) abs x (arg i unknown)) fromTel)
(vArg (proj prjP) ∷ vArg (mkConP fromC id) ∷ [])
(mkCon toC (prjE ∙⟦_⟧))

-- Compute the clauses of a convertible instance.
instanceClauses : (agdaName hsName : Name) TC (List Clause)
instanceClauses agdaName hsName = do
agdaCons getConstrs agdaName
hsCons getConstrs hsName
agdaPars length <$> getParamsAndIndices agdaName
hsPars length <$> getParamsAndIndices hsName
true return (length agdaCons == length hsCons)
where false liftTC $ R.typeErrorFmt "%q and %q have different number of constructors" agdaName hsName
toClauses mapM (conversionClause (quote Convertible.to) (quote to) ) (L.zip agdaCons hsCons)
fromClauses mapM (conversionClause (quote Convertible.from) (quote from)) (L.zip hsCons agdaCons)
return $ toClauses ++ fromClauses

absurdClause : Name Clause
absurdClause prj = absurd-clause (("x" , vArg unknown) ∷ []) (vArg (proj prj) ∷ vArg (absurd 0) ∷ [])

-- Compute conversion clauses for the current goal and wrap them in a pattern lambda.
patternLambda : TC Term
patternLambda = do
quote Convertible ∙⟦ `A ∣ `B ⟧ reduce =<< goalTy
where t liftTC $ R.typeErrorFmt "Expected Convertible A B, got %t" t
agdaCons getConstrsForType `A
hsCons getConstrsForType `B
toClauses mapM (conversionClause (quote Convertible.to) (quote to) ) (L.zip agdaCons hsCons)
fromClauses mapM (conversionClause (quote Convertible.from) (quote from)) (L.zip hsCons agdaCons)
case toClauses ++ fromClauses of λ where
[] return $ pat-lam (absurdClause (quote Convertible.to) ∷ absurdClause (quote Convertible.from) ∷ []) []
cls return $ pat-lam cls []

doPatternLambda : Term R.TC Term
doPatternLambda hole = patternLambda =<< initTCEnvWithGoal hole

-- Deriving a Convertible instance. Usage
-- unquoteDecl iName = deriveConvertible iName (quote AgdaTy) (quote HsTy)
deriveConvertible : Name Name Name R.TC ⊤
deriveConvertible instName agdaName hsName = initUnquoteWithGoal ⦃ defaultTCOptions ⦄ (agda-sort (lit 0)) do
agdaDef getDefinition agdaName
hsDef getDefinition hsName
-- instName ← freshName $ "Convertible" S.++ show hsName
instTel , instTy instanceType agdaName hsName
inst declareDef (iArg instName) (tyView (instTel , instTy))
clauses instanceClauses agdaName hsName
defineFun instName clauses
return _

-- Macros providing an alternative interface. Usage
-- iName : ConvertibleType AgdaTy HsTy
-- iName = autoConvertible
-- or
-- iName = autoConvert AgdaTy
macro
ConvertibleType : Name Name Tactic
ConvertibleType agdaName hsName = initTac ⦃ defaultTCOptions ⦄ $
unifyWithGoal ∘ tyView =<< instanceType agdaName hsName

autoConvertible : Tactic
autoConvertible = initTac ⦃ defaultTCOptions ⦄ $
unifyWithGoal =<< patternLambda

autoConvert : Name Tactic
autoConvert d hole = do
hsTyMeta R.newMeta `Set
R.checkType hole $ quote Convertible ∙⟦ d ∙ ∣ hsTyMeta ⟧
hsTy solveHsType (d ∙)
R.unify hsTyMeta hsTy
R.unify hole =<< doPatternLambda hole
86 changes: 86 additions & 0 deletions docs/agda-spec/src/Foreign/Convertible/DerivingTest.agda
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@

module Foreign.Convertible.DerivingTest where

open import Level
open import MetaPrelude
open import Meta

import Data.List as L
import Data.List.NonEmpty as NE
import Data.String as S
import Data.Product as P

open import Relation.Nullary
open import Relation.Nullary.Decidable

open import Reflection.Tactic
open import Reflection.AST.Term
open import Reflection.AST.DeBruijn
import Reflection.TCM as R
open import Reflection.Utils
open import Reflection.Utils.TCI
import Function.Identity.Effectful as Identity

open import Class.DecEq
open import Class.DecEq
open import Class.Functor
open import Class.Monad
open import Class.MonadTC.Instances
open import Class.Traversable
open import Class.Show
open import Class.MonadReader

open import Foreign.Convertible
open import Foreign.Convertible.Deriving

-- Tests

open import Data.Maybe.Base
open import Data.Sum.Base

data HsMaybe (a : Set) : Set where
just : (x : a) HsMaybe a
nothing : HsMaybe a

data HsEither (a b : Set) : Set where
left : a HsEither a b
right : b HsEither a b

-- We should be able to generate this
ConvertibleMaybe : {a a'} ⦃ Convertible a a' ⦄ Convertible (Maybe a) (HsMaybe a')
ConvertibleMaybe .to (just x) = just (to x)
ConvertibleMaybe .to nothing = nothing
ConvertibleMaybe .from (just x) = just (from x)
ConvertibleMaybe .from nothing = nothing

-- With deriveConvertible
unquoteDecl iConvertMaybe = deriveConvertible iConvertMaybe (quote Maybe) (quote HsMaybe)

-- or with ConvertibleType and autoConvertible
instance
iConvertEither : ConvertibleType _⊎_ HsEither
iConvertEither = autoConvertible

instance
ConvertibleNat = Convertible-Refl {ℕ}

test : ℕ ⊎ Maybe ℕ HsEither ℕ (HsMaybe ℕ)
test = to

_ : test (inj₂ (just 5)) ≡ right (just 5)
_ = refl

-- There was a problem due to Agda#7182 with record types in parameterised modules.

module Param (A : Set) where
record AgdaThing : Set where
field theThing : A

record HsThing : Set where
field theThing :

open Param
unquoteDecl iConvertThing = deriveConvertible iConvertThing (quote AgdaThing) (quote HsThing)

convThing : Convertible AgdaThing HsThing
convThing = autoConvertible
52 changes: 52 additions & 0 deletions docs/agda-spec/src/Foreign/HaskellTypes.agda
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

module Foreign.HaskellTypes where

open import Level using (Level)
open import Data.Bool.Base using (Bool)
open import Data.Nat.Base using (ℕ)
open import Data.String.Base using (String)
open import Data.List.Base using (List)
open import Data.Maybe.Base using (Maybe)
open import Data.Sum.Base using (_⊎_)
open import Data.Product.Base using (_×_)
open import Data.Unit using (⊤)

open import Foreign.Haskell.Pair using (Pair)
open import Foreign.Haskell.Either using (Either)

private variable
l : Level
A B : Set l

record HasHsType (A : Set l) : Set₁ where
field
HsType : Set

HsType : (A : Set l) ⦃ HasHsType A ⦄ Set
HsType _ ⦃ i ⦄ = i .HasHsType.HsType

MkHsType : (A : Set l) (Hs : Set) HasHsType A
MkHsType A Hs .HasHsType.HsType = Hs

instance

iHsTy-ℕ = MkHsType ℕ ℕ
iHsTy-Bool = MkHsType Bool Bool
iHsTy-⊤ = MkHsType ⊤ ⊤
iHsTy-String = MkHsType String String

-- Could make a macro for these kind of congruence instances.
iHsTy-List : ⦃ HasHsType A ⦄ HasHsType (List A)
iHsTy-List {A = A} .HasHsType.HsType = List (HsType A)

iHsTy-Maybe : ⦃ HasHsType A ⦄ HasHsType (Maybe A)
iHsTy-Maybe {A = A} .HasHsType.HsType = Maybe (HsType A)

iHsTy-Fun : ⦃ HasHsType A ⦄ ⦃ HasHsType B ⦄ HasHsType (A B)
iHsTy-Fun {A = A} {B = B} .HasHsType.HsType = HsType A HsType B

iHsTy-Sum : ⦃ HasHsType A ⦄ ⦃ HasHsType B ⦄ HasHsType (A ⊎ B)
iHsTy-Sum {A = A} {B = B} .HasHsType.HsType = Either (HsType A) (HsType B)

iHsTy-Pair : ⦃ HasHsType A ⦄ ⦃ HasHsType B ⦄ HasHsType (A × B)
iHsTy-Pair {A = A} {B = B} .HasHsType.HsType = Pair (HsType A) (HsType B)
Loading

0 comments on commit 4a4a962

Please sign in to comment.