Reputation: 8143
I am adding some function to threepenny ui api. I would like to had the ability to draw line with canvas.
The function that I can write have the following signature:
moveTo :: Vector -> UI ()
lineTo :: Vector -> UI ()
stroke :: UI ()
strokeStyle :: UI ()
beginPath :: UI ()
Each primitive moveTo
and lineTo
should happen in between a beginPath
.. stroke call.
How would you enforce the beginPath
... stroke sequence. By design I would like to give the user no choice for drawing lines. So the user is not aware of the beginPath
... stroke sequence.
Upvotes: 3
Views: 228
Reputation: 60503
Here's how I would design a canvas API.
newtype Drawing = ...
instance Monoid Drawing where ... -- for combining drawings
line :: Vector -> Vector -> Drawing
path :: [Vector] -> Drawing
withStyle :: Style -> Drawing -> Drawing
runDrawing :: Drawing -> UI ()
Here the functions operate on semantically meaningful objects (from the user's perspective), rather than imperative commands. This should be implementable with the type
newtype Drawing = Drawing (UI ())
however sometimes subtleties will require that the type have a bit more structure, so be open to that (e.g. Something -> UI ()
).
Upvotes: 1
Reputation: 16645
It's definitely good to design your API so that it can't be used improperly. One approach you might take here is to create an un-exported wrapper that lets you control how these particular actions are composed (I haven't tried to run this, sorry):
-- Don't export constructor
newtype Line a = Line { runLine :: UI a }
-- Wrap the return types in your current implementation with Line, for:
moveTo :: Vector -> Line ()
lineTo :: Vector -> Line ()
...
instance Monad Line where
(Line ui) >>= f = Line (ui >>= \a-> beginPath >> (runLine $ f a))
return = Line . return
A couple other points:
You may want to use a Monoid
instance instead, if your API doesn't need to bind any values (i.e. all of your line API functions end in -> Line ()
If you need to do something like wrap the entire sequence of composed line actions in e.g. start
and end
actions or whatever, you could further extend the above with
runLine (Line ui) = start >> ui >> end
Upvotes: 2