存在性反模式,如何避免

2024-03-18

下面的似乎有效......但看起来很笨拙。

data Point = Point Int Int
data Box = Box Int Int
data Path = Path [Point]
data Text = Text

data Color = Color Int Int Int
    data WinPaintContext = WinPaintContext Graphics.Win32.HDC

class CanvasClass vc paint where
    drawLine :: vc -> paint -> Point -> Point -> IO ()
    drawRect :: vc -> paint -> Box -> IO ()
    drawPath :: vc -> paint -> Path -> IO ()

class (CanvasClass vc paint) => TextBasicClass vc paint where
    basicDrawText :: vc -> paint -> Point -> String -> IO ()

instance CanvasClass WinPaintContext WinPaint where
    drawLine = undefined
    drawRect = undefined
    drawPath = undefined

instance TextBasicClass WinPaintContext WinPaint where
    basicDrawText (WinPaintContext a) = winBasicDrawText a

op :: CanvasClass vc paint => vc -> Box -> IO ()
op canvas _ = do
    basicDrawText canvas WinPaint (Point 30 30) "Hi"

open :: IO ()
open = do
    makeWindow (Box 300 300) op

winBasicDrawText :: Graphics.Win32.HDC -> WinPaint -> Point -> String -> IO ()
winBasicDrawText hdc _ (Point x y) str = do
    Graphics.Win32.setBkMode hdc Graphics.Win32.tRANSPARENT
    Graphics.Win32.setTextColor hdc (Graphics.Win32.rgb 255 255 0)
    Graphics.Win32.textOut hdc 20 20 str
    return ()

windowsOnPaint :: (WinPaintContext -> Box -> IO ()) ->
                  Graphics.Win32.RECT ->
                  Graphics.Win32.HDC ->
                  IO ()
windowsOnPaint f rect hdc = f (WinPaintContext hdc) (Box 30 30)

makeWindow :: Box -> (WinPaintContext -> Box -> IO ()) -> IO ()
makeWindow (Box w h) onPaint =
  Graphics.Win32.allocaPAINTSTRUCT $ \ lpps -> do
  hwnd <- createWindow w h (wndProc lpps (windowsOnPaint onPaint))
  messagePump hwnd

现在,似乎首选的方法就是简单地拥有

data Canvas = Canvas {
    drawLine :: Point -> Point -> IO (),
    drawRect :: Box -> IO (),
    drawPath :: Path -> IO ()
}

hdc2Canvas :: Graphics.Win32.HDC -> Paint -> IO ( Canvas )
hdc2Canvas hdc paint = Canvas { drawLine = winDrawLine hdc paint ... }

然而...

我们喜欢保留颜料并在整个绘图过程中改变它们,因为它们的创建和销毁成本很高。绘制可以只是一个列表,如 [bgColor red, fgColor blue, font "Tahoma"] 或其他东西,或者它可以是指向绘制系统使用的内部结构的指针(这是对 Windows GDI 的抽象,但最终会抽象over direct2d 和 coregraphics),其中有“绘制”对象,我不想一遍又一遍地重新创建然后绑定。

在我看来,存在主义的美妙之处在于它们可以不透明地包裹某些东西并对其进行抽象,我们可以将其保存在某个地方,将其拉回来,等等。当您部分应用时,我认为存在的问题是您部分应用的东西现在“卡在”容器内。这是一个例子。假设我有一个像这样的绘画对象

data Paint = Paint {
    setFg :: Color -> IO () ,
    setBg :: Color -> IO ()
}

我可以将指针放在哪里?当我将Paint交给Canvas中的某个函数时,他如何获取指针?设计这个 API 的正确方法是什么?


界面

首先,您需要问“我的要求是什么?”。让我们用简单的英语说明我们想要画布做什么(这些是我根据你的问题的猜测):

  • 有些画布上可以放置形状
  • 有些画布上可以放置文字
  • 有些画布根据颜料改变了它们的用途
  • 我们还不知道颜料是什么,但对于不同的画布它们会有所不同

现在我们将这些想法转化为 Haskell。 Haskell 是一种“类型优先”的语言,因此当我们谈论需求和设计时,我们可能正在谈论类型。

  • 在 Haskell 中,当我们在谈论类型时看到“some”这个词时,我们会想到类型类。例如,show类说“某些类型可以表示为字符串”。
  • 当我们谈论一些我们还不知道的事情时,当我们谈论需求时,这是一种我们还不知道它是什么的类型。那是一个类型变量。
  • “放上它们”似乎意味着我们拿一块画布,在上面放一些东西,然后再放一块画布。

