递归下降解析器和函数式编程

2023-12-22

所以最近我一直致力于编写一个简单的编译器,以更好地理解编译器概念。作为 stackoverflow 的忠实读者,似乎有一个共识:用函数式语言编写编译器比命令式语言更容易。为此,我想尝试一下杀两只鸟,用 F# 编写一个编译器,既学习函数式语言,又同时编写编译器。

我一直在阅读龙书,并决定从用 F# 手工编写的递归下降解析器开始。然而,龙书几乎所有的代码示例都是命令式的。例如,匹配令牌函数的大部分工作都是通过副作用完成的。

所以我的问题是,更传统的函数式解析方法(即很少的副作用)会是什么样子?我知道 Haskell 编译器(GHC)是用 Haskell 编写的,但我希望有一个更小、更容易理解的代码示例。

其次,是否值得尝试采用函数式方法进行解析,或者函数式语言真正在中间代码的优化方面表现出色,而我只是还没有做到这一点?也就是说,我是否应该使用命令式风格来进行 F# 中的解析,然后再切换到更实用的方法?


答案源自这篇博客文章 http://fsharpnews.blogspot.com/2010/12/parsing-mathematical-expressions-using.html:

所以我的问题是,更传统的函数式解析方法(即很少的副作用)会是什么样子?

