Как написать правила построения фиксированной точки в Shake, например. Латекс - программирование
Подтвердить что ты не робот

Как написать правила построения фиксированной точки в Shake, например. Латекс

Используя библиотеку сборки Shake Haskell, как я могу написать правило, используя программу, которая должна достигать фиксированной точки? Представьте себе, что у меня есть программа foo, которая принимает файл input и создает выходной файл, который должен иметь foo повторно, пока выходной файл не изменится. Как я могу написать это в Shake?

Типичным примером этого шаблона является LaTeX.

4b9b3361

Ответ 1

Во-первых, обратите внимание, что многократное вызов Latex не всегда приводит к фиксированной точке, поэтому убедитесь, что у вас есть привязка к итерациям. Кроме того, некоторые дистрибутивы (MikTex) предоставляют версии Latex, которые автоматически запускаются столько раз, сколько им нужно, поэтому, если вы используете их, проблема исчезнет.

Напишите свою собственную команду foo_transitive

Самый простой способ решить проблему, предполагая, что каждый запуск foo имеет одинаковые зависимости, заключается в том, чтобы решить проблему за пределами системы сборки. Просто напишите команду foo_transitive, либо как оболочку script, либо как функцию Haskell, которая при подаче входного файла создает выходной файл, повторно запуская его и проверяя, достиг ли он фиксированной точки. Теперь система сборки может использовать foo_transitive, и нет проблем с зависимостями.

Кодировать его в системе сборки

Вам нужно написать два правила, один из которых делает один шаг, и тот, который определяет, какой шаг является правильным:

let step i = "tempfile" <.> show i

"tempfile.*" *> \out -> do
    let i = read $ takeExtension out :: Int
    if i == 0 then
        copyFile "input" out
    else
        let prev = step (i-1)
        need [prev]
        -- perhaps require addition dependencies, depending on prev
        system' "foo" [prev,out]

"output" *> \out -> do
    let f i = do
            old <- readFile' $ step (i-1)
            new <- readFile' $ step i
            if old == new || i > 100 then copyFile (step i) out else f (i+1)
    f 1

Первое правило генерирует tempfile.2 из tempfile.1 и т.д., поэтому мы можем need ["tempfile.100"] получить 100-ю итерацию. Если зависимости изменяются на каждом шаге, мы можем посмотреть на предыдущий результат, чтобы вычислить новые зависимости.

Второе правило циклически проверяет каждую пару значений в последовательности и останавливается, когда они равны. Если вы реализуете это в производственной системе сборки, вы можете не называть readFile' для каждого элемента дважды (один раз как i-1 и один раз как i).

Ответ 2

Развернувшись на @Neil Mitchell, ниже приведен пример кода foo_transitive. Сказав это, для этого конкретного случая я бы просто использовал latexmk, который делает правильную вещь.

import Control.Monad.Fix (fix, mfix)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Text.Printf (printf)

type SHA = Int

data TeXCompilationStage 
  = Init
  | BibTeX
  | Recompile SHA
  deriving (Show, Eq)

data TeXResult
  = Stable SHA
  | Unstable
  deriving (Show, Eq)

f retry x budgetLaTeXCalls
  | budgetLaTeXCalls <= 0
      = do
          liftIO $ putStrLn "Budget for LaTeX depleted; result didn't converge"
          return Unstable
  | otherwise
      = case x of
          Init        -> do
            liftIO $ do 
              putStrLn "Init"
              putStrLn "  # latex"
            retry BibTeX (budgetLaTeXCalls-1)
          BibTeX      -> do
            liftIO $ do
              putStrLn "BibTeX"
              putStrLn "  # bibtex"
            retry (Recompile 0) budgetLaTeXCalls
          Recompile previousSHA -> do
            let budgetLaTeXCalls' = budgetLaTeXCalls - 1
                calculcatedSHA    = 3
            liftIO $ do
              printf "Recompile (budget: %d)\n" budgetLaTeXCalls
              printf "  Prevous SHA:%d\n  Current SHA:%d\n" previousSHA calculcatedSHA
            if calculcatedSHA == previousSHA
              then do
                liftIO $ putStrLn "  Stabilized"
                return $ Stable calculcatedSHA
              else do
                liftIO $ putStrLn "  Unstable"
                retry (Recompile (previousSHA+1)) (budgetLaTeXCalls-1)

latex :: Int -> IO TeXResult
latex = fix f Init