我有以下类型
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
答案 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
最终,即使在动态语言中,您也必须拥有一些同样处理a
和b
类型的代码。这“消除”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
对于您的特定情况,由于a
和b
都是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
。因此,我们需要使用b
,forall
启用的扩展程序。
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
的任何函数f
对eitherOfMyClass
的任何实例都是一般的,因此可以应用于MyClass
和a
您b
中的{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
不会采用参数,因此不适合此框架。Monoid
,mappend :: 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
作为结果。
这种方法是更好还是更差取决于你为什么要首先构建这个类(即你的程序尝试做什么)......