考虑以下代码:
t1 :: [Int] -> (Int,String)
t1 xs = (sum xs,show $ length xs)
t2 :: [Int] -> (Int,String)
t2 xs = (length xs, (x -> '?') <$> xs)
t3 :: [Int] -> (Char,String)
t3 (x:xs) = ('Y',"1+" ++ (show $ length xs))
t3 [] = ('N',"empty")
这三个函数的类型只是部分变化——它们完全可用,而不需要知道它们生成的元组的第一个组件的类型。这意味着我可以在不需要参考的情况下对它们进行操作
fnListToStrs vs fs = (x -> snd $ x vs) <$> fs
将这些定义加载到GHCi中,所有三个函数都作为fnListToStrs
的参数独立工作,事实上,我可以传入一个包含t1和t2的列表,因为它们具有相同的类型:
*Imprec> fnListToStrs [1,2] [t1,t2]
["2","??"]
*Imprec> fnListToStrs [1,2] [t3]
["1+1"]
但我不能同时通过所有3个,即使类型的差异实际上与执行的计算无关:
*Imprec> fnListToStrs [1,2] [t1,t2]
["2","??"]
*Imprec> fnListToStrs [1,2] [t3]
["1+1"]
我有一种感觉,使这项工作与存在类型或非指示类型有关,但在使用我希望fnListToStrs能够接受的类型声明时,这两个扩展都不适用于我,即:
fnListToStrs :: [Int] -> [forall a.[Int]->(a,String)] -> [String]
有其他方法可以做到这一点吗?
存在论是正确的,而不是不确定的。哈斯克尔没有存在论,除非通过显式包装。。。
{-# LANGUAGE GADTs #-}
data SomeFstRes x z where
SFR :: (x -> (y,z)) -> SomeFstRes x z
> fmap ((SFR f) -> snd $ f [1,2]) [SFR t1, SFR t2, SFR t3]
["2","??","1+1"]
但是,这真的有点没用。由于无论如何都不可能对第一个结果做任何事情,因此更明智的做法是立即将其丢弃,并将剩余的函数放在一个简单的单态列表中:
> fmap ($[1,2]) [snd . t1, snd . t2, snd . t3]
["2","??","1+1"]
任何将这些函数放入列表的方法都需要以某种方式"包装"它们中的每一个。最简单的包装就是
wrap :: (a -> (b, c)) -> a -> c
wrap f = snd . f
事实上,还有其他方法来包装这些(尤其是使用存在类型),但您没有提供任何信息来表明在您的应用程序中,这些方法中的任何一种都会比这个最简单的版本稍微好一点。
这里有一个更复杂的东西可能有意义的例子。假设你有
data Blob a b = Blob [a -> b] [a]
现在,假设您想要列出类型为Blob a b
的值,这些值都具有相同的b
类型,但可能具有不同的a
类型。实际上,将每个函数应用于每个参数可能会导致一个庞大的潜在结果列表,因此编写是有意义的
data WrapBlob b where
WrapBlob :: Blob a b -> WrapBlob b
现在,您可以列出列表并推迟决定将哪个函数应用于哪个参数,而无需付出高昂的代价。