Haskell子类型类需要UndecidableInstances?

Ore*_*iki 9 haskell typeclass

请考虑以下代码示例:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-} -- Is there a way to avoid this?

-- A generic class with a generic function.
class Foo a where
  foo :: a -> a

-- A specific class with specific functions.
class Bar a where
  bar :: a -> a
  baz :: a -> a

-- Given the specific class functions, we can implement the generic class function.
instance Bar a => Foo a where
  foo = bar . baz

-- So if a type belongs to the specific class...
instance Bar String where
  bar = id
  baz = id

-- We can invoke the generic function on it.
main :: IO ()
main =
  putStrLn (foo "bar")
Run Code Online (Sandbox Code Playgroud)

(我的实际代码更精细;这是一个用于演示模式的最小简化案例.)

我不清楚为什么UndecidableInstances需要这里 - 类型参数a在两侧出现一次Bar a => Foo a,所以我期望事情"正常工作".我显然在这里遗漏了一些东西.但无论如何,有没有办法在不使用的情况下做到这一点UndecidableInstances

Joh*_*n L 8

您可以采取一些方法; 我认为你没有提供足够的背景来确定哪个是最合适的.如果您使用的是GHC-7.4,则可能需要尝试DefaultSignatures扩展.

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DefaultSignatures #-}

-- A generic class with a generic function.
class Foo a where
  foo :: a -> a
  default foo :: Bar a => a -> a
  foo = bar . baz

-- A specific class with specific functions.
class Bar a where
  bar :: a -> a
  baz :: a -> a

instance Bar String where
  bar = id
  baz = id

instance Foo String

main :: IO ()
main =
  putStrLn (foo "bar")
Run Code Online (Sandbox Code Playgroud)

您仍然需要声明类型是实例Foo,但您不需要重复方法声明,因为将使用默认实现.

另一种相当轻量级的方法是使用newtype.如果您有需要Foo实例的函数,则可以Bar在newtype中包装实例.

newtype FooBar a = FooBar { unFooBar :: a }

instance Bar a => Foo (FooBar a) where
    foo = FooBar . bar . baz . unFooBar

-- imported from a library or something...
needsFoo :: Foo a => a -> b

myFunc = needsFoo (FooBar someBar)
Run Code Online (Sandbox Code Playgroud)

或者,您可以通过替换foo正常函数或为Bar实例创建专用版本来实现:

-- if every `Foo` is also a `Bar`, you can just do this.  No need for `Foo` at all!
foo :: Bar a => a -> a
foo = bar . baz

-- if most `Foo`s aren't `Bar`s, you may be able to use this function when you have a `Bar`
fooBar :: Bar a => a -> a
foo = bar . baz
Run Code Online (Sandbox Code Playgroud)

如果它们适合您的情况,这些可能是最好的解决方案.

另一种选择是Foo手动声明每个实例.虽然可能存在许多不同的可想象的实例,但代码库中只有少量实际使用的实例是相当常见的.如果这是真的,那么只需写出你需要的3个或4个实例,而不是尝试实现更通用的解决方案,可能就不那么重要了.

作为最后的手段,您可以使用类似原始代码的东西,但是您还需要OverlappingInstances使其工作(如果您不需要OverlappingInstances,那么您不需要Foo课程).这是允许GHC在有多个可用匹配时选择"最具体的实例"的扩展.这或多或少会起作用,尽管你可能达不到预期.

class Foo a where
  foo :: a -> a

class Bar a where
  bar :: a -> a
  baz :: a -> a

instance Bar String where
  bar = id
  baz = id

instance Bar a => Foo a where
  foo = bar . baz

instance Foo [a] where
  foo _ = []

main :: IO ()
main =
  print (foo "foo")
Run Code Online (Sandbox Code Playgroud)

现在main打印一个空字符串.有两个Foo实例,for a[a].后者更具体,所以它被选中,foo "foo"因为字符串有类型[Char],尽管你可能想要前者.所以现在你也需要写

instance Foo String where
  foo = bar . baz
Run Code Online (Sandbox Code Playgroud)

此时你也可以完全抛弃Bar a => Foo a实例.