Clojure: Как я применяю функцию к подмножеству записей в карте хеша?

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

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

также полезно, если кто-то в будущем хочет реализовать тот же интерфейс с нуля. Им не придется считать первую реализацию для знания то, что Скидка означает, и тестовое действие как однозначное резервное копирование на любое записанное описание интерфейса, который Вы можете иметь.

Однако Вы обмениваете время. Если существуют другие тесты, Вы могли бы писать использование времени, Вы сохраняете пропуск этих тривиальных тестов, возможно, они были бы более ценными. Это зависит от Вашей тестовой установки и природы приложения, действительно. Если Скидка важна для приложения, то Вы собираетесь поймать любые ошибки в этом методе на функциональном тестировании так или иначе. Все поблочное тестирование делает позволен, Вы ловите их в точке, Вы тестируете эту единицу, когда местоположение ошибки будет сразу очевидно, вместо того, чтобы ожидать, пока приложение не будет интегрировано вместе, и местоположение ошибки могло бы быть менее очевидным.

Между прочим, лично я не использовал бы 100 в качестве цены в тестовом сценарии (или скорее если бы я сделал тогда, то я добавил бы другой тест с другой ценой). Причина состоит в том, что кто-то в будущем мог бы думать, что Скидка, как предполагается, является процентом. Одна цель тривиальных тестов как это состоит в том, чтобы гарантировать, что ошибки в чтении спецификации исправлены.

[Относительно редактирования: Я думаю, что неизбежно, что неправильная спецификация является точкой отказа. Если Вы не знаете то, что приложение, как предполагается, делает, то возможности - оно, не сделает этого. Но запись тестов для отражения спецификации не увеличивает эту проблему, этому просто не удается решить его. Таким образом, Вы не добавляете новые точки отказа, Вы просто представляете существующие отказы в коде вместо вафля документация.]

5
задан Jeroen Dirks 28 October 2009 в 17:37
поделиться

3 ответа

(defn do-to-map [amap keyseq f]
  (reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq))

Разбивка:

Это помогает взглянуть на это изнутри. В Clojure хеш-карты действуют как функции; если вы вызываете их как функцию с ключом в качестве аргумента, возвращается значение, связанное с этим ключом. Таким образом, учитывая единственный ключ, текущее значение для этого ключа можно получить с помощью:

(some-map some-key)

Мы хотим взять старые значения и изменить их на новые значения, вызвав для них некоторую функцию f . Итак, для одного ключа новое значение будет:

(f (some-map some-key))

Мы хотим связать это новое значение с этим ключом в нашей хэш-карте, «заменяя» старое значение. Вот что делает assoc :

(assoc some-map some-key (f (some-map some-key)))

(«Заменить» заключено в кавычки, потому что мы не изменяем ни один объект хэш-карты; мы возвращаем новые, неизменяемые, измененные объекты хеш-карты каждый раз мы вызываем assoc . Это по-прежнему быстро и эффективно в Clojure, потому что хэш-карты являются постоянными и разделяют структуру, когда вы ассоциируете их.)

Нам нужно постоянно ассоциировать новые значения на нашей карте, одно ключ за раз. Итак, нам нужна какая-то конструкция цикла. Мы хотим начать с нашей исходной хэш-карты и одного ключа, а затем «обновить» значение этого ключа. Затем мы берем эту новую хэш-карту и следующий ключ и «обновляем» значение для этого следующего ключа. И мы повторяем это для каждого ключа, по одному, и, наконец, возвращаем хэш-карту, которую мы «накопили». Это то, что делает reduce .

  • Первый аргумент reduce - это функция, которая принимает два аргумента: значение «аккумулятора», которое мы постоянно «обновляем». и более; и один аргумент, используемый в одной итерации для накопления.
  • Второй аргумент reduce - это начальное значение, переданное в качестве первого аргумента этой fn .
  • Третий аргумент reduce - это коллекция аргументов, передаваемых в качестве второго аргумента в эту fn по одному.

Итак:

(reduce fn-to-update-values-in-our-map 
        initial-value-of-our-map 
        collection-of-keys)

fn-to-update-values-in-our-map просто оператор assoc сверху, завернутый в анонимную функцию:

(fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))

Итак, вставив его в reduce :

(reduce (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
        amap
        keyseq)

В Clojure есть сокращение для написания анонимных функций: # (...) - это анонимный fn , состоящий из единственной формы, в которой % 1 привязан к первому аргументу анонимной функции, % 2 ко второму и т. Д. Таким образом, наш fn сверху может быть эквивалентно записан как:

#(assoc %1 %2 (f (%1 %2)))

Это дает нам:

(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq)
24
ответ дан 18 December 2019 в 05:49
поделиться
(defn doto-map [m ks f & args]
  (reduce #(apply update-in %1 [%2] f args) m ks))

Пример вызова

user=> (doto-map {:a 1 :b 2 :c 3} [:a :c] + 2)
{:a 3, :b 2, :c 5}

Надеюсь, это поможет.

4
ответ дан 18 December 2019 в 05:49
поделиться

Похоже, работает следующее:

(defn doto-map [ks f amap]
  (into amap
    (map (fn [[k v]] [k (f v)])
         (filter (fn [[k v]] (ks k)) amap))))

user=> (doto-map #{:hello :foo} (fn [k] (.toUpperCase k)) {:hello "World" :try "This" :foo "bar"})
{:hello "WORLD", :try "This", :foo "BAR"}

Возможно, это лучший способ сделать это. Возможно, кто-то сможет придумать красивый однострочный текст :)

1
ответ дан 18 December 2019 в 05:49
поделиться