Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/html/71.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 如何处理多参数typeclass的函数,谁不需要typeclass的每种类型?_Haskell - Fatal编程技术网

Haskell 如何处理多参数typeclass的函数,谁不需要typeclass的每种类型?

Haskell 如何处理多参数typeclass的函数,谁不需要typeclass的每种类型?,haskell,Haskell,我定义了一个typeclass,它类似于一个接口,其中包含我的程序所需的一系列函数。不幸的是,它需要多个多态类型,但不是这个多参数类型类的每个函数都需要每个类型。GHC的不可还原类型困扰着我,我无法运行代码 一个简化的例子: {-# LANGUAGE MultiParamTypeClasses #-} class Foo a b where -- ... bar :: a -> () baz :: Foo a b => a -> () baz = bar

我定义了一个typeclass,它类似于一个接口,其中包含我的程序所需的一系列函数。不幸的是,它需要多个多态类型,但不是这个多参数类型类的每个函数都需要每个类型。GHC的不可还原类型困扰着我,我无法运行代码

一个简化的例子:

{-# LANGUAGE MultiParamTypeClasses #-}

class Foo a b where
    -- ...
    bar :: a -> ()

baz :: Foo a b => a -> ()
baz = bar
GHC说

可能的修复:添加修复这些类型变量的类型签名
如何为
b
执行此操作?特别是当我想保持
b
多态性时。只有
Foo
的一个实例应该定义这种类型。

这是不可能的

潜在的问题是多参数类型类依赖于每个类型参数。如果类中的特定定义没有使用每个类型参数,编译器将永远无法知道您所指的实例是什么,甚至无法指定它。考虑下面的例子:

class Foo a b where
    bar :: String -> IO a

instance Foo Int Char where
    bar x = return $ read x

instance Foo Int () where
    bar x = read <$> readFile x
使用该定义(需要
函数依赖性
扩展名),您告诉编译器对于
a
的任何特定选择,只有一个有效的
b
选择。甚至试图定义上述两个实例都将是一个编译错误


鉴于此,编译器知道它可以仅基于类型
a
选择
Foo
的实例来使用。在这种情况下,可以调用
bar

将其拆分为较小的类型类就足够了

{-# LANGUAGE MultiParamTypeClasses #-}

class Fo a => Foo a b where
    -- ...
    foo :: a -> b -> ()


class Fo a where
    bar :: a -> ()


baz :: Foo a b => a -> ()
baz = bar

假设您真的想为给定的
a
(因此不能像其他人提到的那样使用函数依赖项)使用多个实例,一种可能对您来说是正确的,也可能不正确的方法是使用标记有“幻影”类型的新类型,该类型仅用于指导类型选择。这包括:

{-# LANGUAGE MultiParamTypeClasses #-}

newtype Tagged t a = Tagged { unTagged :: a }   -- Also defined in the tagged package
                                                -- on Hackage

class Foo a b where
    bar :: Tagged b a -> ()

baz :: Foo a b => Tagged b a -> ()
baz = bar

然后,您将能够以这样一种方式包装您的值,即您可以提供一个显式类型注释来选择正确的实例。

当多参数类型类变得笨拙时,重构它们的另一种方法是使用扩展。与此类似,当您可以将类重新构造为只有一个参数(或至少更少的参数)时,这会很好地工作,而实例之间不同的其他类型是从实际的类参数计算出来的

通常我发现,每当我认为我需要一个多参数类型的类时,参数几乎总是一起变化,而不是独立变化。在这种情况下,选择一个作为“主要”并使用一些系统从中确定其他系统要容易得多。函数依赖项和类型族一样可以做到这一点,但许多人发现类型族更容易理解

下面是一个例子:

{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

class Glue a where 
    type Glued a
    glue :: a -> a -> Glued a


instance Glue Char where 
    type Glued Char = String
    glue x y = [x, y]


instance Glue String where 
    type Glued String = String
    glue x y = x ++ y


glueBothWays :: Glue a => a -> a -> (Glued a, Glued a)
glueBothWays x y = (glue x y, glue y x)
上面声明了一个类
Glue
,该类的类型可以与
Glue
操作粘合在一起,并且具有作为“粘合”结果的相应类型

然后我宣布了几个例子
Glued Char
String
Glued String
也是
String


最后,我编写了一个函数来展示当您在使用的
Glue
实例上进行多态时如何使用
Glued
;基本上,你在你的类型签名中“调用”
Glued
;这意味着
gluebotways
不“知道”粘贴的a是什么类型,但它知道它如何对应于
a
。如果你知道你在粘字符,但不想硬编码,
Glued Char=String

这是否意味着如果我想要类似的功能,我需要将这些函数放入更小的合适的类型类中,然后让更高的类型类依赖于它?如果你试图将它们都合并到一个超类中,你会遇到同样的问题。老实说,听起来你好像过度使用了课程。有可能你不是,但至少同样有可能有一个更简单的方法。嗯,我不知道。我经常使用类型类来抽象IO,这样它就可以依赖于IO之前的另一层。我不应该这样做吗?@Vektorweg:Control.Monad.IO.Class和
liftIO
为此有什么问题?如果您使用异常,可能会使用
monad控件
异常
)进行扩充。重构您的类型类以避免不依赖于整个上下文的函数。无论如何,多参数类型类都是相当笨拙的——如果可以避免,就这样做。只有在定义了类似于
instance Foo Int b
的内容时,这才可能起作用,在这种情况下,编译器将能够仅基于第一个参数选择一个实例。如果是这种情况,您可以简单地使用
Fo
而不是
Foo
,因为您的所有实例都是相同的。
{-# LANGUAGE TypeFamilies, FlexibleInstances #-}

class Glue a where 
    type Glued a
    glue :: a -> a -> Glued a


instance Glue Char where 
    type Glued Char = String
    glue x y = [x, y]


instance Glue String where 
    type Glued String = String
    glue x y = x ++ y


glueBothWays :: Glue a => a -> a -> (Glued a, Glued a)
glueBothWays x y = (glue x y, glue y x)