如何在Haskell中“重载”美元运算符

3
首先,如果此问题已经被提出,我很抱歉,我只是找不到合适的英语术语来表达我的意思。
我想知道在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.Applicative Control.Arrow ,但多参数函数表现不佳。

到目前为止,我最好的尝试是:(,) <$> blackPixel <*> whitePixel


你看过uncurry :: (a -> b -> c) -> ((a, b) -> c)了吗? - mb21
哦,这很好,但是你如何将uncurry应用于元组中的每个函数?组合uncurry N次并不是真正的解决方案,因为我们仍然需要为每个函数复制相同的代码。 - Lilymonade
我不熟悉Xlib,也许其他人可以评论一下使用它的规范方法。但是,您的dsp函数可以像curryuncurry一样正常地作为普通的帮助函数(helper function)工作 - 您不需要为此创建一个类型类。也许可以参考http://learnyouahaskell.com/higher-order-functions#curried-functions。 - mb21
我喜欢你提出的那个类,尽管我觉得你把fundep x y -> f 而不是 f x -> y 或者 f -> x, f -> y 有点令人惊讶。 - leftaroundabout
@mb21 你提出的建议会让我创建与元组大小相同数量的 dspX 函数 ^^ @leftaroundabout 我对 fundep 完全不熟悉,也不知道该如何决定使用哪个 fundep。 - Lilymonade
2个回答

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)

这比我的类型类强大得多!并且教会了我如何在类中使用'type'。谢谢。简短的回答是:没有动态执行它的方法,我们总是要么为N-uple定义N个函数,要么创建一个类型类并实例化它N次。 - Lilymonade
@Shumush 是的,但是由于您可以嵌套元组(我的类可以很好地处理它们),因此您实际上不需要_N_次,您基本上只需要一对,可能还要添加三元组和四元组以方便使用和更好的性能,并从中构建任何更大的东西。 - leftaroundabout

1
你可以使用liftA函数族将多个函数应用于单个参数。嵌套它们以将多个函数应用于多个参数。因此:
(b, w, m) = liftA3 (liftA3 (,,)) blackPixel whitePixel defaultColormap display dScreen

那么我们需要与参数数量相同数量的 liftA3 吗?而且没有其他方法可以改变元组中函数的数量,除非使用另一个类似于 liftA4 ... liftAN 的函数。 - Lilymonade
@Shumush 如果你看一下liftA系列是如何定义的,你会发现当然还有另一种方式。=) - Daniel Wagner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接