Я имею в виду определение экземпляра класса типа, который применяется в локальной (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-аргументные "значения", которые могут зависеть от переменных замыкания, но которые, поскольку они не содержат аргументов, не могут извлекать их из аргументов.