Как можно определить такие операции, как map, filter и reverse, в терминах редукции?

В этой записи блога "CSP и преобразователи в JavaScript", автор заявляет:

Во-первых, мы должны понимать, что многие операции с массивами (или другими коллекциями), такие как map, filter и reverse, могут быть определены в терминах reduce.

У меня вопрос: Как можно определить такие операции, как map, filter и reverse, с точки зрения редукции? Не могли бы вы привести примеры в Clojure?


person hawkeye    schedule 03.09.2014    source источник
comment
Это странное место для использования concat, в уменьшении вы выбираете свой собственный базовый случай, поэтому выберите тот, который имеет необходимое поведение conj.   -  person noisesmith    schedule 04.09.2014


Ответы (3)


Это верно, если мы не заботимся о лени. В Clojure map и filter ленивы, а сокращение нетерпеливо. Мало того, что reverse не ленивый, стандартное определение использует сокращение. По модулю лени мы можем получить эквивалентные результаты для остальных:

user> (defn eager-map [f coll]
        (reduce (fn [acc v] (conj acc (f v)))
        []
        coll))
#'user/eager-map
user> (eager-map inc (range 10))
[1 2 3 4 5 6 7 8 9 10]

user> (defn eager-filter [f coll]
         (reduce (fn [acc v] (if (f v) (conj acc v) acc))
                 []
                 coll))
#'user/eager-filter
user> (eager-filter even? (range 10))
[0 2 4 6 8]

user> (defn eager-reverse [coll]
         (reduce conj () coll))
#'user/eager-reverse
user> (eager-reverse (range 10))
(9 8 7 6 5 4 3 2 1 0)
person noisesmith    schedule 03.09.2014

Как можно определить такие операции, как map, filter и reverse, в терминах редукции?

Это известно как "универсальность сгиба". fold ниже - естественная складка (foldr):

Очевидно, что через складку можно описать различные редукции:

sum :: [Int] -> Int           product :: [Int] -> Int
sum = fold (+) 0              product = fold (*) 1

and :: [Bool] -> Bool         or :: [Bool] -> Bool
and = fold (&&) True          or = fold (||) False

Но мы также можем написать неочевидные сокращения:

-- appending a list
(++) :: [a] -> [a] -> [a]
(++ ys) = fold (:) ys

-- reversing a list
reverse :: [a] -> [a]
reverse = fold (\x xs -> xs ++[x]) []

и map в целом:

map :: (a -> b) -> ([a] -> [b])
map f = fold (\x xs -> f x : xs) []

or filter:

filter :: (a -> Bool) -> ([a] -> [a])
filter p = fold (\x xs -> if p x then x : xs else xs) []

или даже fold left:

foldl f v xs = fold (\x g -> (\a -> g (f a x))) id xs v

Рекомендации:

  1. Учебное пособие по универсальности и выразительности сгибов, Грэм Хаттон, 1999.
  2. Запись foldl с помощью foldr, здесь.
person Don Stewart    schedule 04.09.2014
comment
для меня большая честь, что вы ответили на мой вопрос. Это отличная работа, которую ребята из Haskell проделывают, чтобы связаться с сообществом Clojure. Сейчас работаю через RWH - до 7 главы. - person hawkeye; 04.09.2014
comment
@hawkeye Какая работа, соколиный глаз? Я заинтригован. Является бульдогом Clojure Haskell, поскольку Хаксли был бульдогом Дарвина. - person Thumbnail; 04.09.2014
comment
@Thumbnail .. не определился с чисто функциональным кодом, но, несмотря на это, он искренне поддерживал публичную поддержку функционального программирования .. - похоже, аналогия может работать ;-) - person user2864740; 05.09.2014

Отредактировано для распознавания mapv и filterv.


Стандарт reverse определяется в терминах reduce:

(defn reverse [coll]
  (reduce conj () coll))

map и filter ленивы, поэтому могут работать с бесконечными последовательностями. С reduce сделать это невозможно.

При этом reduce может реализовать mapv и filterv, нетерпеливые аналоги map и filter .

(defn mapv [f coll]
  (vec (reverse (reduce (fn [acc x] (cons (f x) acc)) () coll))))

(defn filterv [pred coll]
  (vec (reverse (reduce (fn [acc x] (if (pred x) (cons x acc) acc)) () coll))))

Мы можем обойтись без reverses и vecs, если будем накапливать в векторах:

(defn mapv [f coll]
  (reduce (fn [acc x] (conj acc (f x))) [] coll))

(defn filterv [pred coll]
  (reduce (fn [acc x] (if (pred x) (conj acc x) acc)) [] coll))

Это последнее почти то, как реализован стандарт filterv.

person Thumbnail    schedule 03.09.2014
comment
Почему conj для реверса, а cons (и внешний реверс) для карты/фильтра? - person user2864740; 04.09.2014
comment
@user2864740 user2864740 В reverse (defn conj [coll x] ...) имеет аргументы в правильном порядке для reduce; cons нужно было бы обернуть функцией, меняющей местами свои аргументы: #(cons %2 %1). В map cons внутри reduce складываются - следовательно, переворачивают - результаты, поэтому их нужно снова перевернуть. То же самое относится и к filter. Идиома Clojure заключается в использовании вектора в таких случаях, что позволяет избежать необходимости реверсирования продукта. Я добавил версии, которые делают это. Обратите внимание, что они возвращаются как последовательности на случай, если кто-то использует в них conj или disj. - person Thumbnail; 04.09.2014
comment
@user2864740 user2864740 последнее редактирование сделало некоторые из моих ответов недействительными. - person Thumbnail; 04.09.2014