如何在具有不同输出类型的两个相同Haskell函数中删除代码重复?

时间:2018-05-08 19:24:52

标签: haskell

我继续尝试使用Haskell和GUI https://github.com/bigos/cairo-example/blob/1c4448a936031b0e5b66b77027be975d040df01b/src/Main.hs 我遇到了另一个问题。

我有两个相同的函数,结果类型不同:

getWidgetSize :: Gtk.DrawingArea -> Render (Int, Int)
getWidgetSize widget = do
  width'  <- fromIntegral <$> Gtk.widgetGetAllocatedWidth widget
  height' <- fromIntegral <$> Gtk.widgetGetAllocatedHeight widget
  return (width', height')

getWidgetSize2 :: Gtk.DrawingArea -> IO (Int, Int)
getWidgetSize2 widget = do
  width'  <- fromIntegral <$> Gtk.widgetGetAllocatedWidth widget
  height' <- fromIntegral <$> Gtk.widgetGetAllocatedHeight widget
  return (width', height')

在此功能中使用了一个

updateCanvas :: Gtk.DrawingArea -> Model -> Render ()

,另一个在主函数中使用。

是否可以删除代码重复?

溶液

根据Vora的建议,我使用了以下内容:

getWidgetSize :: Gtk.DrawingArea -> IO (Int, Int)
getWidgetSize widget = do
  width'  <- fromIntegral <$> Gtk.widgetGetAllocatedWidth widget
  height' <- fromIntegral <$> Gtk.widgetGetAllocatedHeight widget
  return (width', height')

updateCanvas :: Gtk.DrawingArea -> Model -> Render ()
updateCanvas canvas model = do
  size <- liftIO (getWidgetSize canvas)

2 个答案:

答案 0 :(得分:5)

最简单的方法是使用RenderMonadIO这一事实,这意味着您只需制作一个IO类型的定义,并在{}}内部调用liftIO Render Monad。

编辑: 根据Carl的建议,由于widgetGetAllocatedWidth / widgetGetAllocatedHeightMonadIO要求约束,您还可以制作

getWidgetSize :: MonadIO m => Gtk.DrawingArea -> m (Int, Int)
getWidgetSize widget = do
  width'  <- liftIO $ fromIntegral <$> Gtk.widgetGetAllocatedWidth widget
  height' <- liftIO $ fromIntegral <$> Gtk.widgetGetAllocatedHeight widget
  return (width', height')

然后在两个场景中都可以使用。这大致相当,但它允许从任何MonadIO上下文以相同的方式调用它。

Edit2 :(因为更多编辑总是更好的人) 编辑3:因为有时候你会从太近的地方看一些东西,而你会错过它 为清晰起见,我在liftIO区域内移动的do也可以删除,以便对字段进行整理:

getWidgetSize :: MonadIO m => Gtk.DrawingArea -> m (Int, Int)
getWidgetSize widget = do
  width'  <- fromIntegral <$> Gtk.widgetGetAllocatedWidth widget
  height' <- fromIntegral <$> Gtk.widgetGetAllocatedHeight widget
  return (width', height')

答案 1 :(得分:3)

是的,我们可以推断出类型:

首先我们可以去掉do - 块:

getWidgetSize widget = do
  width'  <- fromIntegral <$> widgetGetAllocatedWidth widget
  height' <- fromIntegral <$> widgetGetAllocatedHeight widget
  return (width', height')

是语法糖:

getWidgetSize widget = fromIntegral <$> widgetGetAllocatedWidth widget >>= \w' -> (
    fromIntegral <$> widgetGetAllocatedHeight widget >>= \h' -> (
        return (w', h')
    )
)

首先我们假设getWidgetSize的类型为a -> b,我们假设为widget :: a(我们稍后会弄清ab等等。

接下来我们看到有一个电话widgetGetAllocatedWidth widget,这意味着(HasCallStack, MonadIO m, IsWidget a)成立,而widgetGetAllocatedWidth widget的回复类型为m Int32,所以我们现在知道:< / p>

getWidgetSize :: (HasCallStack, MonadIO m, IsWidget a) => a -> b
widget :: (HasCallStack, MonadIO m, IsWidget a) => a
widgetGetAllocatedWidth widget :: (HasCallStack, MonadIO m, IsWidget a) => m Int32

现在我们执行(<$>) fromIntegral (widgetGetAllocatedWidth widget),这意味着:

(<$>) :: Functor f => (c -> d) -> f c -> f d
fromIntegral :: (Integral c, Num d) => c -> d
widgetGetAllocatedWidth widget :: (HasCallStack, MonadIO m, IsWidget a) => m Int32

因此,我们得出结论f ~ mc ~ Int32,以及:

(<$>) fromIntegral (widgetGetAllocatedWidth widget) :: (HasCallStack, MonadIO m, IsWidget a, Num d, Functor m) => m d

第二部分fromIntegral <$> widgetGetAllocatedHeight widget也是如此,因为widgetGetAllocatedHeight的签名与widgetGetAllocatedWidth相同,我们得出结论:

(<$>) fromIntegral (widgetGetAllocatedHeight widget) :: (HasCallStack, MonadIO n, IsWidget a, Num e, Functor n) => n e

请注意,此处n 本身与m相同(稍后会发现它是),而e是< em> not 本身与d相同。

现在我们有三个部分的功能(我们将前两部分缩短为fg,这样我们的函数看起来像:

getWidgetSize widget = f widget >>= (\w -> g widget >>= (\h -> return (w, h)))

使用

f :: (HasCallStack, MonadIO m, IsWidget a, Num e, Functor m) => m d
g :: (HasCallStack, MonadIO n, IsWidget a, Num e, Functor n) => n e

由于(>>=)函数具有签名(>>=) :: Monad m => m a -> (a -> m b) -> m b,因此我们知道w变量的类型为Num d => d

我们进一步知道g >>= \h -> return (w, h)部分的类型为:

g >>= \h -> return (w, h) :: (HasCallStack, MonadIO n, IsWidget a, Num d, Num e, Functor n) => n (d, e)

由于我们将f\w -> g >>= \h -> return (w, h)“绑定”,我们知道m ~ n。所以我们得出的结论是结果的类型是:

(HasCallStack, MonadIO m, IsWidget a, Num d, Num e, Functor m) => m (d, e)

现在我们可以删除一些重复:因为MonadIO被定义为:

class Monad m => MonadIO m where
    -- ...

从那以后:

class Applicative m => Monad m where
    -- ...
(...)
class Functor m => Applicative m where
    -- ...

我们知道MonadIO m实际上暗示Functor m,因此我们可以删除Functor m,因此实施的最终类型为:

getWidgetSize :: (HasCallStack, MonadIO m, IsWidget a, Num b, Num c) => a -> m (b, c)
getWidgetSize widget = do
  width'  <- fromIntegral <$> widgetGetAllocatedWidth widget
  height' <- fromIntegral <$> widgetGetAllocatedHeight widget
  return (width', height')