问题 如何让函数[a] - > [a]对[(a,Int)]进行操作?


我发现自己经常按照模式编写代码:

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


11625
2018-02-17 08:11


起源

......我真的不清楚你要做什么。 - Louis Wasserman
比方说,我有一个功能在列表上工作 sort。现在,而不是排序列表,我想要回来 索引 的元素。例如。对于 [5.0, 8.0, 7.0] 我不想要 [5.0. 7.0, 8.0] 但 [0,2,1]。 - Landei


答案:


您的类型签名不起作用。您需要能够为传递的函数提供元组列表,这意味着您必须使用更高级别的类型来强制它变为多态,或者在类型签名中明确提及元组。

如果没有这个,你不能“查看”函数,看看它如何重新排列列表的元素。实际上,给定您的类型签名,传递的函数可以对列表执行任何想要的操作,包括插入甚至不在那里的元素!

这是我使用更高级别类型工作的内容:

{-# 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]

11
2018-02-17 08:32



我想你正在回答标题中的问题。 - Rafael Caetano
(继续)......但在给定的例子中,函数实际上是 :: (Ord a) => [a] -> [a]。所以你可以“看看里面”。 - Rafael Caetano
@RafaelCaetano:你仍然需要选择一个不同的 a,通过更高级别的类型或通过明确提及类型签名中的元组。您 可以 制作类型签名 indexOf :: (forall b. Ord b => [b] -> [b]) -> [a] -> [Int]然而,这只适用于 sort 例如,而不是那个 filter,因为该功能只能与彼此比较元素。它无法与之进行比较 10。 - hammar
我懂了。我没想过,谢谢你的解释。 - Rafael Caetano
我认为签名 indexesOf :: (forall b . [(a,b)] -> [(a,b)]) -> [a] -> [Int] 在mnish的实现之上更容易理解并且同样表现良好。用户必须写 fst 明确而不是延迟组合,所以这不是更高阶,更多冗余,更容易使用。如果我们有一个重用这个概念的函数集合,你的类型会更有意义 reordering from projection。 - gasche


很好的问题,但我怀疑没有这样的功能。 看到 定理免费!。 就像锤子说的那样,你需要传递明确采用元组的函数。

这是我的略微简化的版本,不需要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

4
2018-02-17 09:21



这是正确的,虽然更高级别的类型会给你稍微强一些的保证,因为函数不会弄乱整数。它所能做的就是重新排序,复制和删除列表中的现有项目。 - hammar
确实如此。此外,当一个函数具有类型a - > Int的函数时,它必须是微不足道的,即常量(或未定义)。 - mnish