Как aot-компилировать классы gen-класса с подсказками циклического типа?

В Clojure я использую gen-class с библиотекой Java. Программист обычно предоставляет два класса, которые реализуют интерфейс и расширяют класс соответственно. Предполагается, что два класса ссылаются друг на друга, и трудно избежать этой циклической зависимости, учитывая способ проектирования библиотеки.

Циклы не будут проблемой — компилятору не нужно знать о них — за исключением того, что я пытаюсь оптимизировать код, разумно добавляя подсказки типов (с огромным ускорением). Мне удалось избежать жалоб компилятора на циклические зависимости, реорганизовав код, и я свел проблему к подсказке одного типа:

В одном исходном файле Foo.clj у меня есть эта функция/метод, который требуется интерфейсом, который реализует класс:

(defn -step
  [^Foo this ^Bar bar]
  ...)

Другой исходный файл, Bar.clj, создает коллекцию экземпляров Foo, поэтому мне приходится ссылаться на класс Foo. В моем Leiningen project.clj у меня есть такая строка:

:aot [Foo Bar]

Я не получаю ошибку циклической зависимости. Вместо этого я получаю ClassNotFoundException: если я поставлю Foo первым после :aot, компилятор жалуется, что он не знает о Bar при компиляции Foo из-за подсказки типа ^Bar в -step. Если я поставлю Bar первым после :aot, компилятор не сможет найти Foo при компиляции Bar из-за вызовов (Foo.) в Bar.clj.

Мое текущее решение таково:

  1. Удалите подсказку типа ^Bar в определении -step в Foo.clj.
  2. Скомпилируйте оба класса.
  3. Добавьте подсказку типа обратно в -step в Foo.clj.
  4. Скомпилируйте Foo (т.е. снова запустите lein compile).

Это работает, потому что когда Foo компилируется во второй раз, Bar существует, поэтому компилятор не жалуется.

Есть ли способ скомпилировать оба класса без удаления и добавления подсказки типа? (Или по-другому, что я должен думать об этой ситуации?)


person Mars    schedule 15.05.2015    source источник


Ответы (1)


Я бы хотел добавить фабричную функцию для Foo в пространство имен foo.

(defn new-foo [] (Foo.)) ; parameterize to your satisfaction

И тогда вы можете использовать метод предварительного объявления в следующем ответе, чтобы получить доступ к new-foo из Bar - Перенаправить объявление переменной из другого пространства имен в Clojure?

Это, конечно, все еще неприятно - если есть какой-либо другой способ разорвать цикл зависимости, вы можете им воспользоваться. Как насчет определения Foo и Bar в одном и том же пространстве имен, если это имеет смысл? Они кажутся очень тесно связанными, хотя с абстрактным описанием проблемы трудно сказать.

person pete23    schedule 16.05.2015
comment
Спасибо пит23. Ики, я попробую. Извините за абстрактное описание — попытка убрать ненужные детали, а настоящие имена классов — Student и Students, что сбивает с толку. Существует много Foo (Student) и обычно только один Bar (Students) с использованием библиотеки моделирования на основе агентов (MASON). Каждый Student должен иметь метод step, который многократно вызывается методами планирования в суперклассе Students. Возможно, Student расширит класс планирования, но это также непривычно: несколько планировщиков просто не вызываются. - person Mars; 17.05.2015
comment
pete23, то ли компилятор путается, то ли я (можете догадаться, что более вероятно). Вернемся к языку foo/bar: Foo.clj теперь имеет new-foo, как в вашем ответе. Bar.clj включает (ns Foo) (declare new-foo) (ns Bar) перед (Foo/new-foo) в определение функции, как это предлагается в ответе, который вы указали. Bar указан первым в последовательности :aot в файле project.clj. Он компилируется! Но во время выполнения он выдает IllegalStateException: Attempting to call unbound fn: #'Foo/new-foo. Что?? Почему это не привязано? Думаю, я запутал компилятор переключателем пространства имен. - person Mars; 17.05.2015
comment
Хммм, это говорит о том, что он не ловит фу... Подождите. - person pete23; 17.05.2015