После второго раунда обсуждений стало ясно, почему every от пустого множества дает истину. Читатель Миша Левченко внятное объяснение, которое понятно мне как программисту. Оно не опирается на кванторы и логику. И хотя вывод все равно не нравится, приведу объяснение здесь.

Дело в том, что операции над списками нужно рассматривать как свертку. Есть такая функция reduce (она же fold), которая принимает функцию двух аргументов и коллекцию. Результат функции такой:

fn(fn(fn(fn(item0, item1), item2), item3), item4)...

Например, для сложения чисел 1, 2, 3, 4 получим форму:

(((1 + 2) + 3) + 4)

Reduce может накапливать в том числе другую коллекцию: словарь или список. Это вообще очень мощная функция. Про себя я называю ее царицей функций, потому что через reduce можно выразить что угодно.

Reduce выше прекрасно работает, если элементов два и более. Когда их один или ноль, начинаются граничные случаи. Одно из решений в том, что reduce может принимать т.к. init — первичный элемент, который подставляется в начало цепочки. Чаще всего он выступает коллекцией-аккумулятором, но может быть и простым скаляром.

Если передать init, форма будет такой:

fn(fn(fn(init, item0), item1), item2)...

Другими словами, он гарантирует, что элементов больше нуля. Если основной список пустой, просто вернем init.

Так вот, в терминах свертки функция ALL (которую я раньше называл every?) выглядит так:

(func ALL [fn-pred items]
  (reduce (fn [x y]
         (and x (fn-pred y)))
       true
       items))

Демо:

(ALL int? [1 2 3])
true

(ALL int? [1 nil 3])
false

(ALL int? [])
true

А вот функция ANY (что хотя бы один элемент вернул истину для предиката):

(func ANY [fn-pred items]
  (reduce (fn [x y]
         (or x (fn-pred y)))
       false
       items))

Демо:

(ANY int? [1 nil 3])
true

(ANY int? [nil nil nil])
false

(ANY int? [])
false

Аналогично работают функции суммирования: это reduce, где начальные элементы равны 0 и 1. Поэтому (+) дает 0, а (*) — 1.

Как видим, все это можно объяснить без греческих букв и терминов. А пустая истина, о которой я писал ранее, считается истиной только потому, что таков начальный элемент свертки.

Другое дело, что такой подход все равно мне не нравится. В каждом их них скрыт начальный элемент: для ALLtrue, для ANYfalse, единица для умножения и так далее. Считается очевидным, что он должен быть именно таким. А мне это не очевидно. Я спотыкаюсь, когда вижу, что произведение элементов пустого списка равно единице. Я бы предпочел неопределённость — то есть null.

Я в курсе про нейтральный элемент: ноль для сложения, единица для умножения. Но на пустых списках это как-то не очень. Душа не принимает, если совсем честно.

В самом деле, в математике оператор умножения — бинарный, ему нужно два операнда. Нельзя записать что-то вроде 5 * = 5 — тут не хватает операнда справа. С какой стати мы обходим математические правила — не ясно.

Я часто использую reduce и вывел правило: всегда указываю начальный элемент. Например, чтобы сложить список чисел, я пишу так:

(reduce + 0 numbers)

вместо

(apply + numbers)

Потому что во втором случае не очевидно, во что накапливается результат.

Словом, пока что меня отпустило на тему пустой истины. Все оказалось просто: это свертка, где начальный элемент — истина. Крайне неочевидно, на мой взгляд. Чтобы не отстрелить ногу, либо проверяйте коллекцию на пустоту, либо пишите явный reduce, где начальный элемент — ложь, если того требует контекст.