|
| 1 | +module Tactic.Derive.Convertible where |
| 2 | + |
| 3 | +open import Level |
| 4 | +open import MetaPrelude |
| 5 | +open import Meta |
| 6 | + |
| 7 | +import Data.List as L |
| 8 | +import Data.List.NonEmpty as NE |
| 9 | +import Data.String as S |
| 10 | +import Data.Product as P |
| 11 | + |
| 12 | +open import Relation.Nullary |
| 13 | +open import Relation.Nullary.Decidable |
| 14 | + |
| 15 | +open import Reflection.Tactic |
| 16 | +open import Reflection.AST.Term |
| 17 | +open import Reflection.AST.DeBruijn |
| 18 | +import Reflection.TCM as R |
| 19 | +open import Reflection.Utils |
| 20 | +open import Reflection.Utils.TCI |
| 21 | +import Function.Identity.Effectful as Identity |
| 22 | + |
| 23 | +open import Class.DecEq |
| 24 | +open import Class.DecEq |
| 25 | +open import Class.Functor |
| 26 | +open import Class.Monad |
| 27 | +open import Class.MonadTC.Instances |
| 28 | +open import Class.Traversable |
| 29 | +open import Class.Show |
| 30 | +open import Class.MonadReader |
| 31 | + |
| 32 | +open import Reflection.Utils.Substitute |
| 33 | +open import Class.Convertible |
| 34 | +open import Class.HasHsType |
| 35 | +open import Tactic.Derive.HsType |
| 36 | + |
| 37 | +private instance |
| 38 | + _ = Functor-M {TC} |
| 39 | + |
| 40 | +-- TODO: move to agda-stdlib-meta |
| 41 | +liftTC : ∀ {a} {A : Set a} → R.TC A → TC A |
| 42 | +liftTC m _ = m |
| 43 | + |
| 44 | +private |
| 45 | + |
| 46 | + open MonadReader ⦃...⦄ |
| 47 | + |
| 48 | + variable |
| 49 | + A B C : Set |
| 50 | + |
| 51 | + TyViewTel = List (Abs (Arg Type)) |
| 52 | + |
| 53 | + substTel : Subst TyViewTel |
| 54 | + substTel x s [] = [] |
| 55 | + substTel x s (abs z t ∷ tel) = abs z (substArg x s t) ∷ (substTel (ℕ.suc x) s tel) |
| 56 | + -- Note: `abs` is abused in TyViewTel and doesn't actually scope over the `t` |
| 57 | + |
| 58 | + -- Substitute leading level parameters with lzero |
| 59 | + smashLevels : TyViewTel → ℕ × TyViewTel |
| 60 | + smashLevels (abs s (arg i (def (quote Level) [])) ∷ tel) = |
| 61 | + P.map ℕ.suc (substTel 0 (quote 0ℓ ∙)) $ smashLevels tel |
| 62 | + smashLevels tel = (0 , tel) |
| 63 | + |
| 64 | + tyViewToTel : TyViewTel → Telescope |
| 65 | + tyViewToTel = L.map λ where (abs s a) → s , a |
| 66 | + |
| 67 | + hideTyView : Abs (Arg A) → Abs (Arg A) |
| 68 | + hideTyView (abs s (arg (arg-info _ m) x)) = abs s (arg (arg-info hidden m) x) |
| 69 | + |
| 70 | + -- The type of a Convertible instance. For parameterised types adds the appropriate instance |
| 71 | + -- arguments and instantiates level arguments to lzero. For instance, |
| 72 | + -- instanceType _⊎_ Hs.Either = {A B : Set} {a b : Set} → ⦃ Convertible A a ⦄ → ⦃ Convertible B b ⦄ |
| 73 | + -- Convertible (A ⊎ B) (Hs.Either a b) |
| 74 | + instanceType : (agdaName hsName : Name) → TC TypeView |
| 75 | + instanceType agdaName hsName = do |
| 76 | + aLvls , agdaParams ← smashLevels <$> getParamsAndIndices agdaName |
| 77 | + hLvls , hsParams ← smashLevels <$> getParamsAndIndices hsName |
| 78 | + true ← return (length agdaParams == length hsParams) |
| 79 | + where false → liftTC $ R.typeErrorFmt "%q and %q have different number of parameters" agdaName hsName |
| 80 | + let n = length agdaParams |
| 81 | + l₀ = quote 0ℓ ∙ |
| 82 | + agdaTy ← applyWithVisibility agdaName $ L.replicate aLvls l₀ ++ L.map ♯ (take n (downFrom (n + n))) |
| 83 | + hsTy ← applyWithVisibility hsName $ L.replicate hLvls l₀ ++ L.map ♯ (downFrom n) |
| 84 | + let instHead = weaken n $ quote Convertible ∙⟦ agdaTy ∣ hsTy ⟧ |
| 85 | + tel = L.map hideTyView (agdaParams ++ hsParams) ++ |
| 86 | + L.replicate n (abs "_" (iArg (quote Convertible ∙⟦ ♯ (n + n ∸ 1) ∣ ♯ (n ∸ 1) ⟧))) |
| 87 | + return $ tel , instHead |
| 88 | + |
| 89 | + -- Compute one clause of the Convertible instance. For instance, |
| 90 | + -- conversionClause Convertible.to to ((c₁ , _) , (c₂ , _)) generates |
| 91 | + -- .to (c₁ x₁ .. xn) = c₂ (to x₁) .. (to xn) |
| 92 | + -- where the xi are the visible constructor arguments. |
| 93 | + conversionClause : Name → Name → (Name × Type) × (Name × Type) → TC Clause |
| 94 | + conversionClause prjP prjE ((fromC , fromTy) , (toC , toTy)) = do |
| 95 | + let isVis = λ { (abs _ (arg (arg-info visible _) _)) → true; _ → false } |
| 96 | + fromTel = L.filterᵇ isVis (proj₁ (viewTy fromTy)) |
| 97 | + toTel = L.filterᵇ isVis (proj₁ (viewTy toTy)) |
| 98 | + n = length fromTel |
| 99 | + mkCon c mkArg = Term.con c $ L.map (vArg ∘ mkArg ∘ ♯) (downFrom n) |
| 100 | + mkConP c mkArg = Pattern.con c $ L.map (vArg ∘ mkArg ∘ `_) (downFrom n) |
| 101 | + true ← return (n == length toTel) |
| 102 | + where false → liftTC $ R.typeErrorFmt "%q and %q have different number of arguments" fromC toC |
| 103 | + return $ clause (tyViewToTel $ L.map (λ where (abs x (arg i _)) → abs x (arg i unknown)) fromTel) |
| 104 | + (vArg (proj prjP) ∷ vArg (mkConP fromC id) ∷ []) |
| 105 | + (mkCon toC (prjE ∙⟦_⟧)) |
| 106 | + |
| 107 | + -- Compute the clauses of a convertible instance. |
| 108 | + instanceClauses : (agdaName hsName : Name) → TC (List Clause) |
| 109 | + instanceClauses agdaName hsName = do |
| 110 | + agdaCons ← getConstrs agdaName |
| 111 | + hsCons ← getConstrs hsName |
| 112 | + agdaPars ← length <$> getParamsAndIndices agdaName |
| 113 | + hsPars ← length <$> getParamsAndIndices hsName |
| 114 | + true ← return (length agdaCons == length hsCons) |
| 115 | + where false → liftTC $ R.typeErrorFmt "%q and %q have different number of constructors" agdaName hsName |
| 116 | + toClauses ← mapM (conversionClause (quote Convertible.to) (quote to) ) (L.zip agdaCons hsCons) |
| 117 | + fromClauses ← mapM (conversionClause (quote Convertible.from) (quote from)) (L.zip hsCons agdaCons) |
| 118 | + return $ toClauses ++ fromClauses |
| 119 | + |
| 120 | + absurdClause : Name → Clause |
| 121 | + absurdClause prj = absurd-clause (("x" , vArg unknown) ∷ []) (vArg (proj prj) ∷ vArg (absurd 0) ∷ []) |
| 122 | + |
| 123 | + -- Compute conversion clauses for the current goal and wrap them in a pattern lambda. |
| 124 | + patternLambda : TC Term |
| 125 | + patternLambda = do |
| 126 | + quote Convertible ∙⟦ `A ∣ `B ⟧ ← reduce =<< goalTy |
| 127 | + where t → liftTC $ R.typeErrorFmt "Expected Convertible A B, got %t" t |
| 128 | + agdaCons ← getConstrsForType `A |
| 129 | + hsCons ← getConstrsForType `B |
| 130 | + toClauses ← mapM (conversionClause (quote Convertible.to) (quote to) ) (L.zip agdaCons hsCons) |
| 131 | + fromClauses ← mapM (conversionClause (quote Convertible.from) (quote from)) (L.zip hsCons agdaCons) |
| 132 | + case toClauses ++ fromClauses of λ where |
| 133 | + [] → return $ pat-lam (absurdClause (quote Convertible.to) ∷ absurdClause (quote Convertible.from) ∷ []) [] |
| 134 | + cls → return $ pat-lam cls [] |
| 135 | + |
| 136 | +doPatternLambda : Term → R.TC Term |
| 137 | +doPatternLambda hole = patternLambda =<< initTCEnvWithGoal hole |
| 138 | + |
| 139 | +-- Deriving a Convertible instance. Usage |
| 140 | +-- unquoteDecl iName = deriveConvertible iName (quote AgdaTy) (quote HsTy) |
| 141 | +deriveConvertible : Name → Name → Name → R.TC ⊤ |
| 142 | +deriveConvertible instName agdaName hsName = initUnquoteWithGoal ⦃ defaultTCOptions ⦄ (agda-sort (lit 0)) do |
| 143 | + agdaDef ← getDefinition agdaName |
| 144 | + hsDef ← getDefinition hsName |
| 145 | + -- instName ← freshName $ "Convertible" S.++ show hsName |
| 146 | + instTel , instTy ← instanceType agdaName hsName |
| 147 | + inst ← declareDef (iArg instName) (tyView (instTel , instTy)) |
| 148 | + clauses ← instanceClauses agdaName hsName |
| 149 | + defineFun instName clauses |
| 150 | + return _ |
| 151 | + |
| 152 | +-- Macros providing an alternative interface. Usage |
| 153 | +-- iName : ConvertibleType AgdaTy HsTy |
| 154 | +-- iName = autoConvertible |
| 155 | +-- or |
| 156 | +-- iName = autoConvert AgdaTy |
| 157 | +macro |
| 158 | + ConvertibleType : Name → Name → Tactic |
| 159 | + ConvertibleType agdaName hsName = initTac ⦃ defaultTCOptions ⦄ $ |
| 160 | + unifyWithGoal ∘ tyView =<< instanceType agdaName hsName |
| 161 | + |
| 162 | + autoConvertible : Tactic |
| 163 | + autoConvertible = initTac ⦃ defaultTCOptions ⦄ $ |
| 164 | + unifyWithGoal =<< patternLambda |
| 165 | + |
| 166 | + autoConvert : Name → Tactic |
| 167 | + autoConvert d hole = do |
| 168 | + hsTyMeta ← R.newMeta `Set |
| 169 | + R.checkType hole $ quote Convertible ∙⟦ d ∙ ∣ hsTyMeta ⟧ |
| 170 | + hsTy ← solveHsType (d ∙) |
| 171 | + R.unify hsTyMeta hsTy |
| 172 | + R.unify hole =<< doPatternLambda hole |
| 173 | + |
| 174 | +-- Tests |
| 175 | + |
| 176 | +private |
| 177 | + |
| 178 | + record Test : Set where |
| 179 | + field f : ℕ |
| 180 | + g : Maybe ℕ |
| 181 | + h : List ℕ |
| 182 | + |
| 183 | + instance |
| 184 | + HsTy-Test = autoHsType Test ⊣ withConstructor "MkTest" • fieldPrefix "t" |
| 185 | + Conv-Test = autoConvert Test |
0 commit comments