gpt4 book ai didi

haskell - 更好的接口(interface)来构成破坏性运算符

转载 作者:行者123 更新时间:2023-12-04 06:13:10 32 4
gpt4 key购买 nike

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

-- |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图像一百次。当前的界面运行良好,但是我感觉可能会有一些标准的方法来执行这样的操作。所以,


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


编辑:
二进制操作的一个示例是“拍摄图像,进行模糊复制并从原始图像中减去。返回结果”。仅在IO monad中具有最少副本的有效版本将如下所示:

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


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

最佳答案

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

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

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


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

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


ApplicativeMonad和/或 (->) a实例通过将所有功能应用于单个参数来描述组合功能,就像合成功能中的图像一样。基本上是 Reader monad的轻量级嵌入式版本。
MonadIO实例,或者它所隐含的单面结构。通过将 IO的type参数固定为 (),monad定律简化为简单的monoid,以 return ()为单位,以 (>>)作为monoid操作。


为了重构您的组合,给定两个函数(展开的 ImageOperation),并想象 IO ()的隐式monoid是实际的实例,我们可以这样写:

nonOp = pure mempty

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


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

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

除此之外,如果您对构建整体结构的替代方法感兴趣,一个明显的变体是将 operate换成代表构建过程的某种东西,并合并运算符以使用类似 operate。不过,我不知道这是否真的可以给您带来任何好处。

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

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

我也不确定要将哪种类型的二进制运算符概括为该类型,除了可以在 ImageOperation上进行操作之外,没有什么其他可以做的了。您是说要泛化 IO ()来处理一个图像的多个可变引用吗?还是涉及对图像的操作返回了除 poorMansHighPass以外的内容?



编辑:好的,让我们看一下如何分解 gaussian。希望我在这里正确阅读了它在做什么:

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

接下来,也可以将 Image排除在外,并通过附加的 subtr' = ImgOp . flip subtractpoorMansHP' img = gauss' (5, 5) #> subtr' img进行参数设置。

这两个是函数的核心,可以按照通常的方式将它们组合: img。恢复原始功能的最后一件事是,赋予 poorMansHP'operate参数必须是同一图像,其副本将由 ImageOperation传递给内部函数。

首先,我们将显式展开 withClone并将其用于重新实现中:

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


在这里用 clone代替 do

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


operate块进行解糖:

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


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

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


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

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

关于haskell - 更好的接口(interface)来构成破坏性运算符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6059505/

32 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com