Как включить функцию [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?

Ответ 1

Подпись вашего типа не будет работать. Вы должны уметь передать переданной функции список кортежей, что означает, что вам нужно использовать типы более высокого ранга, чтобы заставить их быть полиморфными или явно упоминать кортежи в вашей сигнатуре типа.

Без этого вы не можете "заглянуть внутрь" функции, чтобы увидеть, как она упорядочивает элементы списка. Фактически, с учетом вашей сигнатуры типа переданная функция могла делать все, что захотела в списке, включая вставку элементов, которые даже не были там начаты!

Здесь я должен работать с использованием типов более высокого ранга:

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

Ответ 2

Отличный вопрос, но я подозреваю, что такой функции нет. См. Теоремы бесплатно!. Как говорит молот, вам нужно передать функции, которые явно принимают кортежи.

Вот моя слегка упрощенная версия, которая не требует 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