Всякий раз, когда вы используете ()
в качестве параметра, на самом деле вы говорите
Хотя я объявил параметр здесь, мне не интересно, что это такое, и я не собираюсь ничего делать с его значением.
Вам это не интересно, потому что ()
вообще ничего интересного не имеет; вы не собираетесь ничего с этим делать, потому что вы ничего не можете сделать с ()
.
Проблема заключается в том, что компилятор имеет право оптимизировать его, поскольку передается только одно возможное значение, поэтому его использование всегда предсказуемо, так почему бы не предположить это? Но он перемещает его обратно в CAF и заставляет идею не работать.
К счастью, есть еще один способ сделать это. Посмотрите на следующую модификацию twoTrues
:
twoTrues :: a -> [[[Bool]]]
twoTrues _ = map (++ (True : repeat False)) . trueBlock <$> [1..]
Теперь вы можете использовать twoTrues
так:
map concat $ twoTrues()
Поскольку a
является параметром неиспользуемого типа, вызывающая сторона может передавать все что угодно. И поскольку вы не знаете, что это будет, значит, вы не представляете, что с этим можно сделать. Это на самом деле заставляет вас игнорировать его значение. Так что это в основном декларация того же утверждения, о котором я упоминал ранее.
Конечно, теперь вы можете передать любую функцию (включая undefined
) этой функции. Но это не имеет значения, и на самом деле именно эта возможность делает этот трюк работоспособным, поскольку компилятор больше не может предсказать, как эта функция используется. Когда пользователь видит эту функцию, он должен знать, что вы собираетесь здесь сказать, и заключить, что передача ()
является самой простой, но даже если он этого не делает и передает что-то еще, это ничего не сломает, и поскольку Haskell ленив дополнительный параметр никогда не может быть оценен вообще.
Так что, если ()
будет использоваться в результате? Это еще хуже. Поскольку возвращение ()
означает, что ваша функция вообще ничего не делает (в Haskell все эффекты функции должны быть представлены в ее возвращаемом значении), компилятор имеет право сделать вывод, что ваша функция не нужна.
Вывод таков: ()
как тип не должен появляться в сигнатурах типа, если только не используется с другим типом (то есть в IO ()
).
EDIT
Теперь можно задаться вопросом: если есть только один способ реализовать a -> String
из String
, почему компилятор не может сделать вывод, что они одинаковы. Ответ, как оказалось, заключается в том, что у вас есть два способа реализовать это.
usual :: a -> String
usual _ = "Hello World!"
unusual :: a -> String
unusual a = seq a "Hello World!"
Почти для всех входных данных usual value = unusual value
, но usual undefined
равно "Hello World!"
, а unusual undefined
равно undefined
.
С человеческой точки зрения, unusual
довольно необычно, поскольку заставляет оценивать значение, не связанное с конечным результатом. Если в любом случае вам нужна такая вещь, просто позвоните seq
. Кроме того, поскольку Haskell по умолчанию является ленивым, если вы хотите определить строгую функцию, вам лучше задокументировать это поведение. Поэтому, если вы видите такую подпись без дополнительной документации, вы имеете право предполагать, что она реализована usual
.