Imprimir e executar uma string
-
14-11-2019 - |
Pergunta
Eu me pego escrevendo muitos códigos como
putStr "foo (bar 1) (bar 2) =" print $ foo (bar 1) (bar 2)
O problema é que a mensagem impressa pode ficar fora de sincronia com o código realmente executado.A solução óbvia é gerar automaticamente esse código.
Uma maneira de fazer isso seria colocar todo o texto em um arquivo e escrever um pequeno programa que leia o arquivo e gere o código-fonte Haskell a partir dele.Mas outra alternativa é usar o Template Haskell.
Alguém sabe como eu escreveria uma função que leva um String
e gera o código acima a partir dele?Acho que deve ser bem fácil, mas o TH não está bem documentado.
Solução
Você pode analisar o código Haskell usando o haskell-src-meta
pacote.Aqui está um exemplo rápido de como você pode combinar isso com Template Haskell.
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Language.Haskell.Meta
runShow = QuasiQuoter
{ quoteExp = runShowQQ
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
runShowQQ :: String -> Q Exp
runShowQQ s = do
let s' = s ++ " = "
Right exp = parseExp s
printExp = appE [|print|] (return exp)
infixApp [|putStr s'|] [|(>>)|] printExp
E você usaria assim
{-# LANGUAGE QuasiQuotes #-}
[runShow|foo (bar 1) (bar 2)|]
Outras dicas
Template Haskell não fornece um meio direto de analisar strings arbitrárias, então a solução mais simples é provavelmente usar o pré-processador C.No entanto, o integrado no GHC não suporta stringificação, então precisamos passar opções extras para usar o "real".
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -pgmP cpp #-}
#define PRINT_EXP(x) (putStr #x >> putStr " = " >> print (x))
Você pode então usá-lo assim:
PRINT_EXP(foo (bar 1) (bar 2))
Há um exemplo de eval
-como código Haskell usando API GHC aqui.
Ai.EU pensamento isso seria fácil, mas até onde sei, na verdade é impossível.
Eu esperava que houvesse uma função que transformasse uma string em uma expressão, mas aparentemente essa função não existe.Não existe nem uma função para carregar mais código-fonte do disco.Então parece que esta tarefa é realmente impossível!Estou bastante surpreso com isso.
A coisa mais próxima que posso fazer é citar a expressão que desejo executar e, em seguida, criar uma emenda que imprima a expressão citada antes de executá-la.No entanto, isso me deixa à mercê da bela impressora de expressão do GHC.O rótulo não sai exatamente enquanto eu digitava.(Em particular, parece substituir os operadores por nomes totalmente qualificados, o que é simplesmente doloroso.)
Você pensaria que um recurso como esse seria bastante trivial de implementar.O facto de não ser implementado só pode, portanto, ser atribuído a uma de duas coisas:
Na verdade, ninguém precisa desse recurso.(Bem, exceto eu, obviamente.)
Não é tão trivial quanto parece.(Por exemplo, talvez descobrir em que contexto analisar a expressão seja complicado de alguma forma?)
Você também pode usar o jogar fora pacote que foi escrito para lidar com este caso de uso exato:
{-# language QuasiQuotes #-}
import Debug.Dump
main = putStrLn [d| foo (bar 1) (bar 2) |]
foo = (+)
bar = (+1)
Que imprime: (foo (bar 1) (bar 2)) = 5
Também lida com mais de uma expressão separada por vírgulas:
putStrLn [d| foo (bar 1) (bar 2), map bar [1, 2] |]
Que imprime: (foo (bar 1) (bar 2)) = 5 (map bar [1, 2]) = [2,3]
Bônus:Se você tiver o nix-shell instalado (parte do gerenciador de pacotes nix) você pode até experimentá-lo rapidamente com esta "linha única":
$ nix-shell -p "nix-shell -p "haskellPackages.ghcWithPackages (p: [p.dump])" --run "echo '{-# language QuasiQuotes #-}; import Debug.Dump; foo = (+); bar = (+1); main = putStrLn [d| foo (bar 1) (bar 2), map bar [1, 2] |]' | runhaskell"