现在我们可以为每个需求编写类:

class ShapeCanvas c where -- c is the type of the Canvas
    draw :: Shape -> c -> c

class TextCanvas c where
    write :: Text -> c -> c

class PaintCanvas p c where -- p is the type of Paint
    load :: p -> c -> c

类型变量c仅使用一次,显示为c -> c。这表明我们可以通过替换来使这些更通用c -> c with c.

class ShapeCanvas c where -- c is the type of the canvas
    draw :: Shape -> c

class TextCanvas c where
    write :: Text -> c

class PaintCanvas p c where -- p is the type of paint
    load :: p -> c

Now PaintCanvas看起来像一个class这在 Haskell 中是有问题的。类型系统很难弄清楚像这样的类中发生了什么

class Implicitly a b where
    convert :: b -> a

我会通过改变来缓解这个问题PaintCanvas来利用TypeFamilies扩大。

class PaintCanvas c where 
    type Paint c :: * -- (Paint c) is the type of Paint for canvases of type c
    load :: (Paint c) -> c

现在,让我们将界面的所有内容放在一起,包括形状和文本的数据类型(经过修改以对我有意义):

{-# LANGUAGE TypeFamilies #-}

module Data.Canvas (
    Point(..),
    Shape(..),
    Text(..),
    ShapeCanvas(..),
    TextCanvas(..),
    PaintCanvas(..)
) where

data Point = Point Int Int

data Shape = Dot Point
           | Box Point Point 
           | Path [Point]

data Text = Text Point String

class ShapeCanvas c where -- c is the type of the Canvas
    draw :: Shape -> c

class TextCanvas c where
    write :: Text -> c

class PaintCanvas c where 
    type Paint c :: * -- (Paint c) is the type of Paint for canvases of type c
    load :: (Paint c) -> c

一些例子

除了我们已经制定的要求之外,本节还将介绍对有用画布的额外要求。这类似于我们更换时丢失的东西c -> c with c在画布类中。

让我们从第一个示例代码开始,op。使用我们的新界面,它很简单:

op :: (TextCanvas c) => c
op = write $ Text (Point 30 30) "Hi"

让我们举一个稍微复杂一点的例子。画一个“X”的东西怎么样?我们可以画“X”的第一笔

ex :: (ShapeCanvas c) => c
ex = draw $ Path [Point 10 10, Point 20 20]

但我们没有办法再添加一个Path对于交叉行程。我们需要某种方法将两个绘图步骤结合在一起。有类型的东西c -> c -> c将会是完美的。我能想到的最简单的 Haskell 类提供了这个功能Monoid a's mappend :: a -> a -> a. A Monoid需要同一性和关联性。假设在画布上进行了绘图操作而不影响画布是否合理?听起来很有道理。假设以相同顺序完成的三个绘图操作执行相同的操作,即使前两个一起执行,然后第三个,或者如果执行第一个,然后第二个和第三个一起执行,是否合理? ?同样,这对我来说似乎很合理。这建议我们可以写ex as:

ex :: (Monoid c, ShapeCanvas c) => c
ex = (draw $ Path [Point 10 10, Point 20 20]) `mappend` (draw $ Path [Point 10 20, Point 20 10])

最后,让我们考虑一些交互式的东西,它根据外部的东西决定绘制什么:

randomDrawing :: (MonadIO m, ShapeCanvas (m ()), TextCanvas (m ())) => m ()
randomDrawing = do
    index <- liftIO . getStdRandom $ randomR (0,2)
    choices !! index        
    where choices = [op, ex, return ()]

这不太有效,因为我们没有实例(Monad m) => Monoid (m ())以便ex将工作。我们可以使用Data.Semigroup.Monad从reducers包中,或者我们自己添加一个,但这会让我们陷入不连贯的情况。将 ex 更改为更容易:

ex :: (Monad m, ShapeCanvas (m ())) => m ()
ex = do
    draw $ Path [Point 10 10, Point 20 20]
    draw $ Path [Point 10 20, Point 20 10]

但类型系统无法完全弄清楚从第一个开始的单位draw与第二个的单位相同。我们这里的困难表明了额外的要求,我们一开始无法完全确定这些要求:

  • 画布扩展了现有的操作序列,提供绘图、写入文本等操作。

直接窃取http://www.haskellforall.com/2013/06/from-zero-to-cooperative-threads-in-33.html http://www.haskellforall.com/2013/06/from-zero-to-cooperative-threads-in-33.html:

  • 当您听到“指令序列”时,您应该想到:“monad”。
  • 当你用“扩展”来限定它时,你应该想到:“monad 转换器”。

现在我们意识到我们的画布实现很可能是一个 monad 转换器。我们可以回到我们的界面,并更改它,使每个类都是一个 monad 的类,类似于 Transformer 的MonadIOclass 和 mtl 的 monad 类。

重新审视界面

{-# LANGUAGE TypeFamilies #-}

module Data.Canvas (
    Point(..),
    Shape(..),
    Text(..),
    ShapeCanvas(..),
    TextCanvas(..),
    PaintCanvas(..)
) where

data Point = Point Int Int

data Shape = Dot Point
           | Box Point Point 
           | Path [Point]

data Text = Text Point String

class Monad m => ShapeCanvas m where -- c is the type of the Canvas
    draw :: Shape -> m ()

class Monad m => TextCanvas m where
    write :: Text -> m ()

class Monad m => PaintCanvas m where 
    type Paint m :: * -- (Paint c) is the type of Paint for canvases of type c
    load :: (Paint m) -> m ()

重新审视示例

现在我们所有的示例绘图操作都是一些未知的操作Monad m:

op :: (TextCanvas m) => m ()
op = write $ Text (Point 30 30) "Hi"

ex :: (ShapeCanvas m) => m ()
ex = do
    draw $ Path [Point 10 10, Point 20 20]
    draw $ Path [Point 10 20, Point 20 10]


randomDrawing :: (MonadIO m, ShapeCanvas m, TextCanvas m) => m ()
randomDrawing = do
    index <- liftIO . getStdRandom $ randomR (0,2)
    choices !! index        
    where choices = [op, ex, return ()]

我们还可以用油漆做一个例子。由于我们不知道会存在什么油漆,因此它们都必须从外部提供(作为示例的参数):

checkerBoard :: (ShapeCanvas m, PaintCanvas m) => Paint m -> Paint m -> m ()
checkerBoard red black = 
    do
        load red
        draw $ Box (Point 10 10) (Point 20 20)
        draw $ Box (Point 20 20) (Point 30 30)
        load black
        draw $ Box (Point 10 20) (Point 20 30)
        draw $ Box (Point 20 10) (Point 30 20)

实施

如果您可以让您的代码使用各种绘画来绘制点、框、线和文本而不引入抽象,我们可以更改它以实现第一部分中的接口。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

存在性反模式,如何避免 的相关文章

  • 在 Haskell 中编写 Web 应用程序的最简单方法是什么? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我想在我的项目中更多地使用 Haskell 并且我认为如果我可以开始将它用于 Web 应用程序 这将真正
  • 运营商部分应用

    如果我想在字符末尾添加一个空格以返回列表 如果我不传递任何参数 我将如何通过部分应用程序来完成此操作 还有类型是 space Char gt Char 由于使用 和 运算符出现 解析错误 我在末尾添加空格时遇到问题 到目前为止我所拥有的是
  • 如何在 Windows 7 中配置 cabal?

    我已经在Windows 7中安装了Haskell Platform 2012 我在控制台中编写cabal update我收到消息说有新版本的阴谋集团 我写的cabal install cabal install 安装完成后 它告诉我 cab
  • unsafeInterleaveIO 什么时候不安全?

    与其他不安全 操作不同 文档 http hackage haskell org packages archive base latest doc html System IO Unsafe html v unsafeInterleaveIO
  • 由于标志字节串 -lt-0_10_4,无法使用 Stack 构建 hello world 程序

    通过生成一个裸露的 hello world 项目 stack new myproject simple 每当我跑步时stack setup stack init or stack build我总是出现以下错误 Downloading lts
  • 移动列表中特定元素的简单函数

    我是 Haskell 的新手 我正在尝试弄清楚如何创建一个函数 shift Eq a gt a gt a gt Int gt a shift x h t z 输入 一个通用列表和一个相同类型的元素 x 前提条件 元素x存在于列表中 Outp
  • Haskell/GHC:使用相同模式匹配多个一元构造函数

    所以我正在尝试定义 TrieSet 数据类型 尽管我知道我不需要 http hackage haskell org package TrieMap module Temp where import Data Map data TrieSet
  • 在 Haskell 中将字节转换为 Int64s/Floats/Doubles

    我正在尝试解析 Haskell 中的二进制文件格式 Apple 的二进制属性列表格式 该格式所需的内容之一是将字节序列视为 a 无符号 1 2 或 4 字节整数 b 有符号 8 字节整数 c 32 位floats d 64 位doubles
  • Haskell,optparse-generic 的未命名命令行参数

    我在用着optparse 通用 https hackage haskell org package optparse generic解析名为的程序的命令行参数example 我有一个带有命名字段的数据类型 记录语法 例如 data Exam
  • 访问函数中的环境

    In main我可以读取我的配置文件 并将其提供为runReader somefunc myEnv正好 但somefunc不需要访问myEnv读者提供 链中的下一对也没有提供 需要 myEnv 中某些内容的函数是一个微小的叶函数 如何在不将
  • Haskell 中的前提条件检查有哪些选项

    这是一个简单的问题 我认为答案很复杂 一个非常常见的编程问题是函数返回某些内容 或者前置条件检查失败 在Java中 我会使用一些抛出异常的断言函数IllegalArgumentException在方法的开头 如下所示 method body
  • 在 Haskell 中增长数组

    我想在 Haskell 中实现以下 命令式 算法 给定一个序列对 e0 s0 e1 s1 e2 s2 en sn 其中 e 和 s 部分不一定是自然数不同的是 在每个时间步都会随机选择该序列的一个元素 例如 ei si 并根据 ei si
  • Haskell 类型系统的细微差别

    我一直在深入了解 haskell 类型系统的本质 并试图了解类型类的要点 我已经学到了很多东西 但我在下面的代码片段上遇到了困难 使用这些类和实例定义 class Show a gt C a where f Int gt a instanc
  • 为什么 Haskell 的默认字符串实现是一个字符链接列表?

    Haskell 默认值的事实String众所周知 实现在速度和内存方面都效率不高 据我所知 lists一般来说 在 Haskell 中实现为单链表 并且适用于大多数小型 简单数据类型 例如Int 这似乎不是一个好主意 但是对于String这
  • 将数据类型设置为 Kind * -> * 这不是函子

    布伦特 约尔吉类型分类百科全书 https www haskell org haskellwiki Typeclassopedia给出以下练习 举一个类型的例子 gt 不能将其制成 的实例Functor 不使用undefined 请告诉我什
  • 无点镜头创建不进行类型检查

    在函数中test 我遍历一个列表 从它的成员生成镜头 然后打印一些数据 当我使用有针对性的呼叫风格时 这会起作用 当我使其成为无点时 它无法进行类型检查 为什么会出现这种情况 我该如何解决这个问题 在我看来 GHC 并没有保留排名较高的信息
  • Haskell scala 互操作性

    我是 Scala 初学者 来自面向对象范式 在了解 Scala 的函数式编程部分时 我被引导到 Haskell 纯函数式编程语言 探索 SO 问题答案 我发现 Java Haskell 具有互操作性 我很想知道 Scala Haskell
  • 在 Haskell 中,为什么我必须在这段代码中使用美元符号?

    我仍在尝试破解这段代码 import Data Char groupsOf groupsOf n xs take n xs groupsOf n tail xs problem 8 x maximum map product groupsO
  • 持久 selectList 导致错误“无法将类型‘BaseBackend backend0’与‘SqlBackend’匹配”

    我遇到以下编译错误 Couldn t match type BaseBackend backend0 with SqlBackend arising from a use of runSqlite The type variable bac
  • Haskell 入门

    这个问题的答案是社区努力 help privileges edit community wiki 编辑现有答案以改进这篇文章 目前不接受新的答案或互动 几天来 我一直试图理解 Haskell 中的函数式编程范例 我通过阅读教程和观看截屏视频

随机推荐

  • 将对象转换为 JSON,忽略某些(私有)属性

    我一直在使用 Dean edwards base js http dean edwards name weblog 2006 03 base 将我的程序组织成对象 顺便说一句 base js 非常棒 如果您以前没有使用过它 无论如何 我的问
  • GPS/GIS 计算:根据运动/每小时预测未来位置的算法?

    寻找资源或算法来在导航应用程序中计算以下内容 如果我当前的 GPS 位置为 0 0 并且我以 15 英里 小时的速度前进 32 度 我如何计算 10 秒后我的位置 i e GPSCoordinate predictedCoord GPSCo
  • 耙子中止!错误:必须是数据库的所有者

    我正在研究迈克尔 哈特尔的优秀作品tutorial http ruby railstutorial org chapters modeling and viewing users one sec 3auser validations但是当尝
  • SD卡中的文件路径

    我的 SD 卡上有 mp3 文件 如何在选择文件时从SD卡获取文件的路径 动态 就像用户单击列表视图中的文件一样 其路径会进入变量以供使用 public class PlayListActivity extends ListActivity
  • Angular Cli Webpack 桶文件无法解析

    我刚刚将我的 Angular 2 项目从 SystemJS 切换到 Webpack 现在使用 angular cli webpack 在修复了一些小问题之后 最大的症结似乎是我的桶文件不再按预期得到解决 IE 我一直在进行进口工作 例如 i
  • hmailserver 错误 - 与数据库的连接不可用

    亲爱的先生 我尝试在我的家用电脑上制作网络邮件服务器 所以我下载并安装 hmailserver 但是当我尝试连接它时 出现错误 与数据库的连接不可用 当我看到日志文件时 它有以下错误 请帮助我 ERROR 2920 2013 10 02 0
  • @FXML 注释和 FXMLLoader 类未解析为 Java 11 和 JavaFX 11 中的类型

    早些时候 我的项目曾经在 Java 8 上运行 但现在我使用 Java 11 和 JavaFX 11 现在自 Java 11 以来 JavaFX 已与 Java 解耦 我还没有下载 JavaFX SDK 但在 pom xml 中添加了以下依
  • 如何在不向下移动行的情况下将表情符号插入到 NSTextView 中?

    如何将表情符号插入NSTextView不向下移动线 如果我将表情符号字符插入NSTextView 整条线将向下移动几个像素 如果我删除表情符号字符 它会移回到原来的位置 另一方面 如果我将表情符号插入NSTextField 即使该行中的文本
  • 使用 IAM 角色时 AWS boto3 InvalidAccessKeyId

    我使用预先指定的帖子 网址上传到 S3 并从 S3 下载 预签名的 url post 是使用 Lambda 函数中的 boto3 生成的 它是使用 zappa 部署的 当我添加我的AWS SECRET ACCESS KEY and AWS
  • 如何在 Python 中从文件夹外部访问模块? [复制]

    这个问题在这里已经有答案了 如何从另一个文件夹访问模块 这是文件结构
  • wordpress获取当前用户

    我的 WordPress 目录中有一个用于某些模板应用程序的目录 apacheWWW wordpress jtpc 在我的应用程序中我想要 WordPress 当前用户 ID 我可以在一页中执行此操作 但在另一页中出现错误 这就是我获取用户
  • 硒 while 循环不工作

    所以我开始掌握 while 循环的窍门 但是当在 selenium 代码上使用 while 循环时 我遇到了不足 我几乎尝试将一个任务复制 10 次 代码如下 Main py from selenium import webdriver f
  • python os.path.realpath 无法正常工作

    我有以下代码 os chdir os path dirname os path realpath file test path append os getcwd os chdir os path dirname os path realpa
  • mysql - 将行从一个表移动到另一个表

    如果我有两个结构相同的表 如何将一组行从一个表移动到另一个表 行集将从选择查询中确定 例如 customer table person id person name person email 123 tom email protected
  • 查询字符串由 Spring 框架解码

    我在这里遇到一个奇怪的问题 但不确定这是否是错误 该项目在Spring框架下运行 风景
  • XMLHttpRequest.upload.onprogress 不适用于 HTTPS

    Issue 我有一个页面 用户可以在其中上传文件FormData and an XMLHttpRequest 上传文件工作正常 但是upload onprogress is only上传时工作从 HTTP 连接 HTTPS HTTP 我已经
  • 数据库查询:如何计算多列的最大值

    假设我有下表 claim date person type 01 01 2012 adult 05 05 2012 adult 12 12 2012 adult 12 12 2012 adult 05 05 2012 child 05 05
  • 如何设置java库路径进行处理

    我正在使用 PDE 运行处理草图 但出现以下错误 验证java library path属性设置正确 你们中有人能告诉我如何解决这个问题吗 您可以在命令行上设置它 java Djava library path
  • 发布网站到IIS,服务器锁定dll文件

    我已经为这个问题苦苦挣扎了大约一周 我正在尝试使用 FTP 将我的 MVC 网站发布到我的 IIS 服务器 唯一的问题是 当我传输文件时 MVC 总是锁定一些 DLL 文件 System Web MVC 等 并在 10 分钟后释放它们 这个
  • 存在性反模式,如何避免

    下面的似乎有效 但看起来很笨拙 data Point Point Int Int data Box Box Int Int data Path Path Point data Text Text data Color Color Int I