听起来你需要将功能性(如 Lisp、Scheme、Standard ML、CAML、OCaml、F#)与纯粹性(无副作用,如 Haskell)和附带语言功能(代数数据类型、模式匹配)分开。

得益于代数数据类型、模式匹配和高阶函数,F# 非常适合解析,也非常适合转换和代码生成,但大多数用 F# 编写的生产解析器都不是纯粹的。从历史上看,F# 的语言家族(元语言或 ML)主要源自专门为这种元编程而培育的语言。

这是一组非常简单的相互递归活动模式,用于解析和评估由单个数字组成的数学表达式,+ - *运算符和括号子表达式:

> let rec (|Term|_|) = function
    | Factor(e1, t) ->
        let rec aux e1 = function
          | '+'::Factor(e2, t) -> aux (e1 + e2) t
          | '-'::Factor(e2, t) -> aux (e1 - e2) t
          | t -> Some(e1, t)
        aux e1 t
    | _ -> None
  and (|Factor|_|) = function
    | '-'::Factor(e, t) -> Some(-e, t)
    | Atom(e1, '*'::Factor(e2, t)) -> Some(e1 * e2, t)
    | Atom(e, t) -> Some(e, t)
    | _ -> None
  and (|Atom|_|) = function
    | c::t when '0'<=c && c<='9' -> Some(int(string c), t)
    | '('::Term(e, ')'::t) -> Some(e, t)
    | _ -> None;;
val ( |Term|_| ) : char list -> (int * char list) option
val ( |Factor|_| ) : char list -> (int * char list) option
val ( |Atom|_| ) : char list -> (int * char list) option

这是一个用于解析和评估表达式的示例:

> let (Term e) = List.ofSeq "1+2*(3-4)*-5";;
val e : int * char list = (11, [])

这是一个纯粹的解决方案,使用 F# 的活动模式对列表进行模式匹配。实际上,您需要为抽象语法树定义一个类型并返回该类型的值。这在 F# 中非常简单:

type expr =
  | Int of int
  | Neg of expr
  | Add of expr * expr
  | Sub of expr * expr
  | Mul of expr * expr

  static member (~-) f = Neg f
  static member (+) (f, g) = Add(f, g)
  static member (-) (f, g) = Sub(f, g)
  static member (*) (f, g) = Mul(f, g)

let rec (|Term|_|) = function
  | Factor(e1, t) ->
      let rec aux e1 = function
        | '+'::Factor(e2, t) -> aux (e1 + e2) t
        | '-'::Factor(e2, t) -> aux (e1 - e2) t
        | t -> Some(e1, t)
      aux e1 t
  | _ -> None
and (|Factor|_|) = function
  | '-'::Factor(e, t) -> Some(-e, t)
  | Atom(e1, '*'::Factor(e2, t)) -> Some(e1 * e2, t)
  | Atom(e, t) -> Some(e, t)
  | _ -> None
and (|Atom|_|) = function
  | c::t when '0'<=c && c<='9' -> Some(Int(int(string c)), t)
  | '('::Term(e, ')'::t) -> Some(e, t)
  | _ -> None

let (Term e) = List.ofSeq "1+2*(3-4)*-5"

请注意,只需要对解析器进行一项微小的更改,因为 AST 也可以使用+, - and *运营商。

其次,是否值得尝试采用函数式方法进行解析,或者函数式语言真正在中间代码的优化方面表现出色,而我只是还没有做到这一点?

你说的是纯粹性,而不是函数式编程。纯度在解析文本的上下文中并不是特别有用,事实上,它可能是一个真正的障碍(例如,实习符号在 Haskell 中是一场噩梦)。然而,F# 还有许多其他优点可以很好地解决这组问题。特别是,尽管 OCaml 等其他语言有更好的解析工具,但我认为 F# 是在这种情况下最好的 .NET 语言。

也就是说,我是否应该使用命令式风格来进行 F# 中的解析,然后再切换到更实用的方法?

完全取决于你想要实现什么功能。我会使用 fslex 和 fsyacc 以及纯代码来在操作中构造 AST,但会使用哈希 consing 或生成唯一 ID 之类的杂质。

您可能会欣赏我在以下位置撰写的有关此主题的文章:这个博客 http://fsharpnews.blogspot.com(注意付费墙):

  • “使用 Lex 和 Yacc 解析文本”(2007 年 9 月 30 日)。
  • “优化简单的字节码解释器”(2007 年 10 月 31 日)。
  • “解析器组合器”(2007 年 11 月 30 日)。
  • “面向语言的编程:术语级解释器”(2007 年 12 月 31 日)。
  • “面向语言的编程:术语重写”(2008 年 8 月 16 日)。
  • “运行时代码生成使用System.Reflection.Emit”(2008 年 8 月 31 日)。
  • “解析和可视化二进制地理信息系统数据”(2009 年 11 月 30 日)。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

递归下降解析器和函数式编程 的相关文章

  • 为什么需要数字后缀?

    C 语言 我确信还有其他语言 需要在数字文字末尾添加后缀 这些后缀指示文字的类型 例如 5m是一个小数 5f是一个浮点数 我的问题是 这些后缀真的有必要吗 或者是否可以从上下文中推断出文字的类型 例如 代码decimal d 5 0应该推断
  • 使用 Reader Monad 进行依赖注入

    我最近看到了谈话极其简单的依赖注入 http www youtube com watch v ZasXwtTRkio and 无需体操的依赖注入 http vimeo com 44502327关于 Monads 的 DI 并留下了深刻的印象
  • 函数式语言中的部分求值和函数内联有什么区别?

    我知道 函数内联就是用函数定义代替函数调用 部分评估是在编译时评估程序的已知 静态 部分 在 C 等命令式语言中 两者之间存在区别 其中运算符与函数不同 但是 在像 Haskell 这样的函数式语言 其中运算符也是函数 中 两者之间有什么区
  • F# 和 MEF:导出函数

    因此 我试图在 F 控制台应用程序中运行这个简单的测试 open System Reflection open System ComponentModel Composition open System ComponentModel Com
  • F# 引用的另一个限制?

    今天早些时候 我遇到了 F 引用的限制 并在这里提出了一个问题 F 引号 变量可能会转义作用域 https stackoverflow com questions 6414185 f quotations variable may esca
  • true 和布尔列表 f# 的长度

    直接使用递归 写一个函数truesAndLength bool list gt int int那 返回列表的长度 在该对的第一个组件中 以及列表的数量 列表中正确的元素 在第二个组件中 你的函数必须只迭代 遍历列表的元素一次 请勿使用 Li
  • F# 中的自定义路由事件

    我正在尝试翻译这段 C 代码 https msdn microsoft com en us library ms752288 aspx 到目前为止我的尝试 type MyButtonSimple as self inherit Button
  • 相当于 Java 中 C++ 的 std::bind 吗?

    有没有一种方法可以像 C 中的 std bind 一样将 Java 中的参数绑定到函数指针 Java 中类似的东西会是什么 void PrintStringInt const char s int n std cout lt lt s lt
  • 如何从 C# 可移植类库 (PCL) 添加对 F# 可移植库的引用

    我有一个项目 其中包含两个 F 项目和一个 C 项目 我想在其中编写一些 XUnit 测试 FS PL F 3 1 3 3 1 0 可移植库 FS PL Legacy F 31 2 3 5 1 可移植库 旧版 测试 C NET 4 5 Wi
  • obj[] 和 string[] 作为参数

    我在用Microsoft FSharp Reflection FSharpValue MakeUnion这需要一个Reflection UnionCaseInfo and an obj 可以为空 作为参数 但是 我得到了Type misma
  • 如何在 F# 中进行卷积?

    我想convolve http en wikipedia org wiki Convolution具有离散滤波器的离散信号 信号和滤波器是 F 中的浮点数序列 我能弄清楚如何做到这一点的唯一方法是使用两个嵌套的 for 循环和一个可变数组来
  • 如何忽略异步块中异步函数的返回值?

    The m1 and m2以下函数中存在编译错误 let m p async return p 2 let m1 async do m 2 ERR was expected int but here has type unit let m2
  • 如何让一条记录实现一个接口?

    如果我有一个界面 type IData abstract member firstName string abstract member lastName string 如何定义符合此接口的记录类型 我尝试了如下所示 gt type Dat
  • 如何使用 WebSharper 在服务器上生成 Google Visualizations 数据

    我的目标是能够在服务器上为 Google Visualizations 生成数据 然后将其作为 java 脚本传递给客户端 以便可以将其呈现为折线图 我下面的示例可以正确编译 但在浏览器中呈现时会产生错误 在服务器上构建 DataCommo
  • Async.AwaitTask 在 f# 中如何工作?

    我知道 f 和 c 异步模型之间的主要区别在于 在 f 中 除非您调用 Async RunSynchronously 之类的内容 否则异步执行不会开始 在 C 中 当方法返回任务时 通常 并非总是 立即在后台线程中开始执行 Async Aw
  • 使用 elm 高阶函数处理键盘事件

    我正在尝试创建一个高阶函数来创建仅捕获特定关键代码的函数 该代码的灵感来自 EvanonEnter来自他的 todomvc 实现的函数 仅捕获 Enter 函数 onKeyCode Int gt Msg gt Attribute Msg o
  • IntSummaryStatistics的summaryStatistics方法

    为什么空 IntStream 上的 summaryStatistics 方法返回整数的最大和最小值作为流中存在的最大和最小 int 值 IntStream intStream IntStream of IntSummaryStatistic
  • 从静态成员访问 let 绑定字段

    有没有办法从静态成员访问 let 绑定字段 下面给出了指示的错误 type Foo x let x x static member test let foo Foo System DateTime Now Month printfn A f
  • 如何为 Azure Function 启用“始终开启”功能?

    我有一个具有 3 个功能的功能应用程序 其中一个功能每 2 分钟定时器触发一次 我观察到 过了一会儿 该功能停止被触发 但当我进入门户时又重新启动 据我了解 原因是默认情况下 始终开启 处于关闭状态 但是 当我进入应用程序设置 常规设置时
  • 图像分析-光纤识别

    我是图像分析新手 您知道如何以仅获取纤维的方式对该图像进行二值化吗 我尝试过不同的阈值技术等 但没有成功 我不介意应该使用什么工具 但我更喜欢 NET or Matlab PS 我不知道该把答案放在哪里 所以我把它放在StackOverfl

随机推荐

  • 如何调试从 TeamCity 部署的 nuget 包?

    我已将我的团队使用的库放入 nuget 包中 该包从 TeamCity 部署到网络文件夹中 但我无法调试这段代码 SymbolSource 是我读过的一种解决方案 但我更愿意找到某种方法来直接从 TeamCity 访问 pdb 源文件 有谁
  • .NET解决方案下的部署工具

    我们都使用 Web 应用程序 Windows 应用程序 数据库 帮助文件 配置文件和注册表值编写代码 无论是小型 例如一个 exe 还是大型应用程序 完整的解决方案 我的问题很简单 在我看来 现在我需要在一个安装设置中部署一个 Web 应用
  • UILocalNotification 未触发

    I am very对 Cocoa Touch 和 Objective C 很陌生 但我已经掌握了相当重要的要点 并且正在尝试使用 UIKit 我有一个链接到按钮的操作 该按钮更改标签并触发 UILocalNotification 这是操作方
  • 将“puts”命令输出重定向到日志文件

    我正在使用 daemons gem 在 Ruby 中创建一个守护进程 我想将守护程序的输出添加到日志文件中 我想知道重定向的最简单方法是什么puts从控制台到日志文件 如果您需要捕获 STDERR 和 STDOUT 并且不想诉诸日志记录 s
  • .Net Excel Interop 删除工作表

    我正在尝试使用互操作 Excel 类 适用于 excel 2003 从 Net c 3 5 应用程序的 Excel 文档中删除工作表 我尝试了很多事情 例如 Worksheet worksheet Worksheet workbook Wo
  • 如何在Supervisor服务中设置环境变量

    如何在Supervisor执行的命令中导出环境变量 我首先尝试 command export SITE domain1 python manage py command 但主管报告 找不到命令 然后我尝试了 command bin bash
  • 在react中从json获取本地图像路径

    如何选择图片的本地路径 Json avatarUrl avt1 jpg 所有图片都在下面来源 gt img文件夹 我正在寻找 json 中的绝对路径 图像名称 我怎样才能实现这个目标reactJs img src width 60 包 js
  • 在ubuntu上编译protobuf客户端代码,但找不到包含文件

    我刚刚在我的 ubuntu1604 上安装了 google protocol buffer sudo apt install protobuf compiler 并尝试了快速测试 1个proto文件 1个cpp文件来使用它 尝试查看编码 解
  • Launch4J 插件创建一个 EXE(以及 JAR),但 EXE 在 Spring boot 中找不到主类

    我编写了一个插件 在 Launch4J 插件的帮助下为我的项目创建 EXE 和 JAR 但是 在执行 EXE 文件时 我收到错误 Error Could not find or load main class 但是 我通过提供来运行 JAR
  • 在 Android 上通过 bash 脚本启用/禁用 wifi

    我正在尝试在 bash 脚本中启用 禁用 Android 设备中的 wifi 设备 我正在使用终端仿真器和程序脚本管理器在手机 是 root 的 Nexus One 上执行 bash 脚本 在linux中执行此操作的正常方法是这样的 ifc
  • 我在哪里可以找到一些“hello world”-简单的美丽汤示例?

    我想用 Beautiful Soup 做一个非常简单的替换 假设我想访问页面中的所有 A 标记并将 foo 附加到它们的 href 中 有人可以发布或链接到如何做这样简单的事情的示例吗 from BeautifulSoup import B
  • function_exists 返回 false 但声明抛出错误

    在 PHP 5 3 6 中 我有一个类 其方法如下 public function chunkText if function exists unloadChunkText function unloadChunkText 其中 unloa
  • java中浮点数和双精度数有多少位有效数字?

    float 是否有 32 位二进制数字 double 是否有 64 位二进制数字 该文档太难理解了 所有位都转换为有效数字吗 还是小数点的位置占用了一些位 float 32 bits 4 bytes where 23 bits are us
  • Javascript 中“new”关键字的限制

    我有这个JS代码 var A A new function n return new Array n 它在所有浏览器中都运行良好 但是当我尝试用它来混淆它时混淆器 http javascriptobfuscator com 它显示一个错误
  • 从另一个 Dart 程序运行交互式 Dart 程序

    我有一个相当长的命令行程序 需要用户输入参数 然后使用这些参数进行处理 我想做的是将程序分为交互式和非交互式 我尝试这样做 并打算让非交互式程序 调用 交互式程序并使用结果 参数 根据这些参数进行处理 程序的非交互部分在处理时将结果显示在控
  • 无法查看 Xcode 4.2 帮助“index.html”被锁定以进行编辑

    当我尝试在 Xcode 中搜索文档时 出现以下错误 index html 已被锁定进行编辑 您可能无法保存更改 你想解锁它吗 index html 目前已被锁定 因为它不支持编辑 文件 index html 无法解锁 无法向该文件添加写入权
  • AWS RedShift - .NET Core(ODBC 支持?)

    如何使用 NET Core 连接 AWS RedShift 并运行查询 请提供代码示例 我已经阅读了 AWS 文档和 Net Core 文档 但没有运气 这个答案是针对特定时间点的 不会过时 EntityFramework Core 项目是
  • Google Map APi 缩放栏未显示

    Google 地图 api 没有完全显示缩放栏和图像类型 而是仅显示用于放大和缩小的加减按钮以及用于选择地图类型的下拉按钮 网址是http booking smanager net design index php lv 2 http bo
  • Python - 获取浏览器重定向到的url

    我正在尝试使用 API 验证应用程序 就是这样 我正在使用打开一个 URLwebbrowser open 用户对应用程序进行身份验证 并被重定向到另一个 URL 即 https stackexchange com oauth login s
  • 递归下降解析器和函数式编程

    所以最近我一直致力于编写一个简单的编译器 以更好地理解编译器概念 作为 stackoverflow 的忠实读者 似乎有一个共识 用函数式语言编写编译器比命令式语言更容易 为此 我想尝试一下杀两只鸟 用 F 编写一个编译器 既学习函数式语言