【问题标题】:How to "overload" dollar operator in Haskell如何在 Haskell 中“重载”美元运算符
【发布时间】:2019-03-13 22:31:33
【问题描述】:

首先,很抱歉,如果这个问题已经被问到,我根本找不到合适的英文术语来表达我的意思。

我想知道 Haskell 中是否有任何类型类表示函数应用程序,以便为不同的数据类型定义多种行为。

使用Graphics.X11.Xlib 包,我遇到了许多要求完全相同参数的不同函数。所以我的想法是将这些函数打包成一个元组(因为它们的返回类型不一样),并一次性将它们全部提供参数。像这样:

import Graphics.X11.Xlib

main = do
  display <- openDisplay ":0"
  let dScreen = defaultScreen display
      (black, white, cMap) =
      -- here is where the "parameter dispatch" is needed
      (blackPixel, whitePixel, defaultColormap)  display dScreen

  -- computation
  return ()

我没有找到任何东西,所以我决定创建这种类型类:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-}
import Graphics.X11.Xlib

class Dispatch f x y | x y -> f where
  dsp :: f -> x -> y
instance Dispatch (a -> b, a -> c, a -> d) a (b, c, d) where
  dsp (f, g, h) x = (f x, g x, h x)

main = do
  display <- openDisplay ":0"
  let dScreen = defaultScreen display
      (black, white, cMap) =
      -- here is where the "parameter dispatch" is needed
      (blackPixel, whitePixel, defaultColormap) `dsp` display `dsp` dScreen

  -- computation
  return ()

它工作得很好,并且通过为不同的元组大小乘以实例,可以根据需要的值简单地从“函数元组”中添加或删除函数,并且代码仍然可以编译。

但是如果没有这种解决方法,有没有办法做到这一点? 我尝试使用Control.ApplicativeControl.Arrow,但多个参数函数最终效果不佳。

到目前为止我最好的尝试是:(,) &lt;$&gt; blackPixel &lt;*&gt; whitePixel

【问题讨论】:

  • 你看过uncurry :: (a -&gt; b -&gt; c) -&gt; ((a, b) -&gt; c)
  • 哦,这很好,但是如何将uncurry 应用于元组中的每个函数?编写uncurry N 次并不是真正的解决方案,因为我们仍然需要为每个函数复制相同的代码。
  • 我不熟悉 Xlib,所以也许其他人可以评论一下使用它的规范方法是什么。但是您的dsp 函数可以在某个地方作为普通的辅助函数正常工作(例如curryuncurry)——您不需要一个类型类。也许看到learnyouahaskell.com/higher-order-functions#curried-functions
  • 我喜欢你提议的那门课,虽然我发现你会用x y -&gt; f而不是f x -&gt; y,甚至f -&gt; x, f -&gt; y来代替f -&gt; x, f -&gt; y
  • @mb21 你的建议会让我创建与元组大小一样多的dspX 函数^^ @leftaroundabout 我对fundep完全陌生,我真的不知道如何决定哪个Fundep 使用。

标签: haskell tuples dollar-sign


【解决方案1】:

这不是一个真正的答案,但您可以通过以下方式将此想法扩展到真正的“$ 运算符的重载”:

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

import Prelude hiding (($))

infixr 0 $

class Dispatch f where
  type Argument f :: *
  type Result f :: *
  ($) :: f -> Argument f -> Result f

instance Dispatch (a -> b) where
  type Argument (a->b) = a
  type Result (a->b) = b
  f $ x = f x

instance (Dispatch x, Dispatch y, Argument x ~ Argument y)
            => Dispatch (x,y) where
  type Argument (x,y) = Argument x
  type Result (x,y) = (Result x, Result y)
  (f,g) $ a = (f $ a, g $ a)

instance ( Dispatch x, Dispatch y, Dispatch z
         , Argument x ~ Argument y, Argument y ~ Argument z )
            => Dispatch (x,y,z) where
  type Argument (x,y,z) = Argument x
  type Result (x,y,z) = (Result x, Result y, Result z)
  (f,g,h) $ a = (f $ a, g $ a, h $ a)

main :: IO ()
main = do
   print $ ((\x -> ((2*x+),(3*x+)), (**), logBase) $ 2) $ 4
((8.0,10.0),16.0,2.0)

【讨论】:

  • 这比我的 typeclass 强大多了!并教我如何在课堂上使用̀type`。谢谢你。这么简短的回答:没有办法动态地做到这一点,我们总是必须为 N-uple 定义 N 个函数,或者创建一个类型类并实例化它 N 次。
  • @Shumush 是的,但是由于您可以嵌套元组(我的班级可以很好地处理这些),因此您实际上并不需要 N 次,因此您基本上只需要 pair 并且可能添加三元组和四倍是为了方便和更好的性能,并从中建立更大的东西。
【解决方案2】:

您可以使用liftA 系列函数将多个函数应用于单个参数。嵌套它们以将许多函数应用于许多参数。所以:

(b, w, m) = liftA3 (liftA3 (,,)) blackPixel whitePixel defaultColormap display dScreen

【讨论】:

  • 所以我们需要和参数一样多的liftA3?如果没有像 liftA4 ... liftAN 之类的另一个函数,就没有其他方法可以更改元组中的函数数量。
  • @Shumush 如果您看一下liftA 系列是如何定义的,您会发现当然还有另一种方式。 =)
猜你喜欢
  • 2013-01-02
  • 2021-08-16
  • 2011-10-14
  • 2017-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多