返回两种类型的相同类型类中的一种

时间:2013-05-11 17:46:28

标签: haskell types typeclass

我有以下类型

class MyClass c where
  aFunction :: c -> Bool

和两个不同数据类型的两个实例

data MyDataType1 = MyDataType1

instance MyClass MyDataType1 where
  aFunction c = True

data MyDataType2 = MyDataType2

instance MyClass MyDataType2 where
  aFunction c = False

我想编写一个函数,它接受类型类MyClass的两个参数(它可能是相同的数据类型,也可能是不同的并返回其中一个。我正在努力为此设计类型签名我想我可能采取了错误的做法。

这是正确的吗?如果不是我应该使用什么呢?

chooseOne :: (MyClass a, MyClass b) => a -> b -> ?
chooseOne x y = if (aFunction x) then x else y

4 个答案:

答案 0 :(得分:11)

您的返回值可以是任何一种类型,因此编译器会抱怨,除非您为两者使用相同的类型,给出

chooseOne :: (MyClass a, MyClass a) => a -> a -> a

这不是你的意思。

要将两种可能不同的类型合并为一种,您可以使用Either数据类型:

data Either a b = Left a | Right b

所以你会有

chooseOne :: (MyClass a, MyClass b) => a -> b -> Either a b
chooseOne x y = if (aFunction x) then Right x else Left y

但我宁愿写那个

chooseOne :: (MyClass a, MyClass b) => a -> b -> Either a b
chooseOne x y | aFunction x = Right x 
              | otherwise   = Left y

答案 1 :(得分:5)

您在Haskell中编写的函数是不可能的---返回类型必须在编译时修复并且已知。因此,要写出您感兴趣的内容,需要Either

chooseOne :: (MyClass a, MyClass b) => a -> b -> Either a b
chooseOne x y = if (aFunction x) then Left x else Right y

最终,即使在动态语言中,您也必须拥有一些同样处理ab类型的代码。这“消除”Either并体现在函数Data.Either.either

either :: (a -> c) -> (b -> c) -> Either a b -> c
either f _ (Left a)  = f a
either _ g (Right b) = g b

对于您的特定情况,由于ab都是MyClass的实例,感觉我们可以做出稍微方便的消除功能

eitherOfMyClass :: (MyClass a, MyClass b) => (a -> b) -> Either a a' -> b
eitherOfMyClass f (Left a)   = f a
eitherOfMyClass f (Right a') = f a'

但实际上这不会打字!如果仔细查看可能找到问题的类型 - 我们传入的处理函数专用于a,因此无法应用于Right一侧{ {1}}类型Either。因此,我们需要使用bforall启用的扩展程序。

LANGUAGE RankNTypes

这可以确保您传递到{-# LANGUAGE RankNTypes #-} eitherOfMyClass :: (MyClass a, MyClass b) => (forall x. MyClass x => (x -> c)) -> Either a b -> c eitherOfMyClass f (Left a) = f a eitherOfMyClass f (Right b) = f b 的任何函数feitherOfMyClass的任何实例都是一般的,因此可以应用于MyClassab中的{1}}。

答案 2 :(得分:1)

(另见“Haskell Antipattern: Existential Typeclass”。)

data MyType = MyType { aFunction :: Bool }

chooseOne :: MyType -> MyType -> MyType
chooseOne x y = if aFunction x then x else y

警告:你的真实MyClass可能不够简单,无法使其发挥作用。

n.b。如果您可以使用OOP语言将其编写为类,那么可以使用此技术。 OOP构造函数转换为返回MyType值的独立函数。

  • 所有类函数都必须将类值作为参数。所以例如如果您的真实班级为Monoid,则mempty :: Monoid a => a不会采用参数,因此不适合此框架。
  • 您的类函数不能采用必须具有相同基础类型的多个参数。再例如如果您的真实班级为Monoidmappend :: Monoid a => a -> a -> a有两个参数,并且它们可能是实现Monoid的任何类型,但它们必须都是相同的类型实现Monoid; mappend也不适合此框架。

答案 3 :(得分:1)

你可以随时向后:而不是返回类型x或类型y,你可以接受两个函数作为输入并执行一个或另一个取决于什么你想“回归”:

chooseOne :: (x -> z) -> (y -> z) -> x -> y -> z
chooseOne f1 f2 x y = if aFunction x then f1 x else f2 y

请注意,如果您执行chooseOne Left Right,那么现在您已经拥有了基于Either的解决方案,其他人已经建议了。您也可以执行chooseOne show show之类的操作,以返回String作为结果。

这种方法是更好还是更差取决于你为什么要首先构建这个类(即你的程序尝试做什么)......