Что я делаю: я пишу небольшую систему интерпретатора, которая может анализировать файл, превращать его в последовательность операций, а затем передавать тысячи наборов данных в эту последовательность для извлечения некоторого окончательного значения. от каждого. Скомпилированный интерпретатор состоит из списка чистых функций, которые принимают два аргумента: набор данных и контекст выполнения. Каждая функция возвращает измененный контекст выполнения:
type ('data, 'context) interpreter = ('data -> 'context -> 'context) list
Компилятор - это, по сути, токенизатор с последним этапом сопоставления токена с инструкциями, который использует описание карты, определенное следующим образом:
type ('data, 'context) map = (string * ('data -> 'context -> 'context)) list
Типичное использование интерпретатора выглядит так:
let pocket_calc =
let map = [ "add", (fun d c -> c # add d) ;
"sub", (fun d c -> c # sub d) ;
"mul", (fun d c -> c # mul d) ]
in
Interpreter.parse map "path/to/file.txt"
let new_context = Interpreter.run pocket_calc data old_context
Проблема: я бы хотел, чтобы мой pocket_calc
интерпретатор работал с любым классом, который поддерживает методы add
, sub
и mul
и соответствующий тип data
(могут быть целыми числами для одного класса контекста и числами с плавающей запятой для Другая).
Однако pocket_calc
определяется как значение, а не функция, поэтому система типов не делает свой тип универсальным: при первом использовании типы 'data
и 'context
привязываются к типам любых данных и контекста, которые я сначала предоставляю, и интерпретатор навсегда становится несовместимым с любыми другими типами данных и контекстов.
Жизнеспособное решение - расширить определение интерпретатора, чтобы параметры его типа были универсальными:
let pocket_calc data context =
let map = [ "add", (fun d c -> c # add d) ;
"sub", (fun d c -> c # sub d) ;
"mul", (fun d c -> c # mul d) ]
in
let interpreter = Interpreter.parse map "path/to/file.txt" in
Interpreter.run interpreter data context
Однако такое решение неприемлемо по нескольким причинам:
Он повторно компилирует интерпретатор каждый раз при его вызове, что значительно снижает производительность. Даже этап сопоставления (превращение списка токенов в интерпретатор с использованием списка карт) вызывает заметное замедление.
Мой дизайн полагается на то, что все интерпретаторы загружаются во время инициализации, потому что компилятор выдает предупреждения всякий раз, когда токен в загруженном файле не соответствует строке в списке карт, и я хочу видеть все эти предупреждения при запуске программного обеспечения (а не когда отдельные в конечном итоге запускаются интерпретаторы).
Иногда я хочу повторно использовать данный список карт в нескольких интерпретаторах, будь то сам по себе или добавляя дополнительные инструкции (например,
"div"
).
Вопросы: есть ли способ сделать тип параметрическим, кроме расширения eta? Может быть, какой-нибудь хитрый трюк с подписями модулей или наследованием? Если это невозможно, есть ли способ решить три проблемы, о которых я упомянул выше, чтобы сделать eta-расширение приемлемым решением? Спасибо!