Нет, вы не можете разделить модуль между несколькими файлами, и вы определенно не можете определить функцию в разных местах. Самое близкое к этому - это функция, которая является частью класса типа, с экземплярами, определенными в разных модулях. Но это, вероятно, не то, что вы хотите здесь.
Однако является возможно скомпилировать взаимно-рекурсивные модули. Теоретически это должно просто работать (tm), но GHC требует немного обручей, чтобы сделать это; см. the User's Guide для деталей. Если вы получаете ошибку импорта циклического модуля, это должно позволить вам заставить эту версию работать.
Нет никакого «приятного» способа поймать ошибку неисчерпаемого соответствия шаблону и попробовать что-то еще. Есть несколько не очень приятных способов, но вы, вероятно, не хотите туда идти.
Если ваша цель - сопоставление образцов по одному типу данных с большим количеством случаев, самый простой способ разделить вещи, не вступая во взаимодействие с взаимно рекурсивными модулями или типами классов, - это иметь отдельные функции в других модулях, которые принимают содержимое каждого конструктора в качестве прямых аргументов, затем одно совпадение шаблонов во всех случаях в модулях, которые импортируют другие и отправляют.
Скажем, у вас есть тип Foo
с A
случаев, B
, & гр., С аналогичными именами модулей. В «центральном» модуле вы можете:
doStuff (A x y) = A.doStuffA x y
doStuff (B z) = B.doStuffB z
... и так далее.
В некоторых сценариях даже имеет смысл разделить весь тип данных аналогичным образом и создать отдельный тип для каждого конструктора, например: data Foo = A FooA | B FooB | ...
. Это наиболее полезно, когда у вас сложный путаница типов данных, которые могут быть взаимно рекурсивными несколькими способами, классическим примером является AST.
Хорошо, вот один из способов имитировать то, что вы хотите, не делая ничего слишком отрывочного.
Сначала разделите свою функцию на разные модули так, как вам хотелось бы. Затем сделайте следующие изменения:
Изменить тип результата использовать Maybe
, оберточная результаты в Just
и добавляя кетчуп все дела по умолчанию, который производит Nothing
.
Добавить дополнительный аргумент r
, и заменить рекурсивные вызовы на evalExp
с r
.
Из центрального модуля импортируйте каждый модуль, содержащий evalExp
чехлы. При необходимости используйте квалифицированный импорт, чтобы избежать двусмысленности. Определить список каждой Eval функции (все они должны иметь одинаковый тип), а затем определить «реальный» evalExp
как что-то вроде этого:
expCases = [A.GHC.Num.evalExp, A.GHC.Types.evalExpr {- etc... -} ]
evalExpCases exp = mapMaybe (\eval -> eval evalExp exp) expCases
evalExp exp = case evalExpCases exp of
(r:_) -> -- use the first result
[] -> -- no cases matched
По существу, это с помощью Maybe
указать неисчерпаемые модели в явном виде, и заменяющее прямую рекурсию с конструкцией стиля fix
, в которой объединенная, рекурсивная функция передается каждому (индивидуально нерекурсивному) множеству случаев.
Это довольно неудобно, но я не уверен, что есть лучший способ. Возможно, есть способ автоматизировать все, что дерьмо, используя Template Haskell, но это, вероятно, будет так же сложно, как делать это вручную.
Лично я бы, наверное, просто потрогал зубы и оставил все в одном модуле.
Я добавил тег haskell, но я не уверен, что вопрос относится к нему, поскольку вопрос касается GHC. При необходимости удалите. –
Очевидный вопрос в отношении первого пункта: вы уверены, что не можете избежать циклического импорта? Можете ли вы как-то разделить 'A' таким образом, чтобы устранить необходимость циклического импорта? – gspr
Для большинства практических применений Haskell в целом может быть специфичным для GHC. Не беспокойтесь об этом. Это далекий и самый распространенный компилятор, и большинство людей говорят, что «Haskell» они действительно означают «интерпретацию отчета GHC и некоторые неуказанные расширения GHC». –