Haskell:函数可以编译吗?

时间:2018-07-19 04:29:52

标签: haskell compilation ghc hint ghc-api

考虑一个简单的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和hint有帮助吗?

编辑

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

main = interpret "..."

但是,当我们尝试编译某些内容时,通常不会这样做,因此请考虑使用interpretFile :: FilePath -> IO ()。让BF程序保存在文件(helloworld.bf)中。

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

$ ./MyBfCompiler helloworld.bf -o helloworld

2 个答案:

答案 0 :(得分:2)

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

在前一种情况下,您想要的东西比这还充实一些:

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命令将编译为表示Brainfuck程序的字节码。

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

您也可以将此想法表示为

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

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

上一个答案

在某些情况下,编译器可以内联函数调用(GHC中有编译指示要其执行此操作)。如果命名闭包,编译器也更有可能执行您想要的操作,例如:

main = interpret foo

在GHC中,您可以通过添加

为编译器提供提示
{-# INLINE main #-}

甚至

{-# INLINE interpret #-}

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

答案 1 :(得分:2)

答案基本上是。

有多种方法构造IO值:

  1. 内置putStrLn之类的功能
  2. 诸如return>>=之类的Monad操作

一旦有了IO值,就可以通过三种方法对其进行细分:

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

所有这些分解为将IO a转换为a的过程。没有其他方法可以对其进行检查以查看其作用。

类似地,您对函数只能执行的操作是将其放入变量中或调用它们(或将它们转换为C函数指针)。

没有健全的方法来检查功能。

您可以做的一件不编译但要链接的事情是,使解释器主函数在某个外部c字符串上运行,将其构建为静态对象,然后您的“编译器”可以由此创建一个新对象程序中的C字符串,并将其链接到您已有的文件。


这种局部评估理论说,如果您对应用于输入的解释器的局部评估器进行局部评估,那么您得到的是编译器,但是ghc并不是足够高级的局部评估器。