I would like to make the same program use two different Diagrams backends, notably diagrams-rasterific to generate PNGs, and diagrams-svg to generate SVGs, from the same diagram. Since Diagrams seems to be designed around using a single backend, I'm trying to define a composed backend, but am running into trouble defining renderToTree for the Backend instance:
import Diagrams.Core
import Diagrams.Core.Types
import qualified Diagrams.Backend.Rasterific as BackendA
import qualified Diagrams.Backend.SVG as BackendB
tokenA :: BackendA.B
tokenA = BackendA.Rasterific
tokenB :: BackendB.B
tokenB = BackendB.SVG
data Multi = Multi
deriving (Eq, Ord, Read, Show)
type B = Multi
data MultiResult n = MultiResult (Result BackendA.B V2 n) (Result BackendB.B V2 n)
-- alternatively:
-- data MultiResult n =
-- ResultA (Result BackendA.B V2 n)
-- | ResultB (Result BackendB.B V2 n)
type instance V Multi = V2
type instance N Multi = Double
instance (TypeableFloat n, Show n) => Backend Multi V2 n where
data Render Multi V2 n =
RenderMulti
{ renderA :: Render BackendA.B V2 n
, renderB :: Render BackendB.B V2 n
}
-- alternatively:
-- data Render Multi V2 n =
-- RenderA (renderA :: Render BackendA.B V2 n)
-- | RenderB (renderB :: Render BackendB.B V2 n)
type Result Multi V2 n = MultiResult n
data Options Multi V2 n = MultiOptions
renderRTree _ o tree = MultiResult
(renderRTree tokenA (toOptA o) (treeToA tree))
(renderRTree tokenB (toOptB o) (treeToB tree))
I'm unclear of how to defer to the individual backend implementations' renderRTree functions here. In either alternative structure (with the Render and Result types as sums or products), I'm failing to make the types match up. Concretely, in this approach, I'm stuck at
treeToA :: RTree Multi V2 n a -> RTree BackendA.B V2 n a
treeToA = fmap f
where
f (RAnnot a) = RAnnot a
f (RStyle s) = RStyle s
f REmpty = REmpty
f (RPrim x) = RPrim (toA x)
toA :: Prim Multi V2 n -> Prim BackendA.B V2 n
toA = ???
but I'm not that confident this is even the way to go.
toOptA, toOptB aren't a problem, I can fill those in once they're needed. I can also provide Renderable instance for this backend with either approach, e.g.
instance (Show n, TypeableFloat n) => Renderable (Path V2 n) Multi where
render _ x = RenderMulti (render tokenA x) (render tokenB x)
I agree with other-Daniel about keeping the
Diagrampolymorphic and using it at two types. Something like:The type sigs will only get worse as you add primitive types, so I'd probably define a constraint for all the primitives I want:
I don't think we can write
renderRTreefor yourMultiand have it type-check. We expect that all the instances ofRenderable _ Multiwill be of the form(Renderable p SVG, Renderable p Rasterific) => Renderable p Multi, which should be enough to writetoA. But we can't (AFAIK) promise this to GHC, becauseRenderableis an open type class.