3

我正在向threepenny ui api 添加一些功能。我想有能力用画布画线。

我可以编写的函数具有以下签名:

moveTo :: Vector -> UI ()
lineTo :: Vector -> UI ()
stroke :: UI ()
strokeStyle :: UI ()
beginPath :: UI ()

每个原语moveTolineTo应该发生在一个beginPath..stroke 调用之间。您将如何执行beginPath...笔画序列。按照设计,我想让用户别无选择地画线。所以用户不知道beginPath......笔画顺序。

4

2 回答 2

2

设计你的 API 绝对是好的,这样它就不会被不当使用。您可以在这里采取的一种方法是创建一个未导出的包装器,让您可以控制这些特定操作的组合方式(抱歉,我没有尝试运行它):

-- 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

其他几点:

  1. 如果您Monoid的 API 不需要绑定任何值(即您的所有行 API 函数以-> Line ()

  2. 如果您需要做一些事情,例如将整个组合的行动作序列包装在例如startend动作或其他内容中,您可以进一步扩展上述内容

    runLine (Line ui) = start >> ui >> end
    
于 2014-06-27T18:17:01.037 回答
1

以下是我设计画布 API 的方式。

newtype Drawing = ...
instance Monoid Drawing where ... -- for combining drawings

line :: Vector -> Vector -> Drawing
path :: [Vector] -> Drawing
withStyle :: Style -> Drawing -> Drawing
runDrawing :: Drawing -> UI ()

在这里,函数在语义上有意义的对象(从用户的角度)上运行,而不是命令式命令。这应该可以通过类型实现

newtype Drawing = Drawing (UI ())

然而,有时细微之处需要类型有更多的结构,所以要对此持开放态度(例如Something -> UI ())。

于 2014-06-27T21:26:38.277 回答