哈斯克尔:函数可以编译吗



考虑一个简单的Haskell Brainf*ck解释器。看看interpret函数就知道了。

import Prelude hiding (Either(..))
import Control.Monad
import Data.Char (ord, chr)
-- function in question
interpret :: String -> IO ()
interpret strprog = let (prog, []) = parse strprog
in execBF prog

interpretFile :: FilePath -> IO ()
interpretFile fp = readFile fp >>= interpret

type BF = [BFInstr]
data BFInstr = Left | Right | Inc | Dec | Input | Output | Loop BF
type Tape = ([Integer], [Integer])
emptyTape = (repeat 0, repeat 0)
execBFTape :: Tape -> BF -> IO Tape
execBFTape = foldM doBF
execBF :: BF -> IO ()
execBF prog = do
execBFTape emptyTape prog
return ()
doBF :: Tape -> BFInstr -> IO Tape
doBF ((x:lefts),   rights)   Left     = return (lefts,         x:rights)
doBF (lefts,    (x:rights))  Right    = return (x:lefts,         rights)
doBF (left,     (x:rights))  Inc      = return (left,      (x+1):rights)
doBF (left,     (x:rights))  Dec      = return (left,      (x-1):rights)
doBF (left,     (_:rights))  Input    = getChar >>= c -> return (left, fromIntegral (ord c):rights)
doBF t@(_,      (x:     _))  Output   = putChar (chr (fromIntegral x)) >> return t
doBF t@(left,   (x:     _)) (Loop bf) = if x == 0
then return t
else do t' <- execBFTape t bf
doBF t' (Loop bf)
simpleCommands = [('<', Left),
('>', Right),
(',', Input),
('.', Output),
('+', Inc),
('-', Dec)]
parse :: String -> (BF, String)
parse []          = ([], [])
parse (char:prog) = case lookup char simpleCommands of
Just command -> let (rest, prog') = parse prog
in (command : rest, prog')
Nothing      ->
case char of
']' -> ([], prog)
'[' -> let (loop, prog')  = parse prog
(rest, prog'') = parse prog'
in (Loop loop:rest, prog'')
_   -> parse prog

所以我有一个类似interpret "[->+<]"的函数。这给了我一个执行给定程序的IO ()单元操作。它的类型适合作为某个程序的main

假设我想把这个动作编译成一个可执行文件,也就是说,我想生成一个以interpret ...的结果为主要函数的可执行文件。当然,这个可执行文件必须包含GHC运行时系统(用于无限列表、整数运算等(

问题:

  1. 我认为,不可能只执行一元操作并将其保存为新文件。这是真的吗
  2. 如何才能达成类似的解决方案?GHC Api和提示有帮助吗

编辑

对不起,我在原来的问题中过于简单化了。当然,我可以写一个这样的文件:

main = interpret "..."

但当我们试图编译某个东西时,这不是我们通常要做的,所以请考虑interpretFile :: FilePath -> IO ()。将BF程序保存在一个文件中(helloworld.bf(。

我该如何创建一个执行helloworld.bf内容的可执行文件而不需要该文件?

$ ./MyBfCompiler helloworld.bf -o helloworld

答案基本上是否定的。

构造IO值的方法有很多:

  1. 内置函数,如putStrLn
  2. 类似return>>=的Monad操作

一旦你有了IO值,有三种方法可以分解它:

  1. main设置为等于值
  2. unsafePerformIO
  3. 作为导出的C函数的返回值

所有这些都分解为将IO a转换为a。没有其他方法可以检查它,看看它做了什么。

类似地,对函数唯一能做的就是将它们放入变量中或调用它们(或将它们转换为C函数指针(。

没有其他合理的方法来检查函数。

你可以做的一件事不是编译而是链接,那就是让你的解释器主函数在某个外部c字符串上运行,把它构建成一个静态对象,然后你的"编译器"可以用这个程序的c字符串创建一个新对象,并把它链接到你已经拥有的。


有一种部分求值理论认为,如果你对应用于某些输入的解释器的部分求值器进行部分求值,那么你得到的是编译器,但ghc不是一个足够高级的部分求值程序。

我不确定您是在问如何编写一个可以将helloworld.bf等文件作为输入的编译器,还是在问如何编译运行helloworld.bf的Haskell程序。

在前一种情况下,你会想要比这更丰富的东西:

import System.Environment (getArgs)
main :: IO ()
main = do
(_:fileName:_) <- getArgs
source <- readFile fileName
interpret source
interpret :: String -> IO ()
interpret = undefined -- You can fill in this piddly little detail yourself.

如果你想要后者,有几个不同的选择。首先,您可以将*.bf文件的内容存储在字符串常量中(或者更确切地说,是Text或严格的ByteString(,并将其传递给解释器函数。如果GHC足够乐观,能够在编译时完全内联和扩展该调用,我会感到惊讶,但原则上Haskell编译器可以。

第二个是将Brainfuck变成一种特定于领域的语言,使用您定义的运算符,这样您就可以真正编写类似的东西

interpret [^<,^+,^>,^.]

如果定义(^<)和其他运算符,Brainfuck命令将编译为代表Brainfuc克程序的字节码。

在这种情况下,与第一种方法相比没有明显的好处,但使用更结构化的语言,您可以进行优化,将源代码编译为更适合解释器执行的基于堆栈的字节码,或者生成更复杂的AST。

你也可以将这个想法表达为

interpret
(^< ^+ ^> ^.)
input

这里,如果Brainfuck命令是具有从右到左优先级的高阶函数,并且是interpret bf input = (bf begin) input,则Brainfuc克代码将简单地编译为解释器调用的函数。这最有可能被转换成快速的本机代码。

上一个答案

在某些情况下,编译器可以内联函数调用(GHC中有一些杂注可以告诉它这样做(。如果您命名闭包,编译器也更有可能执行您想要的操作,例如:

main = interpret foo

在GHC中,您可以通过添加来给编译器一个提示

{-# INLINE main #-}

甚至

{-# INLINE interpret #-}

您可以通过使用-S编译模块并查看源代码来检查GHC生成了什么代码。

相关内容

  • 没有找到相关文章

最新更新