Haskell 在程序设计语言解释器运行时使用unsafePerformIO

Haskell 在程序设计语言解释器运行时使用unsafePerformIO,haskell,io,referential-transparency,Haskell,Io,Referential Transparency,要将IO函数添加到用Haskell编写的编程语言解释器中,我基本上有两种选择: 修改整个解释器以在IO monad中运行 让可由解释程序调用的运行时函数使用unsafePerformIO 前者对我来说是个坏主意——这实际上抵消了IOreach在程序中几乎无处不在所带来的任何纯度好处。我目前也大量使用ST,必须修改大量程序才能实现这一点,因为我无法同时使用ST和IO(?) 后者让我感到紧张——正如函数名所述,它是不安全的,但我认为在这种情况下,它可能是合理的。特别是: 此更改所涉及的代码量将

要将IO函数添加到用Haskell编写的编程语言解释器中,我基本上有两种选择:

  • 修改整个解释器以在IO monad中运行
  • 让可由解释程序调用的运行时函数使用
    unsafePerformIO
前者对我来说是个坏主意——这实际上抵消了
IO
reach在程序中几乎无处不在所带来的任何纯度好处。我目前也大量使用
ST
,必须修改大量程序才能实现这一点,因为我无法同时使用
ST
IO
(?)

后者让我感到紧张——正如函数名所述,它是不安全的,但我认为在这种情况下,它可能是合理的。特别是:

  • 此更改所涉及的代码量将非常小
  • 在对解释表达式求值期间,通过在控制点使用
    seq
    ,可以执行IO的点已经被明确排序
  • 也许更重要的是,IO操作返回的值只能在代码的解释部分中使用,我可以通过以下事实来保证引用的透明度:不能使用相同的参数多次调用解释器,因为操作计数器将作为相同更改的一部分贯穿整个系统,并始终将唯一值传递给将使用
    unsafePerformIO
    的每个函数
在这种情况下,是否有充分的理由不使用
unsafePerformIO

更新
有人问我为什么要在口译员中保持纯洁。原因有很多,但可能最紧迫的是我打算稍后为这种语言构建一个编译器,这种语言将包括各种元编程技术,这些技术要求编译器包含解释器,但我希望能够保证编译结果的纯度。为此,该语言将有一个纯子集,我希望解释器在执行该子集时是纯的。

如果我理解正确,您希望将
IO
操作添加到解释语言(不纯primops),而解释器本身是纯的

第一个选项是来自解释器的抽象primops。例如,解释器可以在一些未指定的monad中运行,同时注入priops:

data Primops m = Primops
  { putChar :: Char -> m ()
  , getChar :: m Char
  , ...
  }

interpret :: Monad m => Primops m -> Program -> m ()
现在,解释器不能执行任何
IO
操作,除了关闭的primops列表。(使用自定义monad而不是将primops作为参数传递,可以获得类似的结果。)


但是我会考虑工程问题,直到你确切地说出你为什么需要纯粹的翻译。也许你不知道?如果您只是想使解释器的纯部分易于测试,那么最好将这些部分提取到单独的纯函数中。这样一来,顶级入口点将是不纯净的,但很小,但所有解释器的逻辑都是可测试的。

假设您的解释器将与用户交互,那么它不会在IO中运行吗?为什么不在解释语言的表示中也使用值来包装
IO
。如果您已经在处理不同类型的值,那么其中一种类型可能会包含IO值。这就是我打算如何建模用户交互以及其他形式的IO。目前,IO仅在初始模块加载阶段(发生并在执行之前完成)涉及。在这一点上,在口译过程中使用IO的改变似乎至少会失去在纯系统中工作的一些好处。在我不需要的时候避免IO似乎是一个有用的目标,如果我能分离出一个确实需要它的子系统,并证明该子系统仍然是引用透明的,有一个很好的理由不这样做吗?预测什么时候在惰性语言中强制使用thunk是非常棘手的——这就是为什么实际上没有惰性的不纯函数语言的原因。在正确的位置添加
seq
s可能没有看上去那么简单。此外,您可以为解释器编写一个模拟类型吗?它是
int::Program->Result
还是类似的东西?解释器的总体类型基本上是
Scope s->Expr->ST s(ErrorMessage Value)
,其中
Scope s
包含全局(可变)变量和程序中定义的函数/类的描述,
Expr
基本上是一个简单的引导表达式,它调用程序中的函数。这是我简单考虑过的一个选项,但是这个选项太复杂了。也就是说,如果只是因为它有一些有用的应用程序(例如,记录IO操作的跟踪,以便更容易地测试程序),那么它可能值得再考虑一段时间。至于我想要一个纯解释器的原因,我将在问题中添加一个编辑。好的,您需要不同的primops集,因此注入它们看起来是合理的。无论如何,不要让过度思考阻碍了你的尝试——不管怎样,你可能会多次重新设计和重构一切。