我发现自己经常按照模式编写代码:
foo xs = map snd $ filter ((< 10).fst) $ zip xs [0..]
bar ys = map snd $ sortBy (compare `on` fst) $ zip ys [0..]
现在我想抽象出来
foo = indexesOf (filter (<10))
bar = indexesOf sort
indexesOf :: ([a] -> [a]) -> [a] -> [Int]
indexesOf f xs = map snd $ magick $ zip xs [0..] where
magick = undefined
如何执行 magick
?
您的类型签名不起作用。您需要能够为传递的函数提供元组列表,这意味着您必须使用更高级别的类型来强制它变为多态,或者在类型签名中明确提及元组。
如果没有这个,你不能“查看”函数,看看它如何重新排列列表的元素。实际上,给定您的类型签名,传递的函数可以对列表执行任何想要的操作,包括插入甚至不在那里的元素!
这是我使用更高级别类型工作的内容:
{-# LANGUAGE RankNTypes #-}
import Data.List (sortBy)
import Data.Ord (comparing)
indexesOf :: (forall b. (b -> a) -> [b] -> [b]) -> [a] -> [Int]
indexesOf f xs = map snd $ f fst $ zip xs [0..]
foo :: (Ord a, Num a) => [a] -> [Int]
foo = indexesOf (filter . ((< 10) .))
bar :: Ord a => [a] -> [Int]
bar = indexesOf (sortBy . comparing)
请注意,我还必须向传递的函数添加一个额外的参数,以告诉它如何从它正在处理的列表的元素中提取它关心的部分。如果没有这个,您将只能使用不检查列表元素的函数,例如 reverse
,这不会很有用。
示例在GHCi中运行:
> let xs = [42, 0, 7, 3, 12, 17, 99, 36, 8]
> foo xs
[1,2,3,8]
> bar xs
[1,3,2,8,4,5,7,0,6]
> indexesOf (const reverse) xs
[8,7,6,5,4,3,2,1,0]
很好的问题,但我怀疑没有这样的功能。
看到 定理免费!。
就像锤子说的那样,你需要传递明确采用元组的函数。
这是我的略微简化的版本,不需要RankNTypes(不可否认,这是对原始代码的一个非常好的改进):
import Data.List
import Data.Ord
indexesOf :: ([(a,Int)] -> [(a,Int)]) -> [a] -> [Int]
indexesOf f xs = map snd $ f $ zip xs [0..]
foo :: (Ord a,Num a) => [a] -> [Int]
foo = indexesOf $ filter $ (< 10) . fst
bar :: Ord a => [a] -> [Int]
bar = indexesOf $ sortBy $ comparing fst