Usando il modello Haskell, come posso giungere lo stesso tipo in più posizioni?
-
29-10-2019 - |
Domanda
Sto definendo istanze di lezioni da vettore-spazio per il OpenGL Tipi e per risparmiare i miei muscoli da digitare, voglio usare il modello Haskell per scrivere un sacco di istanze per me.
Ho iniziato in piccolo definendo la funzione per derivare istanze per AdditiveGroup
:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Data.VectorSpace.OpenGL.TH where
import Control.Applicative
import Control.Monad
import Data.AdditiveGroup
import Data.VectorSpace
import Language.Haskell.TH
deriveScalarAdditive ts = concat <$> forM (map conT ts) (\t -> [d|
instance AdditiveGroup $t where zeroV = 0; (^+^) = (+); negateV = negate
|])
Funziona bene, ma nota che sto solo girando $t
una volta tra le staffe di Oxford. Ora, la funzione per derivare VectorSpace
istanze:
deriveScalarVectorSpace ts = concat <$> forM (map conT ts) (\t -> [d|
instance VectorSpace $t where type Scalar $t = $t; (*^) = (*)
|])
Ma questo barf:
Type indexes must match class instance head
Found `t_tt' but expected `t_ts'
In the associated type instance for `Scalar'
In the instance declaration for `VectorSpace $t'
In the Template Haskell quotation
[d| instance VectorSpace $t where
type instance Scalar $t = $t
{ *^ = (*) } |]
La differenza tra t_ts
e t_tt
Nell'errore mi dice che questo sta creando un nuovo nome univoco ogni volta che mi girgo $t
, quando ovviamente la definizione funzionerà solo se questi tipi sono uguali.
C'è un modo per ottenere il comportamento che voglio con le parentesi di Oxford, o dovrò ricadere in un buon vecchio ambito lessicale e il Language.Haskell.TH
Combinatori? So che questo sarebbe probabilmente più facile con il CPP, ma voglio cogliere l'occasione per imparare un po '.
Soluzione
Penso che dovrai usare il Language.Haskell.TH
Combinatori. Vedi i seguenti biglietti:
Farlo è molto semplice. Inizierei con questo (leggermente formattato)
*Foo Language.Haskell.TH> runQ (deriveScalarAdditive [''Int] ) >>= print
[InstanceD [] (AppT (ConT Data.AdditiveGroup.AdditiveGroup) (ConT GHC.Types.Int))
[ValD (VarP zeroV_12) (NormalB (LitE (IntegerL 0))) [],
ValD (VarP ^+^_13) (NormalB (VarE GHC.Num.+)) [],
ValD (VarP negateV_14) (NormalB (VarE GHC.Num.negate)) []]
]
Da qui, è piuttosto semplice vedere come costruire istanze usando i combinatori. Si noti inoltre che è possibile mescolare citazioni con th, un'espressione citata [| some code |] :: ExpQ
, che è spesso utile per creare corpi funzionali.