请考虑以下代码示例:
{-# 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")
(我的实际代码更精细;这是一个用于演示模式的最小简化案例。)
我不清楚为什么这里需要UndecidableInstances
- 类型参数a
在Bar a => Foo a
的两边出现一次,所以我希望事情“正常工作” 。我显然在这里遗漏了一些东西。但无论如何,有没有办法在不使用UndecidableInstances
的情况下执行此操作?
答案 0 :(得分: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")
您仍然需要声明类型是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)
或者,您可以通过使用普通函数替换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
如果它们适合您的情况,这些可能是最好的解决方案。
另一种选择是手动声明每个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")
现在main
打印一个空字符串。 Foo
和a
有两个[a]
个实例。后者更具体,因此它被foo "foo"
选中,因为字符串的类型为[Char]
,尽管你可能想要前者。所以现在你还需要写
instance Foo String where
foo = bar . baz
此时您可以完全省略Bar a => Foo a
个实例。
答案 1 :(得分:0)
除了上面的回答。来自Data.Traversable
库的base
模块中使用的政治很有吸引力。简而言之,在库中提供通用实例会迫使最终用户接受您的决定,这并不总是最好的事情。 Data.Traversable
包含foldMapDefault
等函数,它提供默认实现,但具体实现的决定仍由用户决定。