Haskell 打字无标记最终口译:复制有什么用?

Haskell 打字无标记最终口译:复制有什么用?,haskell,interpreter,Haskell,Interpreter,这个问题与论文有关。在第11页中,介绍了一个函数trice,该函数依赖于一个复制函数: thrice' :: (Int, (String, Tree)) -> IO () thrice' (reprInt, (reprStr, reprTree)) = do print $ eval reprInt print $ view reprStr print $ toTree reprTree 我尝试将其编码到Haskell中,结果函数如下所示: thrice

这个问题与论文有关。在第11页中,介绍了一个函数
trice
,该函数依赖于一个
复制
函数:

thrice' :: (Int, (String, Tree)) -> IO ()
thrice' (reprInt, (reprStr, reprTree)) = do
    print $ eval reprInt
    print $ view reprStr
    print $ toTree reprTree    

我尝试将其编码到Haskell中,结果函数如下所示:

thrice :: (Int, (String, Tree)) -> IO () -- Is this the most generic type we can give?
thrice x0 = do
    x1 <- dupConsume eval x0
    x2 <- dupConsume view x1
    print $ toTree x2
    where
      dupConsume ev y = do
          print (ev y0)
          return y1
              where
                (y0, y1) = duplicate y

所以我想知道在这个例子中,
duplicate
有什么用?

首先,作为旁白,请注意,那篇文章中的代码已经是有效的Haskell代码,只是使用了一些符号来代替通常的Haskell语法。例如,符号“◦" 用于代替函数合成的
(.)
运算符

因此,您可以直接根据本文中的定义,将
编写三次作为以下有效的Haskell代码:

thrice x = dup_consume eval x >>= dup_consume view 
             >>= print . toTree

dup_consume ev x = print (ev x1) >> return x2
  where (x1, x2) = duplicate x
无论如何,回到你的问题上来……正如你正确指出的那样,解释器
duplicate
没有真正的用途。例如,你可以将
dup\u consume
定义为上面的版本,或者完全删除
duplicate
,然后写下:

dup_consume ev x = print (ev x1) >> return x2
  where (x1, x2) = x
当然,您可以将
dup\u consume
的定义直接合并为
三次
,就像您所做的那样

然而,本文中所有的最终“解释者”都没有真正的目的。这就是重点。特别是,你不需要
eval
view
来定义
三次

thrice' :: (Int, (String, Tree)) -> IO ()
thrice' (reprInt, (reprStr, reprTree)) = do
  print $ reprInt
  print $ reprStr
  print $ reprTree
之后,您可以执行以下操作:

> thrice' (add (lit 5) (neg (lit 2)))
3
"(5 + (-2))"
Node "Add" [Node "Lit" [Leaf "5"],Node "Neg" [Node "Lit" [Leaf "2"]]]
> 
这些最终口译员的想法是打字决定口译。口译员的目的只是添加没有明确类型签名的打字信息。因此,
eval(neg(lit 1))
可以在GHCi提示符下输入,而不需要类型签名:

> eval (neg (lit 1))
-1
>
它之所以“起作用”,是因为
eval
——这只是
id
函数——强制返回值为整数,这反过来又选择正确的实例来计算最终表达式,而不是
查看它或其他什么。但您也可以编写:

> neg (lit 1) :: Int
-1
>
为了得到同样的效果

事实证明,
duplicate
比其他口译员更不必要,因为在唯一使用它的地方,即
dup\u consume
的定义:

dup_consume ev x = print (ev x1) >> return x2
  where (x1, x2) = duplicate x
类型检查器已经可以推断需要一个元组,因此为
x
提供的任何最终表达式,如
neg(lit 1)
,都必须被解释为元组的复制实例(即,在定义
replicate
之前定义的实例),因此如上所述,您可以编写:

dup_consume ev x = print (ev x1) >> return x2
  where (x1, x2) = x

类型检查器会找到答案。

我可能错了,但我怀疑您的
三次
函数可能涉及多次解析表达式,而Oleg的
复制
技巧只会复制解析树(即解析结果)

重复的需要源于对解析结果的模式匹配,它为匹配的解析结果分配了一个单态类型。因此,一旦你为它选择了一个解释器,你就会被它卡住。论文提到了一种更高的秩编码,以牺牲可扩展性来回收这种丢失的多态性,这违背了f无标记的最终口译员

另一种方法是
replicate
技巧,它将(单态)解析结果复制到另一个(单态)值中,以进行不同的解释


当然,如果解析始终成功(例如,通过直接在解析树中编码解析错误)这样就不需要重复了,因为解析结果可以保持多态性,并且可以进行不同的解释。

请不要嵌入文本图像。将文本复制为文本。感谢您的反馈。是否有任何原因说明嵌入图像不方便?嵌入图像对于使用屏幕阅读器或盲文显示的人来说是无用的是的。用移动浏览器很难阅读。而且也没有希望在浏览器中搜索文本。所以不要这样做。另外,我怀疑对于不容易支持多态值(如Rust)的语言,也需要使用
replicate
技巧。