haskell - matematico - Cómo escribir reglas de construcción de punto fijo en Shake, por ejemplo, látex
exponencial en latex (2)
Ampliando la de , a continuación hay un código de ejemplo de foo_transitive
. Dicho esto, para este caso en particular, solo usaría latexmk
que hace lo correcto ™.
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
Usando la biblioteca de compilación Shake Haskell, ¿cómo puedo escribir una regla usando un programa que necesita alcanzar un punto fijo? Imagina que tengo un programa foo
que toma una input
archivo y produce un archivo de salida, que debería haber aplicado foo
repetidamente hasta que el archivo de salida no cambie. ¿Cómo puedo escribir eso en Shake?
El ejemplo típico de este patrón es LaTeX.
En primer lugar, tenga en cuenta que llamar a Latex repetidamente no siempre produce un punto fijo, así que asegúrese de tener un límite en las iteraciones. Además, algunas distribuciones (MikTex) proporcionan versiones de Latex que se ejecutan automáticamente tantas veces como sea necesario, por lo que si las usa, el problema desaparece.
Escribe tu propio comando foo_transitive
La forma más fácil de resolver el problema, suponiendo que cada ejecución de foo
tenga las mismas dependencias, es resolver el problema fuera del sistema de compilación. Simplemente escriba un comando foo_transitive
, ya sea como un script de shell o como una función de Haskell, que cuando se suministra un archivo de entrada produce un archivo de salida ejecutándose repetidamente y verificando si ha alcanzado un punto fijo. El sistema de compilación ahora puede usar foo_transitive
y no hay problemas con las dependencias.
Codificarlo en el sistema de compilación.
Necesita escribir dos reglas, una que da un paso y otra que determina qué paso es el correcto de usar:
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
La primera regla genera tempfile.2
desde tempfile.1
y así sucesivamente, por lo que podemos need ["tempfile.100"]
para obtener la iteración número 100. Si las dependencias cambian en cada paso, podemos ver el resultado anterior para calcular las nuevas dependencias.
La segunda regla recorre todos los pares de valores en la secuencia y se detiene cuando son iguales. Si está implementando esto en un sistema de compilación de producción, es posible que desee evitar llamar a readFile''
en cada elemento dos veces (una vez como i-1
y una vez como i
).