更好的接口来构成破坏性运算符 [英] Better interface for composing destructive operators

查看:63
本文介绍了更好的接口来构成破坏性运算符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的一个旧项目中有以下代码:

I've got the following code in an old project of mine:

-- |ImageOperation is a name for unary operators that mutate images inplace.
newtype ImageOperation c d = ImgOp (Image c d-> IO ())

-- |Compose two image operations
(#>) :: ImageOperation c d-> ImageOperation c d -> ImageOperation c d
(#>) (ImgOp a) (ImgOp b) = ImgOp (\img -> (a img >> b img))

-- |An unit operation for compose 
nonOp = ImgOp (\i -> return ())

-- |Apply image operation to a Copy of an image
img <# op = unsafeOperate op img

-- | Apply the operation on a clone of an image
operate (ImgOp op) img = withClone img $ \clone -> 
                                           op clone >> return clone

unsafeOperate op img = unsafePerformIO $ operate op img

它的主要目的是允许组成opencv运算符,这些运算符可以就地运行并接受相同格式和尺寸的图像.这是一项重要的优化,因为例如不绘制100条线,就会分配1mb图像一百次.当前的界面效果很好,但是我感觉可能会有一些标准的方法来做这样的事情.所以,

Its main purpose is to allow composition of opencv operators that run in place and accept an image of the same format and a dimension. It is an important optimization, since for example, without it drawing 100 lines would allocate the 1mb image hundred times. The current interface works nicely but I have a feeling that there might be a some standard approach to doing thing like this. So,

  1. 我是否正在做其他以标准化名称出现的事情?
  2. 我能做得更好吗?
  3. 这种方法是否可以以不允许不安全引用可变图像特定状态的方式推广到二进制运算符?

修改: 二进制运算的一个示例是拍摄图像,进行模糊的复制并从原始图像中减去.返回结果".仅在IO monad中具有最少副本的有效版本将如下所示:

An example of a binary operation is 'take an image, make a blurred copy and subtract from the original. Return the result'. The effective version with minimal copies in just the IO monad would be something like:

poorMansHighPass img = do
    x <- clone img
    gaussian (5,5) x
    subtract x img
    return x

尽管我可以使像这样的运算符,但是我更喜欢由原始运算符组成的东西,而不是难看的不安全的io代码.

Although I can make an operator like this, I would much prefer something that is more of a composition of primitive operators than ugly bit of unsafe io code.

推荐答案

好吧,我至少可以指出如何称呼您当前正在使用的某些模式.

Well, I can at least point out what to call some of the patterns you're using currently.

因此,我们有一个表示对某些可变数据的引用的类型,以及一个表示对它进行不透明操作的类型.我们还有一个null op和一个composition函数,它给出了一个明显的Monoid实例:

So we have a type representing a reference to some mutable data, and a type representing opaque operations on it. We also have a null op and a composition function, which gives an obvious Monoid instance:

instance Monoid (ImageOperation c d) where
    mempty = nonOp
    mappend = (#>)

因此,至少可以使用一个标准名称.

So that's at least one standard name you could use.

此外,上面的Monoid实际上是其他两个众所周知的类型的属性的直接结果:

Further, the above Monoid is actually a straightforward result of the properties of two other well-known types:

  • (->) aApplicative和/或Monad实例通过将所有函数应用于单个参数来描述组合函数,就像合成函数中的图像一样.基本上是Reader monad的轻量级嵌入式版本.

  • The Applicative and/or Monad instance for (->) a describes combining functions by applying all of them to a single argument, as with the image in the composition function. Basically a lightweight, in-line version of the Reader monad.

IOMonad实例,或者它所隐含的单面结构.通过将IO的类型参数固定为(),单子定律可简化为简单的等式面,以return ()为单位,以(>>)为等面运算.

The Monad instance for IO, or rather the monoidal structure it implies. By fixing IO's type parameter to (), the monad laws reduce to a simple monoid, with return () as unit and (>>) as the monoid operation.

要重构您的组合,请给定两个函数(未包装的ImageOperation s)并想象IO ()的隐式monoid是实际实例,我们可以编写:

To reconstruct your combination, given two functions (unwrapped ImageOperations) and imagining that the implied monoid for IO () is an actual instance, we could write:

nonOp = pure mempty

x #> y = mappend <$> x <*> y

还值得注意的是,诸如阅读器monad和允许可变状态的monad之类的组合本质上描述了具有可变引用的周围环境",也就是可变全局变量,不同之处在于全局"在这里意味着在一次计算中"组合的单子".我实际上已经使用ReaderTSTM显式构造了这种monad.

It's also worth noting that the combination of something like a reader monad and a monad allowing mutable state essentially describes "a surrounding environment with mutable references", a.k.a. mutable global variables, except that "global" here means "within a single computation of the combined monad". I've actually constructed such a monad explicitly, using ReaderT and STM.

处理合并操作.要实际运行某个操作,您需要一个Image,而我正在收集,您只想对克隆进行操作,而克隆的创建效率很低.幸运的是,考虑到Monoid的上述结构的通用性,在真正运行ImageOperation之前,您几乎没有什么可以塞进ImageOperation的.生成克隆大概是IO操作,这是我假设在operate中进行的操作-可能真的没有其他方法可以做到这一点.

That handles combining operations. To actually run an operation, you need an Image and I'm gathering you want to only operate on clones, the creation of which is inefficient. Fortunately, considering how very general the above construction for the Monoid is, there's really nothing you can't cram into an ImageOperation before actually running it. Generating the clone is presumably an IO operation and is what I assume is going on in operate--there's probably not really any other way to do that.

除此之外,如果您对构建整件商品的替代方式感兴趣,则一个明显的变体是将Image换成代表构造商品的过程,并合并运算符以转换生成的图像使用类似operate之类的东西.我不知道这是否真的可以给你带来任何好处.

Beyond that, if you're interested in alternate ways to structure the whole thing, one obvious variant would be to wrap Image instead into something representing the process of constructing one, with operators merged in to transform the image being produced using something like operate. I don't know if this would actually gain you anything, though.

事实上,我倾向于怀疑确实还有其他方法可以做到这一点.您正在将FFI绑定到一个高度命令性的库中,并且只能做很多事情来掩饰它.

In fact, I'm inclined to doubt that there're really any other ways to do this. You're writing an FFI binding to a highly imperative library and there's only so much you can do to disguise that.

但是,我不确定为什么您使用的是不安全版本的operate.这有什么实际目的?

I'm not sure, however, why you have an unsafe version of operate. What practical purpose would this serve?

我也不知道您想将哪种二进制运算符概括为该类型,除了在这里使用ImageOperation之外,您还可以执行其他操作.您是说要泛化ImageOperation来处理一个图像的多个可变引用吗?还是涉及到对图像进行操作的操作,这些操作返回的不仅是IO ()以外的内容?

I'm also not sure what kind of binary operators you'd like to generalize this to--there's not much else you can do operating on ImageOperation besides what you have here. Do you mean generalizing ImageOperation to work on more than one mutable reference to an image? Or something involving operations on images that return something other than just IO ()?

编辑:好的,让我们看一下如何分解poorMansHighPass.希望我在这里正确阅读了它的内容:

EDIT: Okay, let's look at how one might decompose poorMansHighPass. Hopefully I'm correctly reading what it's doing here:

首先,gaussian是独立的,可以将其分解为自己的操作:gauss' = ImgOp . gaussian.

First, gaussian is independent and can be factored out as its own operation: gauss' = ImgOp . gaussian.

接下来,也可以将subtract分解出来,并通过附加的Image:subtr' = ImgOp . flip subtract进行参数设置.

Next, subtract can also be factored out, parameterized by an additional Image: subtr' = ImgOp . flip subtract.

这两个是函数的核心,可以按照通常的方式将它们组合在一起:poorMansHP' img = gauss' (5, 5) #> subtr' img.恢复原始功能的最后一件事是,赋予poorMansHP'img参数必须是 same 图像,其克隆副本将通过operate传递给内部函数.

These two are the core of the function, and they can be combined in the usual manner: poorMansHP' img = gauss' (5, 5) #> subtr' img. The last thing needed to recover the original function is that the img argument given to poorMansHP' must be the same image whose clone is passed to the inner function by operate.

首先,我们将显式拆开ImageOperation并将其用于重新实现:

First we'll explicitly unwrap the ImageOperation and use that in the reimplementation:

poorMansHighPass img = 
    let (ImgOp op) = gauss' (5, 5) #> subtr' img
    in do x <- clone img
          op x
          return x

在此处使用clone替代withClone

poorMansHighPass img = 
    let (ImgOp op) = gauss' (5, 5) #> subtr' img
    in withClone img $ \x -> do op x
                                return x

do块加糖:

poorMansHighPass img = 
    let (ImgOp op) = gauss' (5, 5) #> subtr' img
    in withClone img $ \x -> op x >> return x

...显然包含了operate的重新实现,因此请替换并简化:

...which obviously contains a reimplementation of operate, so replace that and simplify:

poorMansHighPass img = operate (gauss' (5, 5) #> subtr' img) img

更有趣的是以修改参数而不是克隆的方式实现poorMansHighPass,这将允许将其打包为ImageOperation本身.可能就是这样,我误读了您的代码吗?

More interesting would be implementing poorMansHighPass in a way that modifies the argument instead of the clone, which would allow it to be packaged up as an ImageOperation itself. Possibly that's what it's supposed to be doing, and I misread your code?

无论如何,重构的基本结构是相同的,但是您需要一个不同的合成运算符-与其在序列中将两个运算符依次应用于同一输入,还需要在内部创建输入的副本重新组合结果之前.我有一个大概的想法,什么样的结构可以使此工作顺利进行,如果您愿意,我可以详细说明,但我必须仔细研究一下以确保其正常工作.

Anyway, the basic structure of the refactoring would be the same, but you'd need a different composition operator--instead of applying two operators to the same input in sequence, it would need to create a clone of the input internally before recombining the results. I have a rough idea what kind of structure would make this work smoothly, which I can elaborate on if you'd like but I'll have to work through it a bit to make sure it behaves properly.

这篇关于更好的接口来构成破坏性运算符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