diagrams / diagrams-lib

Diagrams standard library
https://diagrams.github.io/
Other
135 stars 63 forks source link

[Question] is there an easier way to have "multi-main" with IO action support? #293

Open Javran opened 7 years ago

Javran commented 7 years ago

Let's say we have the following code:

{-# LANGUAGE
    NoMonomorphismRestriction
  , FlexibleContexts
#-}
module Main where

import Diagrams.Prelude
import Diagrams.Backend.SVG.CmdLine

main :: IO ()
main = mainWith
    [ ("big", circle 10 :: Diagram B)
    , ("small", circle 1)
    ]

now suppose I want to have a circle of random size, or whatever action with IO involved. while I could have performed all IO actions and still passing [(String, Diagram B)] to mainWith, but that's way too eager. so I turn the argument of mainWith to be [(String, IO (Diagram B))] (as the actual IO action does not really matter, I've omitted them to leave just pures):

main :: IO ()
main = mainWith
    [ ("big", pure $ circle 10 :: IO (Diagram B))
    , ("small", pure $ circle 1)
    ]

then GHC starts to complain about a missing instance of Mainable [(String, IO (Diagram B))]. this leads me to have to write the following stuff out in full (together with FlexibleInstances, TypeFamilies and probably -fno-warn-orphans):

instance SVGFloat n => Mainable [(String,IO (QDiagram SVG V2 n Any))] where
    type MainOpts [(String,IO (QDiagram SVG V2 n Any))]
        = (MainOpts (QDiagram SVG V2 n Any), DiagramMultiOpts)
    mainRender = defaultMultiMainRender

despite that there is a very close one in svg lib (i.e. Mainable [(String, Diagram B)]) but I really doubt GHC can figure this out by itself.

Is there anything to make this a bit easier, I don't really expect something that sounds easy to be this involved.

byorgey commented 7 years ago

Good question. Actually, off the top of my head I see no reason we can't generalize the instance in diagrams-svg to something like

instance (SVGFloat n, Mainable d) => Mainable [(String, d)] where
  type MainOpts [(String, d)] = (MainOpts d, DiagramMultiOpts)
  mainRender = defaultMultiMainRender

We already have an instance Mainable d => Mainable (IO d) so this would cover your use case and a lot more besides. I'll try to look into doing this soon.

cchalmers commented 7 years ago

You could write the generalised instance in Diagrams.Backend.CmdLine, you don't have to do it for each backend.

I've actually done this instance in my rewrite although the class is a little different there. I'll write a similar version for HEAD.

cchalmers commented 7 years ago

Looking at the comment for defaultMultiMainRender it says:

We do not provide this instance in general so that backends can choose to opt-in to this form or provide a different instance that makes more sense.

But I feel like the instance generalises nicely. I can't think of any reason a backend would want a different instance. Is everyone happy with writing a instance Mainable d => Mainable [(String, d)] instance in Diagrams.Backend.CmdLine?

Note that writing that instance in diagrams-svg would overlap with any other backend that writes the same instance.

fryguybob commented 7 years ago

I think I wrote that comment thinking about the [d] instance for multiple pages in postscript. It should be fine to have the general instance. Maybe a good general reasoning for this is all backends will be able to handle single diagrams so we should be able to have general instances for anything that has a straight forward interpretation (somewhat subjective) for running single diagrams. Backends that open up different interpretations (like postscript handling multiple pages or Rasterific handling animations) can use newtypes to have their specific interpretation of a general form.

byorgey commented 7 years ago

Yes, sounds good to me. Let's put the general instance in Diagrams.Backend.CmdLine.

byorgey commented 7 years ago

@cchalmers did we ever get around to this? If not, I can do it.

cchalmers commented 7 years ago

@byorgey I remember looking at it but I must have forgot.