Я имею в виду определение экземпляра класса типа, который применяется в локальной (let или where) области действия. Что еще более важно, я хочу, чтобы функции в этом экземпляре были закрытыми, т.е. Иметь возможность закрывать переменные в лексической области, где указан экземпляр (что означает, что экземпляр может работать по-другому в следующий раз, когда функция, в которой она находится, называется).
Я могу дать вам упрощенный пример использования. Предположим, что у меня есть функция, которая работает с типом, основанным на классе типа. В этом примере я использую возведение в квадрат, который работает на любом типе, в экземплярах Num (да, возведение в квадрат очень простое и его можно легко переделать, но оно стоит для чего-то более сложного). Мне нужно иметь возможность использовать существующую функцию as-is (без ее изменения или повторной реализации).
square :: Num a => a -> a
square x = x * x
Теперь предположим, что я хочу использовать эту операцию в модульной арифметике, т.е. сложение, умножение и т.д. mod some number. Это было бы легко реализовать для любой фиксированной модульной базы, но я хочу иметь что-то общее, что я могу повторно использовать для разных модулей по модулю. Я хочу иметь возможность определить что-то вроде этого:
newtype ModN = ModN Integer deriving (Eq, Show)
-- computes (x * x) mod n
squareModN ::
squareModN x n =
let instance Num ModN where
ModN x * ModN y = ModN ((x * y) `mod` n) -- modular multiplication
_ + _ = undefined -- the rest are unimplemented for simplicity
negate _ = undefined
abs _ = undefined
signum _ = undefined
fromInteger _ = undefined
in let ModN y = square (ModN x)
in y
Дело в том, что мне нужно использовать функцию сверху (square), которая требует, чтобы ее аргумент был типом, являющимся экземпляром определенного типа. Я определяю новый тип и делаю его экземпляром Num; однако, чтобы он правильно выполнял арифметику по модулю, он зависит от базы по модулю n, которая из-за общего дизайна этой функции может меняться от вызова к вызову. Я хочу определить функции экземпляра как однократные "обратные вызовы" (если хотите) для функции square, чтобы настроить, как она выполняет операции на этот раз (и только на этот раз).
Одним из решений может быть интеграция "замыкающих переменных" непосредственно в сам тип данных (т.е. ModN (x, n) для представления числа и базы, к которой он принадлежит), и операции могут просто извлечь эту информацию из аргументов. Однако это имеет несколько проблем: 1) для функций с несколькими аргументами (например, (*)) во время выполнения необходимо проверить, что эта информация соответствует, что является уродливым; и 2) экземпляр может содержать 0-аргументные "значения", которые могут зависеть от переменных замыкания, но которые, поскольку они не содержат аргументов, не могут извлекать их из аргументов.