Haskell 用于在两种类型之间转换的多态函数
是否可以使用Haskell98或扩展名编写以下函数Haskell 用于在两种类型之间转换的多态函数,haskell,polymorphism,Haskell,Polymorphism,是否可以使用Haskell98或扩展名编写以下函数f f :: (a -> b) -> (b -> a) -> c -> d where (c,d) :: (a,b) || (c,d) :: (b,a) 我试图创建一些通用函数来在任何两种类型a和b之间进行转换,但这两种类型都有点随意性,这使我感到困难。但是,它们是由第一个参数a->b立即设置的,所以我希望这是可能的。感谢您的指导 谢谢 [编辑] 在我看来,我的问题归结为haskell的类型系统是否支持或关键字。。
f
f :: (a -> b) -> (b -> a) -> c -> d
where (c,d) :: (a,b) || (c,d) :: (b,a)
我试图创建一些通用函数来在任何两种类型a
和b
之间进行转换,但这两种类型都有点随意性,这使我感到困难。但是,它们是由第一个参数a->b
立即设置的,所以我希望这是可能的。感谢您的指导
谢谢
[编辑]
在我看来,我的问题归结为haskell的类型系统是否支持
或关键字。。。由于我的函数的直观特征是:f:(a->b)->(b->a)->a->b或(a->b)->(b->a)->b->a
。我不完全确定您希望如何使用这样的函数,但我的直觉是否定的。假设我们有:
intToString :: Int -> String
intToString = show
stringToInt :: String -> Int
stringToInt = read
foo = f intToString stringToInt
什么是foo
类型?您必须能够将foo
应用于Int
和String
,这在haskell中通常是不可能的:您可以将foo
应用于其中一个,但不能同时应用于这两个
编辑:这并不是整个故事的全部--foo
肯定可以是foo::c->d
类型,但这将允许它应用于任何类型,而不仅仅是Int
或String
。当应用到错误的类型时,可能会抛出错误。即使这样做是可能的,但听起来也不是一个好主意。正如alecb解释的那样,这不能按照您的签名建议的方式进行,至少不能通过普通方式进行(需要在运行时检查类型)。您可能喜欢的另一种选择是和来自镜头<如果您的转换函数是全向和双射的,那么code>Iso
符合要求;如果其中一个函数是部分函数,则可以使用Prism
。下面是一个一次性的例子:
import Control.Lens
import Text.ReadMaybe
stringInt :: Prism' String Int
stringInt = prism' show readMaybe
假设我们有两个转换函数:
i2s :: Int -> String
s2i :: String -> Int
g=f i2s s2i的类型是什么?我们希望g1
成为一个合法的表达方式,也希望g“one”
。所以g
接受Int
和String
。这给我们留下了三个选择:
g
是一个完全多态的函数-也就是说,它的第一个参数的类型是a
。因此g True
和g[1,2,3]
也是合法的。如此泛型的函数与转换无关,您可以证明这类函数不能做任何有趣的事情(例如,类型为A->Int
的函数必须是常量)
g
需要某个类型类的参数-例如,show
是一个只接受有限类型组(包括Int
和String
)的函数。但是现在您的函数不是完全多态的-它只适用于这个受限类型组
我们可以为此任务定义新的数据类型:
data IntOrString = I Int | S String
-- the type signature is slightly different
f :: (Int -> String) -> (String -> Int) -> IntOrString -> IntOrString
f i2s s2i (I n) = S $ i2s n
f i2s s2i (S s) = I $ s2i S
虽然此函数在某些情况下可能有用,但它不再通用(它支持的唯一对是Int
和String
)
所以答案是-你可以用这个类型签名定义一个函数,但是它只适用于特定的类型或类型类
编辑:不,不支持a->b类型的函数,其中a是Int或String
。这需要支持子类型的动态语言或静态语言。我怀疑是否可以直接执行您想要的操作,但您可以使用一些变通方法:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
class Converter a b c where
($$) :: a -> b -> c
data Conv a b = Conv a b
instance Converter (Conv (a -> b) (b -> a)) a b where
($$) (Conv f _) = f
instance Converter (Conv (a -> b) (b -> a)) b a where
($$) (Conv _ g) = g
intToString :: Int -> String
intToString = show
stringToInt :: String -> Int
stringToInt = read
printString :: String -> IO ()
printString = print
printInt :: Int -> IO ()
printInt = print
main = do
let convert = Conv intToString stringToInt
printString $ convert $$ (12345 :: Int)
printInt $ convert $$ "12345"
我相信这已经足够接近你想要的了。唯一的区别是必须使用$$
运算符,但这是不可避免的
更新:您甚至可以删除特殊的Conv
结构并使用普通元组:
instance Converter (a -> b, b -> a) a b where
($$) (f, _) = f
instance Converter (a -> b, b -> a) b a where
($$) (_, g) = g
-- ...
printString $ (intToString, stringToInt) $$ (12345 :: Int)
printInt $ (intToString, stringToInt) $$ "12345"
我认为这更接近您的要求。您能否详细说明您希望如何使用这样的函数?以实现更干净的代码。大多数在类型a->b
之间转换的函数要么完全僵化,要么仅在一种类型上具有多态性。我可以用类型类实现后者,但我不确定这次如何实现。我想我可以使用多个参数类型类并声明实例ab
,也许…为什么?在任何给定的情况下,似乎您都知道应用转换int的方向,并且可以根据需要使用a2b
或b2a
:您使用convertAB
的唯一时间是您不确定在哪个方向进行转换时,这似乎是个坏主意。这与在镜头包中使用Isos非常相关。我建议您去看看,但请注意,这可能是一个非常复杂的库,需要学习。我不认为这会使代码更干净。一方面,你去掉了一个名字;另一方面,在使用函数读取代码时,需要更多的上下文来猜测函数在做什么。损失大于收益。这正是我想要的,但有两个问题:1。创建混合数据
类型2。输出(IntOrString
)似乎不会自动转换为原始类型:(Int
或String
)。对不起,这不是一个正确的同构。“Hello!”
的Int
表示形式是什么?一个方向上的对应关系只是局部的,这使得它成为一个棱镜而不是Iso
@Carl事实上,让它这样做是误导性的。我将示例改为使用Prism
。
instance Converter (a -> b, b -> a) a b where
($$) (f, _) = f
instance Converter (a -> b, b -> a) b a where
($$) (_, g) = g
-- ...
printString $ (intToString, stringToInt) $$ (12345 :: Int)
printInt $ (intToString, stringToInt) $$ "12345"