Смотрите. Какая-нибудь randomRIO (возьму функцию с явным аргументом, чтобы не конфликтовать по поводу терминологии и отличий значения от 0-арной функции) с типом :: Random a => (a, a) -> IO a абсолютно чистая, потому что для одного и того же аргумента с типом (а, а) она вернёт одно и то же значение типа IO a. В это значение завёрнута функция, которая принимает RealWorld (как тут уже объяснили в комментариях) и возвращает пару из значения типа a и нового RealWorld'а, и она тоже чистая, потому что для одинаковых состояний мира она вернула бы одну и ту же пару. Фича реализации типа IO в том, что эту копию состояния нельзя получить (в общем-то, как и в реальном мире :). И монада IO — не единственный вариант это обеспечить. Например, в Clean тоже все функции чистые, а невозможность копировать мир достигается при помощи уникальных типов. Похожий механизм, кажется, есть в Rust. А в старых, домонадических версиях хаскеля были явные функции, обрабатывавшие мир (точнее, приходящие от него события).
Но концептуально действительно схоже, с тем отличием, что тут мы выносим семантическую разницу в компайл-тайм. У Спольски неплохая статья есть про то, как не путать строки со столбцами (с примечанием переводчика «а давайте это за нас ещё и компилятор проверит») и как венгерская нотация получила свою печальную репутацию.
Функциональщина этого и не утверждает. Любая функция есть значение, но не любое значение — функция (и не любая конструкция языка — значение).