Умышленное определение бесконечного типа в haskell

Я хочу определить, что, как представляется, требует бесконечного типа.

Требуется: функция "есть", которая ест все ее аргументы, кроме "3", для которых она возвращает 3

eat 3 = 3
eat x = eat

Таким образом, в основном произвольное выражение типа "eat (+) foldl (Just 5) 3" оценивается до 3. Но проблема здесь в том, что есть еда. Что это должно быть?

Ближайший я получил к запущенному коду:

newtype Rec = MakeRec (Int -> Rec)

eat :: Int -> Rec
eat x = MakeRec eat


instance Show Rec where
     show _ = "EAT"

Это работает нормально для "eat 6", но не для "eat 6 7", и это не работает, если я ставлю (ем 3 = 3) в нем определение.

Я не уверен, возможно ли это в Haskell. (Какой аргумент можно использовать, чтобы показать, что это невозможно?)

ОБНОВЛЕНИЕ: Как указано ниже, информация о типе необходима во время компиляции, чтобы компилятор мог знать, является ли "eat foldl 3 foldl" недопустимым или нет. Таким образом, точное решение этой проблемы невозможно.

Ответ 1

Это невозможно. Бесконечные типы (которые не являются типами данных) явно запрещены в Haskell, и легко создать код, который потребует их, и, следовательно, приведет к ошибке (например, попробуйте let foo = (42, foo) in foo).

Вы можете, конечно, создать их с помощью newtype и data, как и вы, но тогда вы должны явно переносить и разворачивать значения внутри и вне конструкторов.

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

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

class Eat a where
    eat :: a

instance Eat Integer where
    eat = 3

instance (Eat r) => Eat (a -> r) where
    eat _ = eat

Уловка заключается в том, что вам нужно точно указать типы, когда вы его используете:

*Main> eat 1 foldr ()

<interactive>:6:1:
    Ambiguous type variable `t0' in the constraint:
      (Eat t0) arising from a use of `eat'
    Probable fix: add a type signature that fixes these type variable(s)
    In the expression: eat 1 foldr ()
    In an equation for `it': it = eat 1 foldr ()
*Main> eat 1 foldr () :: Integer
3

Это потому, что eat 1 foldr () может быть Integer, но также может быть другой функцией, так же, как мы использовали eat 1 и eat 1 foldr как функции в одном выражении. Опять же, мы получаем гибкую типизацию, но должны явно указывать типы, которые мы хотим взамен.

1 Подумайте о перегрузке текстового класса, например, о перегруженных числовых литералах (42 может быть любым типом, который является экземпляром Num).