提交 31e34f17 编写于 作者: W wizardforcel

2022-01-08 19:04:01

上级 f2467a6f
......@@ -792,7 +792,7 @@ public Guid ProjectId { get; set; }
在面向对象的语言中,我们有类继承,这是关系数据库所没有的。我们如何将它存储在关系数据库中?
马丁·福勒在他的开创性著作《企业应用程序架构的模式》中描述了在关系数据库中保持类层次结构的三种模式:
马丁·福勒在他的开创性著作《企业应用架构的模式》中描述了在关系数据库中保持类层次结构的三种模式:
1. 单表继承或每个类层次结构的表:单个表用于表示整个层次结构;它包含所有类的所有映射属性的列。其中许多将为空,因为它们将只存在于一个特定的类中;一个区别列将存储一个值,该值将告诉实体框架特定记录将映射到哪个类。
......
......@@ -182,7 +182,7 @@ ctx.Entry(project).Reload();
### 概述
乐观并发控制是一种使用数据库的方法,它假设多个事务可以在不相互影响的情况下完成;不需要锁定。提交记录时,每个事务都将检查数据库中的记录是否已被修改,并将失败。这对于在 web 应用程序的上下文中处理对数据的多次访问非常有用。
乐观并发控制是一种使用数据库的方法,它假设多个事务可以在不相互影响的情况下完成;不需要锁定。提交记录时,每个事务都将检查数据库中的记录是否已被修改,并将失败。这对于在 web 应用的上下文中处理对数据的多次访问非常有用。
有两种方法可以处理数据被更改的情况:
......@@ -297,7 +297,7 @@ modelBuilder.Entity<Project>().Property(x => x.RowVersion).IsRowVersion();
## 分离的实体
web 应用程序中的一个常见场景是这样的:从数据库中加载某个实体,将其存储在会话中,然后在后续请求中,从那里获取它并继续使用它。这一切都很好,除了,如果您使用的是实体框架代码优先,您将不会在两个请求上使用相同的上下文实例。这个新的上下文对这个实例一无所知。在这种情况下,据说实体相对于上下文是分离的。这样做的效果是,不会跟踪对此实例的任何更改,也不会加载在会话中存储时未加载的任何延迟加载属性。
web 应用中的一个常见场景是这样的:从数据库中加载某个实体,将其存储在会话中,然后在后续请求中,从那里获取它并继续使用它。这一切都很好,除了,如果您使用的是实体框架代码优先,您将不会在两个请求上使用相同的上下文实例。这个新的上下文对这个实例一无所知。在这种情况下,据说实体相对于上下文是分离的。这样做的效果是,不会跟踪对此实例的任何更改,也不会加载在会话中存储时未加载的任何延迟加载属性。
我们需要做的是将这个实例与新的上下文相关联。
......
......@@ -2,7 +2,7 @@
## 概述
我们生活在一个互联的世界里,如今分布式应用程序很常见。微软。NET 堆栈提供了一些有趣的技术,通过利用实体框架向外部世界公开实体。在接下来的几页中,我们将讨论两种 web 服务技术和一种动态 CRUD 接口技术。
我们生活在一个互联的世界里,如今分布式应用很常见。微软。NET 堆栈提供了一些有趣的技术,通过利用实体框架向外部世界公开实体。在接下来的几页中,我们将讨论两种 web 服务技术和一种动态 CRUD 接口技术。
[WCF 数据服务](http://msdn.microsoft.com/en-us/library/cc668792.aspx)是微软实施的 [OData](http://www.odata.org) 标准。基本上,这是发布、查询和交换来自实体的数据的标准,例如 REST web 服务,它是在 WCF 之上实现的。您可以通过 URL 发出查询,甚至可以生成使用 LINQ 查询发布模型的强类型代理。
......
......@@ -77,7 +77,7 @@ var allProjectsSQL = ctx.Projects.ToSqlString();
## 微型先验仪
[MiniProfiler](http://miniprofiler.com/) 是一个开源项目,为 ASP.NET MVC 和实体框架提供了一个代码分析器。我将演示如何在 MVC 项目中使用它,但是除了 MVC 控制台之外,它还可以用在 Windows 窗体、控制台甚至 Web 窗体应用程序中。
[MiniProfiler](http://miniprofiler.com/) 是一个开源项目,为 ASP.NET MVC 和实体框架提供了一个代码分析器。我将演示如何在 MVC 项目中使用它,但是除了 MVC 控制台之外,它还可以用在 Windows 窗体、控制台甚至 Web 窗体应用中。
要使用它,需要先从 [NuGet](http://www.nuget.org/packages/MiniProfiler/) 获取它的核心包。
......@@ -161,7 +161,7 @@ var parameters = timings.First().Parameters;
图 57: SQL Server 事件探查器输出
您注意到“应用程序名称”栏了吗?实体框架总是将应用程序名称设置为“EntityFrameworkMUE”,这样您总是可以知道它发送了哪些 SQL 查询,并且您可以在 SQL Server Profiler 上创建一个过滤器来仅显示这些条目。如果需要,可以通过在连接字符串中提供应用程序名称参数来指定不同的应用程序名称。
您注意到“应用名称”栏了吗?实体框架总是将应用名称设置为“EntityFrameworkMUE”,这样您总是可以知道它发送了哪些 SQL 查询,并且您可以在 SQL Server Profiler 上创建一个过滤器来仅显示这些条目。如果需要,可以通过在连接字符串中提供应用名称参数来指定不同的应用名称。
```cs
<connectionStrings>
......
......@@ -7,7 +7,7 @@
* EFCF 假设了 dbo 的数据库模式,这在其他数据库中是不存在的,所以我们被迫在每个实体的映射中指定一个模式。
* 一些。其他一些数据库不支持. NET 类型,例如枚举类型、Guid、[数据库几何](http://msdn.microsoft.com/en-us/library/system.data.spatial.dbgeometry.aspx)[数据库地理](http://msdn.microsoft.com/en-us/library/system.data.spatial.dbgeography.aspx)
* 数据库数据类型,即使概念上相同,也有不同的名称。例如,SQL Server 中的可变长度 Unicode 字符串称为 NVARCHAR,而在 Oracle 中则称为 VARCHAR2。需要指定时要小心。
* 一些等价类型略有不同。例如,在 SQL Server 中,dateTIME 属性可以转换为同时具有日期和时间部分的 DateTime 列,但是在 Oracle 中,当转换为 DATE 列时,它将只具有日期部分。另一个例子是,DateTimeOffset 甚至在 Oracle 和其他应用程序中也有对等项,但在 SQL Server 2005 上没有。
* 一些等价类型略有不同。例如,在 SQL Server 中,dateTIME 属性可以转换为同时具有日期和时间部分的 DateTime 列,但是在 Oracle 中,当转换为 DATE 列时,它将只具有日期部分。另一个例子是,DateTimeOffset 甚至在 Oracle 和其他应用中也有对等项,但在 SQL Server 2005 上没有。
* 用于并发检查的 [TimestampAttribute](http://technet.microsoft.com/en-us/library/ms182776.aspx) 所隐含的 [ROWVERSION](http://technet.microsoft.com/en-us/library/ms182776.aspx) 类型,或者更好地说,它的工作方式,也只存在于 SQL Server 系列中。
* 浮点或十进制类型可能有不同的精度。
* 最后但同样重要的是,不要期望数据库生成能够工作:它不会工作,这意味着你只能靠自己。
......
......@@ -262,7 +262,7 @@ HttpContext 是巨大的。如果你需要伪造的不仅仅是对 HttpContext
## 伪造原则
到目前为止,我们已经了解了如何伪造`HttpContextBase``HttpRequestBase``HttpResponseBase``HttpSessionStateBase`。这涵盖了您将在 MVC 控制器中使用的大量代码类型,但是 MVC 应用程序中有一个很大的部分我们还没有谈到,那就是身份验证和授权。让我们看看如何在控制器的动作方法中单元测试使用`IPrincipal`的代码。
到目前为止,我们已经了解了如何伪造`HttpContextBase``HttpRequestBase``HttpResponseBase``HttpSessionStateBase`。这涵盖了您将在 MVC 控制器中使用的大量代码类型,但是 MVC 应用中有一个很大的部分我们还没有谈到,那就是身份验证和授权。让我们看看如何在控制器的动作方法中单元测试使用`IPrincipal`的代码。
| ![](img/note.png) | 注意:你可以在这里找到更多关于 IPrincipal [的信息。](https://msdn.microsoft.com/en-us/library/system.security.principal.iprincipal(v=vs.110).aspx) |
......
# 前言
# 零、前言
## 使用代码示例
这本书非常依赖代码示例来表达 F#概念。代码样本可在[https://bitbucket.org/syncfusion/fsharp-succinctly](https://bitbucket.org/syncfusion/fsharp-succinctly)获得。
这本书非常依赖代码示例来表达 F# 概念。代码样本可在[https://bitbucket.org/syncfusion/fsharp-succinctly](https://bitbucket.org/syncfusion/fsharp-succinctly)获得。
代码示例作为单独的 Visual Studio F#项目文件提供。样本按章节组织,并以各自章节的小标题命名。
代码示例作为单独的 Visual Studio F# 项目文件提供。样本按章节组织,并以各自章节的小标题命名。
大多数示例都是控制台应用程序。在 Visual Studio 中,如果以调试模式(F5)运行应用程序,控制台窗口会弹出并立即关闭。要查看样本结果,使用**启动**而不调试(Ctrl+F5)。这将在控制台应用程序的末尾添加一个*按任意键继续*提示,允许您通过按任意键关闭控制台窗口。
\ No newline at end of file
大多数示例都是控制台应用。在 Visual Studio 中,如果以调试模式(F5)运行应用,控制台窗口会弹出并立即关闭。要查看样本结果,使用**启动**而不调试(Ctrl+F5)。这将在控制台应用的末尾添加一个*按任意键继续*提示,允许您通过按任意键关闭控制台窗口。
\ No newline at end of file
# 第一章简介
# 一、简介
这一介绍性章节将解决一些你可能对 F#和函数式编程(FP)有疑问的主要问题。
这一介绍性章节将解决一些你可能对 F# 和函数式编程(FP)有疑问的主要问题。
## 什么是函数式编程?
纯函数式编程将所有程序视为接受参数和返回值的函数的集合。与命令式和面向对象编程不同,它不允许任何副作用,并且使用递归代替循环进行迭代。函数程序中的函数非常像数学函数,因为它们不会改变程序的状态。最简单地说,一旦一个值被赋给一个标识符,它就不会改变,函数也不会改变参数值,函数返回的结果是全新的值。在典型的底层实现中,一旦一个值被分配给内存中的某个区域,它就不会改变。为了创建结果,函数复制值,然后更改副本,留下原始值供其他函数自由使用,并最终在不再需要时丢弃。(这就是垃圾收集的想法起源的地方。)
纯函数式编程的数学基础是优雅的,因此 FP 为许多计算问题提供了漂亮、简洁的解决方案,但它的无状态和递归性质使其他范式便于处理许多常见的编程任务。然而,F#的一个很大的优势是,你可以使用多种范式,并将它们混合起来,以你认为最方便的方式解决问题。
纯函数式编程的数学基础是优雅的,因此 FP 为许多计算问题提供了漂亮、简洁的解决方案,但它的无状态和递归性质使其他范式便于处理许多常见的编程任务。然而,F# 的一个很大的优势是,你可以使用多种范式,并将它们混合起来,以你认为最方便的方式解决问题。
## 为什么函数式编程很重要?
......@@ -22,38 +22,38 @@
## 什么是 F#?
函数式编程是解决许多棘手计算问题的最佳方法,但纯 FP 通常不适合通用编程。正因为如此,FP 语言已经逐渐接受了命令式和 OO 范例的某些方面,保持了对 FP 范例的忠实,但是包含了轻松编写任何类型程序所需的特性。F#是这条道路上的天然接班人。它也不仅仅是一种 FP 语言。
函数式编程是解决许多棘手计算问题的最佳方法,但纯 FP 通常不适合通用编程。正因为如此,FP 语言已经逐渐接受了命令式和 OO 范例的某些方面,保持了对 FP 范例的忠实,但是包含了轻松编写任何类型程序所需的特性。F# 是这条道路上的天然接班人。它也不仅仅是一种 FP 语言。
一些最流行的函数式语言,包括 OCaml、Haskell、Lisp 和 Scheme,传统上都是使用定制运行时实现的,这导致了缺乏互操作性等问题。F#是一种通用编程语言。NET,一个通用运行时。F#平滑地集成了所有三种主要的编程范例。使用 F#,您可以选择最能有效解决问题的范式。如果你是一个纯粹主义者,你可以做纯函数式编程,但是你可以很容易地在同一个程序中结合函数式、命令式和面向对象的风格,并利用每种范式的优势。像其他类型化函数语言一样,F#是强类型化的,但也使用推断类型,因此程序员不需要花时间明确指定类型,除非存在歧义。此外,F#与无缝集成。NET 框架基类库。在 F#中使用 BCL 就像在 C#或 Visual Basic 中使用它一样简单(甚至可能更简单)。
一些最流行的函数式语言,包括 OCaml、Haskell、Lisp 和 Scheme,传统上都是使用定制运行时实现的,这导致了缺乏互操作性等问题。F# 是一种通用编程语言。NET,一个通用运行时。F# 平滑地集成了所有三种主要的编程范例。使用 F#,您可以选择最能有效解决问题的范式。如果你是一个纯粹主义者,你可以做纯函数式编程,但是你可以很容易地在同一个程序中结合函数式、命令式和面向对象的风格,并利用每种范式的优势。像其他类型化函数语言一样,F# 是强类型化的,但也使用推断类型,因此程序员不需要花时间明确指定类型,除非存在歧义。此外,F# 与无缝集成。NET 框架基类库。在 F# 中使用 BCL 就像在 C#或 Visual Basic 中使用它一样简单(甚至可能更简单)。
F#是以 Objective Caml (OCaml)为模型的,OCaml 是一种成功的面向对象的函数式编程语言,然后经过调整和扩展,在技术上和哲学上与. NET 很好地结合在一起。NET,并使用户能够做任何事情。NET 允许。F#编译器可以编译它所支持的公共语言基础设施的所有实现。NET 泛型,它甚至提供了内联中间语言代码。F#编译器不仅为任何命令行界面生成可执行文件,还可以在任何有命令行界面的环境下运行,这意味着 F#不限于 Windows,而是可以在 Linux、苹果的 OS X 和 iOS 以及谷歌的安卓操作系统上运行。
F# 是以 Objective Caml (OCaml)为模型的,OCaml 是一种成功的面向对象的函数式编程语言,然后经过调整和扩展,在技术上和哲学上与. NET 很好地结合在一起。NET,并使用户能够做任何事情。NET 允许。F# 编译器可以编译它所支持的公共语言基础设施的所有实现。NET 泛型,它甚至提供了内联中间语言代码。F# 编译器不仅为任何命令行界面生成可执行文件,还可以在任何有命令行界面的环境下运行,这意味着 F# 不限于 Windows,而是可以在 Linux、苹果的 OS X 和 iOS 以及谷歌的安卓操作系统上运行。
F# 2.0 编译器与 Visual Studio 2012、Visual Studio 2010 一起分发,并作为 Visual Studio 2008 的插件提供。它支持智能感知表达式完成和自动表达式检查。它还提供工具提示来显示已经为表达式推断了哪些类型。程序员经常评论说,这真的有助于将语言带入生活。F# 2.0 也有一个开源版本,根据 Apache 许可证授权,可从[http://github.com/fsharp](http://github.com/fsharp)获得。
F#首先由剑桥微软研究院(MSR)的唐·赛姆实现。该项目现已被华盛顿州雷德蒙的微软公司接受,编译器和 Visual Studio 集成的实现现已由位于剑桥和雷德蒙的团队开发。在撰写本文时,团队专注于实现 F# 3.0,它在 Visual Studio“dev 11”测试版中可用。
F# 首先由剑桥微软研究院(MSR)的唐·赛姆实现。该项目现已被华盛顿州雷德蒙的微软公司接受,编译器和 Visual Studio 集成的实现现已由位于剑桥和雷德蒙的团队开发。在撰写本文时,团队专注于实现 F# 3.0,它在 Visual Studio“dev 11”测试版中可用。
虽然其他 FP 语言运行在。NET,F#已经确立了自己作为事实上的。NET 函数式编程语言,因为它的实现质量及其与。NET 和 Visual Studio。
虽然其他 FP 语言运行在。NET,F# 已经确立了自己作为事实上的。NET 函数式编程语言,因为它的实现质量及其与。NET 和 Visual Studio。
没有其他的。NET 语言和 F#一样好用,一样灵活!
没有其他的。NET 语言和 F# 一样好用,一样灵活!
## 谁在用 F#?
F#在微软内部有着强大的影响力,无论是在 MSR 还是整个公司。拉尔夫·赫布里奇是 MSR 应用游戏集团的领导者,该集团专门研究机器学习技术,是 F#越来越多粉丝的典型代表:
F# 在微软内部有着强大的影响力,无论是在 MSR 还是整个公司。拉尔夫·赫布里奇是 MSR 应用游戏集团的领导者,该集团专门研究机器学习技术,是 F# 越来越多粉丝的典型代表:
第一个应用程序是解析分布在 300 多个目录中的 11,000 多个文本文件中的 110GB 日志数据,并将其导入到一个 SQL 数据库中。整个应用程序长达 90 行(包括注释!)并在 18 小时内完成了解析源文件和导入数据的任务;这样算下来,每秒钟要处理惊人的 10,000 个日志行!请注意,我根本没有优化代码,而是以最明显的方式编写了应用程序。我真的很惊讶,因为我计划了至少一周的时间来编写和运行应用程序
第一个应用是解析分布在 300 多个目录中的 11,000 多个文本文件中的 110GB 日志数据,并将其导入到一个 SQL 数据库中。整个应用长达 90 行(包括注释!)并在 18 小时内完成了解析源文件和导入数据的任务;这样算下来,每秒钟要处理惊人的 10,000 个日志行!请注意,我根本没有优化代码,而是以最明显的方式编写了应用。我真的很惊讶,因为我计划了至少一周的时间来编写和运行应用
第二个应用是对数百万条反馈的分析。我们已经开发了模型方程,我只是把它们作为 F#程序输入;加上从 SQL 数据库读取数据和将结果写入 MATLAB 数据文件,F#源代码有 100 行长(包括注释)。再次,我对运行时间感到惊讶;在一台标准的台式计算机上,数百万个数据项的整个处理过程需要 10 分钟。我的 C#参考应用程序(来自一些早期的任务)几乎有 1000 行长,速度也并不快。从开发模型方程到获得第一个真实世界的数据结果,整个工作花了两天时间。
第二个应用是对数百万条反馈的分析。我们已经开发了模型方程,我只是把它们作为 F# 程序输入;加上从 SQL 数据库读取数据和将结果写入 MATLAB 数据文件,F# 源代码有 100 行长(包括注释)。再次,我对运行时间感到惊讶;在一台标准的台式计算机上,数百万个数据项的整个处理过程需要 10 分钟。我的 C#参考应用(来自一些早期的任务)几乎有 1000 行长,速度也并不快。从开发模型方程到获得第一个真实世界的数据结果,整个工作花了两天时间。
拉尔夫·赫布里奇,微软研究院(http://blogs.msdn.com/dsyme/archive/2006/04/01/566301.aspx T2)
微软之外的 F#使用也在快速增长。我问了 Cyfeon Solutions 的 Chance Coble,关于 F#给他的工作带来了什么。
微软之外的 F# 使用也在快速增长。我问了 Cyfeon Solutions 的 Chance Coble,关于 F# 给他的工作带来了什么。
F#一遍又一遍地向我陈述它的理由。我决定尝试的第一个项目是机器视觉努力,它将从提交的指纹卡中识别和提取指纹,并将它们加载到生物识别系统中。项目计划是手动执行指纹提取,这变得越来越麻烦,自动化结果是一个巨大的胜利(用很少的代码)。后来我们决定将 F#工作包含在一个用 C#编写的更大的应用程序中,并轻松地完成了集成。从那以后,我在机器学习、特定领域语言设计、三维可视化、符号分析和任何需要高性能数据处理的项目中使用了 F#。能够轻松地将功能模块集成到现有的生产规模应用程序中,这使得 F#不仅很有趣,而且是项目领导的重要补充。用一个成熟而丰富的平台统一函数式编程。NET 开辟了大量的机会。
F# 一遍又一遍地向我陈述它的理由。我决定尝试的第一个项目是机器视觉努力,它将从提交的指纹卡中识别和提取指纹,并将它们加载到生物识别系统中。项目计划是手动执行指纹提取,这变得越来越麻烦,自动化结果是一个巨大的胜利(用很少的代码)。后来我们决定将 F# 工作包含在一个用 C#编写的更大的应用中,并轻松地完成了集成。从那以后,我在机器学习、特定领域语言设计、三维可视化、符号分析和任何需要高性能数据处理的项目中使用了 F#。能够轻松地将功能模块集成到现有的生产规模应用中,这使得 F# 不仅很有趣,而且是项目领导的重要补充。用一个成熟而丰富的平台统一函数式编程。NET 开辟了大量的机会。
Cyfeon Solutions 首席技术官 Chance Coble(私人电子邮件)
## 这本书是给谁的?
这本书主要面向想要在 F#上快速上手的 IT 专业人士。工作知识。NET 框架和一些 C#或 Visual Basic 的知识会很好,但这不是必需的。你真正需要的只是一些用任何语言编程的经验,这样你才能舒服地学习 F#。
这本书主要面向想要在 F# 上快速上手的 IT 专业人士。工作知识。NET 框架和一些 C#或 Visual Basic 的知识会很好,但这不是必需的。你真正需要的只是一些用任何语言编程的经验,这样你才能舒服地学习 F#。
即使是完全的初学者,他们以前从未编程过,并且正在学习 F#作为他们的第一种计算机语言,也应该会发现这本书非常易读。虽然它本身并不试图教授入门编程,但它确实仔细呈现了 F#的所有重要细节。
\ No newline at end of file
即使是完全的初学者,他们以前从未编程过,并且正在学习 F# 作为他们的第一种计算机语言,也应该会发现这本书非常易读。虽然它本身并不试图教授入门编程,但它确实仔细呈现了 F# 的所有重要细节。
\ No newline at end of file
# 第二章 F#的第一步
# 二、F# 的第一步
本章将重点介绍一些关于 F#语言及其编程环境的一般介绍性细节。接下来的三章将集中在充实语言的细节,而这一章将只是提供一个可以做的尝试。所以不要担心,如果你不理解本章中看到的例子的所有细节,本书的其余部分会把它们填满。
本章将重点介绍一些关于 F# 语言及其编程环境的一般介绍性细节。接下来的三章将集中在充实语言的细节,而这一章将只是提供一个可以做的尝试。所以不要担心,如果你不理解本章中看到的例子的所有细节,本书的其余部分会把它们填满。
## 获取并安装 F#
使用 F#最简单快捷的方法是使用微软的 Visual Studio。F#包含在 Visual Studio 2012 和 2010 中。如果没有 Visual Studio 的副本,可以从[http://www.microsoft.com/visualstudio/try](http://www.microsoft.com/visualstudio/try)下载 90 天免费试用版。
使用 F# 最简单快捷的方法是使用微软的 Visual Studio。F# 包含在 Visual Studio 2012 和 2010 中。如果没有 Visual Studio 的副本,可以从[http://www.microsoft.com/visualstudio/try](http://www.microsoft.com/visualstudio/try)下载 90 天免费试用版。
默认情况下,Visual Studio 2012 和 2010 都安装了 F#,所以只需安装带有默认选项的 Visual Studio 就足够了。如果您安装了 Visual Studio,但 F#不可用,则可能是在安装 Visual Studio 时停用了 F#。要激活 F#,打开**控制面板**,进入**程序**菜单。
默认情况下,Visual Studio 2012 和 2010 都安装了 F#,所以只需安装带有默认选项的 Visual Studio 就足够了。如果您安装了 Visual Studio,但 F# 不可用,则可能是在安装 Visual Studio 时停用了 F#。要激活 F#,打开**控制面板**,进入**程序**菜单。
如果你不想在 Visual Studio 中使用 F#的话,你可以在[http://www.microsoft.com/download/en/details.aspx?id=11100](http://www.microsoft.com/download/en/details.aspx?id=11100)从微软下载一个命令行编译器,用你喜欢的文本编辑器编辑 F#源文件。因为我认为 Visual Studio 是初学者体验 F#的最佳方式,所以本章的其余部分将假设您使用的是 Visual Studio,尽管所有示例都将与编译器的命令行版本一起工作。
如果你不想在 Visual Studio 中使用 F# 的话,你可以在[http://www.microsoft.com/download/en/details.aspx?id=11100](http://www.microsoft.com/download/en/details.aspx?id=11100)从微软下载一个命令行编译器,用你喜欢的文本编辑器编辑 F# 源文件。因为我认为 Visual Studio 是初学者体验 F# 的最佳方式,所以本章的其余部分将假设您使用的是 Visual Studio,尽管所有示例都将与编译器的命令行版本一起工作。
## 你好世界
按照惯例,让我们从 F#中的“hello world”节目开始。首先,我们需要创建一个 Visual Studio 项目来托管我们的程序。为此,导航至**文件** > **新建** > **项目……**并选择一个 **F#应用程序**
按照惯例,让我们从 F# 中的“hello world”节目开始。首先,我们需要创建一个 Visual Studio 项目来托管我们的程序。为此,导航至**文件** > **新建** > **项目……**并选择一个 **F# 应用**
| ![](img/note.png) | 注意:F#仅附带四个预安装的应用程序或库模板。然而,网上有更多的模板。这些在线模板由微软的 F#团队和 F#社区共同提供。它们可以通过 Visual Studio 的“新建项目”对话框进行搜索和安装。 |
| ![](img/note.png) | 注意:F# 仅附带四个预安装的应用或库模板。然而,网上有更多的模板。这些在线模板由微软的 F# 团队和 F# 社区共同提供。它们可以通过 Visual Studio 的“新建项目”对话框进行搜索和安装。 |
删除 **program.fs** 文件中的内容,并输入以下行:
......@@ -23,7 +23,7 @@
```
现在按 F5 编译并执行程序,您会看到控制台短暂弹出“Hello World”问候语。请注意程序只有一行长——这是 F#哲学的一部分,代码应该尽可能不受语法混乱的影响,您会发现这是许多函数式编程语言共有的哲学。我们只是希望能够调用`System.Console.WriteLine`方法并传递给它一个字符串,所以这是我们需要的程序的仅有的两个元素。
现在按 F5 编译并执行程序,您会看到控制台短暂弹出“Hello World”问候语。请注意程序只有一行长——这是 F# 哲学的一部分,代码应该尽可能不受语法混乱的影响,您会发现这是许多函数式编程语言共有的哲学。我们只是希望能够调用`System.Console.WriteLine`方法并传递给它一个字符串,所以这是我们需要的程序的仅有的两个元素。
因为程序在问候被写入控制台后直接退出,所以问候文本在屏幕上的时间可能太短,我们看不到。让我们通过从控制台读取一行来解决这个问题,以便程序在按下回车键之前不会退出:
......@@ -37,9 +37,9 @@
现在再次按下 F5 键。这次执行程序时,问候语会一直保留,直到您按回车键,程序退出。注意我们如何使用`open`关键字打开`System`命名空间。这允许我们从`Console`类名的开头移除`System`,编译器仍然能够找到该类,因为它现在将在`System`命名空间中寻找它。`open`关键字在用于导入名称空间时与 C#中的`using`关键字非常相似。
## 使用 F#交互
## 使用 F# 交互
Visual Studio 附带了一个名为 F# Interactive 的 F#交互版本。这有时被称为读取-评估-打印循环,简称 REPL。它给了 F#一种动态语言的感觉,因为程序员能够交互式地评估他或她的程序的各个部分,并立即看到结果,尽管应该注意的是,F# Interactive 动态编译您传递给它的代码部分,因此您应该看到与编译后的 F#代码相似的性能水平。要使用 F# Interactive,只需突出显示要评估的代码部分,然后按 Alt+Enter。然后,您将在通常位于屏幕底部的 F#交互式窗口中看到这段代码的打印结果。因此,如果我们突出显示最初的“hello world”程序并按 Alt+Enter,我们会看到以下结果:
Visual Studio 附带了一个名为 F# Interactive 的 F# 交互版本。这有时被称为读取-评估-打印循环,简称 REPL。它给了 F# 一种动态语言的感觉,因为程序员能够交互式地评估他或她的程序的各个部分,并立即看到结果,尽管应该注意的是,F# Interactive 动态编译您传递给它的代码部分,因此您应该看到与编译后的 F# 代码相似的性能水平。要使用 F# Interactive,只需突出显示要评估的代码部分,然后按 Alt+Enter。然后,您将在通常位于屏幕底部的 F# 交互式窗口中看到这段代码的打印结果。因此,如果我们突出显示最初的“hello world”程序并按 Alt+Enter,我们会看到以下结果:
你好世界
......@@ -47,13 +47,13 @@ Visual Studio 附带了一个名为 F# Interactive 的 F#交互版本。这有
第一行是输出到控制台的问候。第二个是关于程序类型的一些细节——暂时不要太担心这个。类型将在后面的章节中解释。
能够交互执行这样的代码是我最喜欢的 F#特性之一。我认为能够快速尝试这样的想法是一种真正的生产力提升。所以让我们继续看看你可以用 F# Interactive 做的一些其他事情,比如创建交互式图表。
能够交互执行这样的代码是我最喜欢的 F# 特性之一。我认为能够快速尝试这样的想法是一种真正的生产力提升。所以让我们继续看看你可以用 F# Interactive 做的一些其他事情,比如创建交互式图表。
F#团队已经为`System.Windows.​Forms.DataVisua​lization.Charti​ng.dll`创建了一个 F#友好的包装。这个包装器的主要目的是让您能够快速地将程序中可用的数据(或 F#交互会话)显示为图表。可从[http://code . msdn . Microsoft . com/windowsdesktop/fsharpchat-b 59073 F5](http://code.msdn.microsoft.com/windowsdesktop/FSharpChart-b59073f5)下载。
F# 团队已经为`System.Windows.​Forms.DataVisua​lization.Charti​ng.dll`创建了一个 F# 友好的包装。这个包装器的主要目的是让您能够快速地将程序中可用的数据(或 F# 交互会话)显示为图表。可从[http://code . msdn . Microsoft . com/windowsdesktop/fsharpchat-b 59073 F5](http://code.msdn.microsoft.com/windowsdesktop/FSharpChart-b59073f5)下载。
一旦你解压下载的 **FSharpChart** 文件夹,你会在 **F#** > **脚本**文件夹中找到 **FSharpChart.fsx** 文件。您需要确保该脚本与您正在使用的 F#脚本在同一个目录中,或者相应地修改脚本的路径。
一旦你解压下载的 **FSharpChart** 文件夹,你会在 **F#** > **脚本**文件夹中找到 **FSharpChart.fsx** 文件。您需要确保该脚本与您正在使用的 F# 脚本在同一个目录中,或者相应地修改脚本的路径。
现在让我们看看如何使用 F#图表。以下示例显示了如何创建显示简单直线的图表:
现在让我们看看如何使用 F# 图表。以下示例显示了如何创建显示简单直线的图表:
```fs
#load "FSharpChart.fsx"
......@@ -71,7 +71,7 @@ F#团队已经为`System.Windows.​Forms.DataVisua​lization.Charti​ng.dll`
图 1:F #交互中的折线图
让我们来看看这个程序是如何工作的。第一行将图表脚本(一个名为`FSharpChart.fsx`的文件)加载到 F#交互会话中。这一行可能需要几秒钟,因为图表脚本相当大,但您只需要加载一次,这些功能将继续在交互式会话中可用。下一行导入我们将要使用的图表函数的名称空间。下面一行创建一个整数列表,并将它们绑定到`data`标识符。最后,我们将我们的列表传递给制图功能`FSharpChart.Line`,它会绘制一个线形图。这不是世界上最令人兴奋的图表,让我们再看一张。
让我们来看看这个程序是如何工作的。第一行将图表脚本(一个名为`FSharpChart.fsx`的文件)加载到 F# 交互会话中。这一行可能需要几秒钟,因为图表脚本相当大,但您只需要加载一次,这些功能将继续在交互式会话中可用。下一行导入我们将要使用的图表函数的名称空间。下面一行创建一个整数列表,并将它们绑定到`data`标识符。最后,我们将我们的列表传递给制图功能`FSharpChart.Line`,它会绘制一个线形图。这不是世界上最令人兴奋的图表,让我们再看一张。
下面的代码示例将创建一个柱形图,显示日期和每个日期的值:
......@@ -97,7 +97,7 @@ F#团队已经为`System.Windows.​Forms.DataVisua​lization.Charti​ng.dll`
程序的顶部,加载 **FSharpChart.fsx** 脚本和`open`语句的部分与之前基本相同。第一个主要区别是我们定义了一个函数`dateInApril`,提供了一种在 2012 年 4 月创建`DateTime`对象的简写方式。接下来,您会注意到我们的数据列表不是单个值,而是成对的值,称为*元组*。每对包含一个日期对象和一个整数。最后,我们将元组列表传递给绘制柱形图的制图功能`FSharpChart.` `Column`。虽然这个图表可能比上一个图表有趣一点,但这个例子并不太现实,因为我们更有可能绘制来自外部数据源(如文本文件)的数据。
让我们看看如何从. csv 文件中加载一些数据并用 F#绘制图表:
让我们看看如何从. csv 文件中加载一些数据并用 F# 绘制图表:
```fs
#load "FSharpChart.fsx"
......@@ -122,4 +122,4 @@ F#团队已经为`System.Windows.​Forms.DataVisua​lization.Charti​ng.dll`
## 总结
这一章已经非常简要地介绍了使用 F#来创建编译程序和使用 F# Interactive 来快速测试想法。本书的剩余部分将详细介绍 F#语言的语法和特性,从而指导如何用 F #编程。
\ No newline at end of file
这一章已经非常简要地介绍了使用 F# 来创建编译程序和使用 F# Interactive 来快速测试想法。本书的剩余部分将详细介绍 F# 语言的语法和特性,从而指导如何用 F #编程。
\ No newline at end of file
# 第三章功能编程
# 三、函数式编程
您在[第 1 章](01.html#_Chapter_1_)中看到,纯函数式编程将一切都视为值,包括函数。虽然 F#不是纯粹的函数式语言,但它确实鼓励你用函数式风格编程;也就是说,它鼓励您使用返回结果的表达式和计算,而不是导致某些副作用的语句。在本章中,我们将调查支持函数式编程范式的 F#的主要语言结构,并了解它们如何使函数式编程变得更容易。
您在[第 1 章](01.html#_Chapter_1_)中看到,纯函数式编程将一切都视为值,包括函数。虽然 F# 不是纯粹的函数式语言,但它确实鼓励你用函数式风格编程;也就是说,它鼓励您使用返回结果的表达式和计算,而不是导致某些副作用的语句。在本章中,我们将调查支持函数式编程范式的 F# 的主要语言结构,并了解它们如何使函数式编程变得更容易。
## 文字
*文字*代表常数值,是计算的有用构件。F#有丰富的文字集,我们将在下一个示例中看到。
*文字*代表常数值,是计算的有用构件。F# 有丰富的文字集,我们将在下一个示例中看到。
在 F#中,字符串文字可以包含换行符,常规字符串文字可以包含标准转义码。逐字字符串文字使用反斜杠(\)作为常规字符,两个双引号(`""`)是引号的转义码。通过使用适当的前缀和后缀指示符,可以使用十六进制和八进制定义所有整数类型。下面的示例显示了一些绑定到*标识符*的实际文字,这些文字在本章稍后的[标识符和让绑定](#_Identifiers_and_let)一节中有所描述。
在 F# 中,字符串文字可以包含换行符,常规字符串文字可以包含标准转义码。逐字字符串文字使用反斜杠(\)作为常规字符,两个双引号(`""`)是引号的转义码。通过使用适当的前缀和后缀指示符,可以使用十六进制和八进制定义所有整数类型。下面的示例显示了一些绑定到*标识符*的实际文字,这些文字在本章稍后的[标识符和让绑定](#_Identifiers_and_let)一节中有所描述。
```fs
// Some strings.
......@@ -26,7 +26,7 @@
## 功能
在 F#中,使用关键字`fun`定义函数。函数的参数用空格分隔,参数用 ASCII 箭头(`->`)与函数体分隔。
在 F# 中,使用关键字`fun`定义函数。函数的参数用空格分隔,参数用 ASCII 箭头(`->`)与函数体分隔。
下面是一个函数示例,该函数接受两个值并将它们相加:
......@@ -43,7 +43,7 @@
## 标识符和字母绑定
*标识符*是您给 F#中的值命名的方式,以便您以后可以在程序中引用它们。您可以使用关键字`let`定义一个标识符,后跟标识符的名称、等号和指定标识符引用的值的表达式。表达式是表示将返回值的计算的任何代码段。以下表达式显示了分配给标识符的值:
*标识符*是您给 F# 中的值命名的方式,以便您以后可以在程序中引用它们。您可以使用关键字`let`定义一个标识符,后跟标识符的名称、等号和指定标识符引用的值的表达式。表达式是表示将返回值的计算的任何代码段。以下表达式显示了分配给标识符的值:
```fs
let x = 42
......@@ -52,16 +52,16 @@
对于大多数来自命令式编程背景的人来说,这看起来像是一个变量赋值。有许多相似之处,但一个关键的区别是,在纯函数编程中,一旦一个值被分配给一个标识符,它就不会改变。这就是为什么我在本书中将它们称为*标识符*,而不是*变量*
| ![](img/note.png) | 注意:在某些情况下,您可以重新定义标识符。这看起来有点像标识符改变值,但它有细微的不同。此外,在 F#的命令式编程中,标识符的值在某些情况下可能会改变。在本章中,我们将重点讨论标识符不改变其值的函数式编程。 |
| ![](img/note.png) | 注意:在某些情况下,您可以重新定义标识符。这看起来有点像标识符改变值,但它有细微的不同。此外,在 F# 的命令式编程中,标识符的值在某些情况下可能会改变。在本章中,我们将重点讨论标识符不改变其值的函数式编程。 |
标识符既可以指值,也可以指函数,既然 F#函数本身就是值,这就不足为奇了。这意味着 F#没有函数名或参数名的真实概念;这些只是标识符。可以像将字符串或整数文字绑定到标识符一样,将匿名函数绑定到标识符:
标识符既可以指值,也可以指函数,既然 F# 函数本身就是值,这就不足为奇了。这意味着 F# 没有函数名或参数名的真实概念;这些只是标识符。可以像将字符串或整数文字绑定到标识符一样,将匿名函数绑定到标识符:
```fs
let myAdd = fun x y -> x + y
```
但是,由于需要用名称定义函数是非常常见的,F#为此提供了一个简短的语法。您编写函数定义的方式与编写值标识符的方式相同,只是一个函数在`let`关键字和等号之间有两个或多个标识符,如下所示:
但是,由于需要用名称定义函数是非常常见的,F# 为此提供了一个简短的语法。您编写函数定义的方式与编写值标识符的方式相同,只是一个函数在`let`关键字和等号之间有两个或多个标识符,如下所示:
```fs
let raisePowerTwo x = x ** 2.0
......@@ -70,7 +70,7 @@
第一个标识符是函数名`raisePowerTwo`,其后的标识符是函数参数名`x`。如果函数有名称,强烈建议您使用这种较短的语法来定义它。
在 F#中声明*值**函数*的语法是无法区分的,因为函数*是*值,F#语法对它们的处理是相似的。例如,考虑以下代码:
在 F# 中声明*值**函数*的语法是无法区分的,因为函数*是*值,F# 语法对它们的处理是相似的。例如,考虑以下代码:
```fs
let n = 10
......@@ -81,11 +81,11 @@
```
在第一行,值`10`被分配给标识符`n`。在第二行,定义了一个`add`函数,该函数接受两个参数并将它们相加。请注意语法是多么相似,唯一的区别是函数有列在函数名后面的参数。因为在 F#中一切都是值,所以第一行的文字`10`是值,下一行的表达式`a + b`的结果也是自动成为`add`函数结果的值。请注意,不需要像在命令式语言中那样显式地从函数返回值。
在第一行,值`10`被分配给标识符`n`。在第二行,定义了一个`add`函数,该函数接受两个参数并将它们相加。请注意语法是多么相似,唯一的区别是函数有列在函数名后面的参数。因为在 F# 中一切都是值,所以第一行的文字`10`是值,下一行的表达式`a + b`的结果也是自动成为`add`函数结果的值。请注意,不需要像在命令式语言中那样显式地从函数返回值。
## 标识符名称
有一些管理标识符名称的规则。标识符必须以下划线(`_`)或字母开头,然后可以包含任何字母数字字符、下划线或单引号(`'`)。关键字不能用作标识符。由于 F#支持使用单引号作为标识符名称的一部分,因此您可以用它来表示“prime”,为不同但相似的值创建标识符名称,如下例所示:
有一些管理标识符名称的规则。标识符必须以下划线(`_`)或字母开头,然后可以包含任何字母数字字符、下划线或单引号(`'`)。关键字不能用作标识符。由于 F# 支持使用单引号作为标识符名称的一部分,因此您可以用它来表示“prime”,为不同但相似的值创建标识符名称,如下例所示:
```fs
let x = 42
......@@ -93,7 +93,7 @@
```
F#支持 Unicode,因此您可以使用重音字符和非拉丁字母作为标识符名称:
F# 支持 Unicode,因此您可以使用重音字符和非拉丁字母作为标识符名称:
```fs
let 标识符 = 42
......@@ -114,7 +114,7 @@ F#支持 Unicode,因此您可以使用重音字符和非拉丁字母作为标
```fs
例如,您可能需要使用不是用 F#编写的库中的一个成员,并使用 F#的一个关键字作为其名称。一般来说,最好避免过度使用这个特性,因为它可能会导致库很难从其他库中使用。NET 语言。
例如,您可能需要使用不是用 F# 编写的库中的一个成员,并使用 F# 的一个关键字作为其名称。一般来说,最好避免过度使用这个特性,因为它可能会导致库很难从其他库中使用。NET 语言。
## 范围
......@@ -122,7 +122,7 @@ F#支持 Unicode,因此您可以使用重音字符和非拉丁字母作为标
所有标识符—无论它们与函数还是值相关—的范围都是从它们的定义的末尾开始,直到它们出现的部分的末尾。因此,对于顶层的标识符(也就是说,不是另一个函数或其他值的本地标识符),标识符的范围是从它被定义的地方到源文件的末尾。一旦顶层的标识符被赋予一个值(或函数),这个值就不能被改变或重新定义。标识符只有在其定义结束后才可用,这意味着通常不可能根据其本身来定义标识符。
您会注意到,在 F#中,您永远不需要显式返回值;计算的结果自动绑定到其关联的标识符。那么,如何计算函数中的中间值呢?在 F#中,这是由空白控制的。缩进会创建一个新的作用域,这个作用域的结束由缩进的结束来表示。缩进意味着`let`绑定是计算中的中间值,在此范围之外不可见。当一个范围关闭(缩进结束)并且一个标识符不再可用时,它被称为*退出范围*或*退出范围*。
您会注意到,在 F# 中,您永远不需要显式返回值;计算的结果自动绑定到其关联的标识符。那么,如何计算函数中的中间值呢?在 F# 中,这是由空白控制的。缩进会创建一个新的作用域,这个作用域的结束由缩进的结束来表示。缩进意味着`let`绑定是计算中的中间值,在此范围之外不可见。当一个范围关闭(缩进结束)并且一个标识符不再可用时,它被称为*退出范围*或*退出范围*。
为了演示作用域,下面的示例显示了一个计算两个整数中间点的函数。第三和第四行显示正在计算的中间值。
......@@ -143,7 +143,7 @@ F#支持 Unicode,因此您可以使用重音字符和非拉丁字母作为标
## 捕获标识符
您已经看到,在 F#中,您可以在其他函数中定义函数。这些函数可以使用范围内的任何标识符,包括定义它们的函数的本地定义。因为这些内部函数是值,所以它们可以作为函数的结果返回,或者作为参数传递给另一个函数。这意味着,虽然标识符是在一个函数中定义的,因此其他函数看不到它,但它的实际生命周期可能比定义它的函数长得多。让我们看一个例子来说明这一点。考虑以下函数,定义为`calculatePrefixFunction`:
您已经看到,在 F# 中,您可以在其他函数中定义函数。这些函数可以使用范围内的任何标识符,包括定义它们的函数的本地定义。因为这些内部函数是值,所以它们可以作为函数的结果返回,或者作为参数传递给另一个函数。这意味着,虽然标识符是在一个函数中定义的,因此其他函数看不到它,但它的实际生命周期可能比定义它的函数长得多。让我们看一个例子来说明这一点。考虑以下函数,定义为`calculatePrefixFunction`:
```
// Function that returns a function to
......@@ -174,7 +174,7 @@ F#支持 Unicode,因此您可以使用重音字符和非拉丁字母作为标
*递归*是指根据函数本身来定义函数;换句话说,函数在其定义内调用自己。递归经常用在函数式编程中,在命令式编程中使用循环。许多人认为,用递归而不是循环来表达算法更容易理解。
要在 F#中使用递归,请在`let`关键字后使用`rec`关键字,使标识符在函数定义中可用。下面的例子展示了递归的作用。请注意,在第五行中,函数如何对自己进行两次调用,作为其自身定义的一部分。
要在 F# 中使用递归,请在`let`关键字后使用`rec`关键字,使标识符在函数定义中可用。下面的例子展示了递归的作用。请注意,在第五行中,函数如何对自己进行两次调用,作为其自身定义的一部分。
```
// A function to generate the Fibonacci numbers.
......@@ -200,22 +200,22 @@ F#支持 Unicode,因此您可以使用重音字符和非拉丁字母作为标
拥有基本案例本身不足以确保终止。递归案例必须倾向于基本案例。在`fib`的例子中,如果`x`大于或等于 3,那么递归情况将倾向于基本情况,因为`x`将总是变得更小,并且在某个点达到 2。但是,如果`x`小于 1,那么`x`将持续变得更负,并且该功能将重复,直到达到机器的极限,导致堆栈溢出错误(`System.StackOverflowException`)。
前面的代码也使用了 F#模式匹配,这将在本章后面的[模式匹配](#_Pattern_Matching)部分讨论。
前面的代码也使用了 F# 模式匹配,这将在本章后面的[模式匹配](#_Pattern_Matching)部分讨论。
## 操作员
在 F#中,你可以把*运算符*看作是一种更具美感的调用函数的方式。
在 F# 中,你可以把*运算符*看作是一种更具美感的调用函数的方式。
F#有两种不同的运算符:
F# 有两种不同的运算符:
* *前缀*运算符是操作数在运算符之后的运算符。
* 一个*中缀*运算符位于第一个和第二个操作数之间。
F#提供了一组丰富多样的运算符,可以用于数字、布尔、字符串和集合类型。F#及其库中定义的运算符太多了,本节不做介绍,所以我们不看单个运算符,而是看如何在 F#中使用和定义运算符。
F# 提供了一组丰富多样的运算符,可以用于数字、布尔、字符串和集合类型。F# 及其库中定义的运算符太多了,本节不做介绍,所以我们不看单个运算符,而是看如何在 F# 中使用和定义运算符。
像在 C#中一样,F#运算符是重载的,这意味着一个运算符可以使用多个类型。但是,与 C#不同,两个操作数必须是相同的类型,否则编译器会生成错误。F#还允许用户定义和重新定义运算符。
像在 C#中一样,F# 运算符是重载的,这意味着一个运算符可以使用多个类型。但是,与 C#不同,两个操作数必须是相同的类型,否则编译器会生成错误。F# 还允许用户定义和重新定义运算符。
运算符遵循一组类似于 C#的规则来解析运算符重载;因此,任何类在 BCL 或任何。用 C#编写的支持运算符重载的. NET 库将在 F#中支持它。例如,您可以使用`+`运算符连接字符串,也可以将`System.TimeSpan`添加到`System.DateTime`,因为这些类型支持`+`运算符的重载。以下示例说明了这一点:
运算符遵循一组类似于 C#的规则来解析运算符重载;因此,任何类在 BCL 或任何。用 C#编写的支持运算符重载的. NET 库将在 F# 中支持它。例如,您可以使用`+`运算符连接字符串,也可以将`System.TimeSpan`添加到`System.DateTime`,因为这些类型支持`+`运算符的重载。以下示例说明了这一点:
```
let rhyme = "Jack " + "and " + "Jill"
......@@ -263,7 +263,7 @@ F#提供了一组丰富多样的运算符,可以用于数字、布尔、字符
(加 4 ^ 5)= 9
在 F#中,函数有固定数量的参数,并应用于源文件中下一个出现的值。调用函数时不一定需要使用圆括号,但是 F#程序员经常使用圆括号来定义哪个函数应该应用于哪个参数。考虑一个简单的情况,您想要使用`add`函数添加四个数字。您可以将每个函数调用的结果绑定到一个新的标识符,但是对于这样一个简单的计算来说,这将是非常麻烦的:
在 F# 中,函数有固定数量的参数,并应用于源文件中下一个出现的值。调用函数时不一定需要使用圆括号,但是 F# 程序员经常使用圆括号来定义哪个函数应该应用于哪个参数。考虑一个简单的情况,您想要使用`add`函数添加四个数字。您可以将每个函数调用的结果绑定到一个新的标识符,但是对于这样一个简单的计算来说,这将是非常麻烦的:
```
let add x y = x + y
......@@ -287,7 +287,7 @@ F#提供了一组丰富多样的运算符,可以用于数字、布尔、字符
这里`add`函数的第二次和第三次出现分别用参数`4`、`5`和`6`、`7`分组,第一次出现的`add`函数将作用于其他两个函数的结果。
F#还提供了另一种使用*管道转发*操作符(`|>`)组合函数的方法。该运算符具有以下定义:
F# 还提供了另一种使用*管道转发*操作符(`|>`)组合函数的方法。该运算符具有以下定义:
```
let (|>) x f = f x
......@@ -312,11 +312,11 @@ F#还提供了另一种使用*管道转发*操作符(`|>`)组合函数的方法
一些程序员认为这种风格更易读,因为它具有使代码以从右向左的方式阅读的效果。代码现在应该是“将 6 加 7,将这个结果转发给下一个将加 4 的函数,然后将这个结果转发给将加 5 的函数。”
这个例子还利用了在 F#中部分应用函数的能力,这将在下一节中讨论。
这个例子还利用了在 F# 中部分应用函数的能力,这将在下一节中讨论。
## 函数的部分应用
F#支持函数的部分应用(这些有时被称为*部分*或 *curried* 函数)。这意味着您不需要一次将所有参数传递给一个函数。请注意,上一节中的最后一个示例将一个参数传递给`add`函数,该函数接受两个参数。这与函数就是值的思想有很大关系。因此,我们可以创建一个`add`函数,向其传递一个参数,并将结果函数绑定到一个新的标识符:
F# 支持函数的部分应用(这些有时被称为*部分*或 *curried* 函数)。这意味着您不需要一次将所有参数传递给一个函数。请注意,上一节中的最后一个示例将一个参数传递给`add`函数,该函数接受两个参数。这与函数就是值的思想有很大关系。因此,我们可以创建一个`add`函数,向其传递一个参数,并将结果函数绑定到一个新的标识符:
```
let add x y = x + y
......@@ -331,7 +331,7 @@ F#支持函数的部分应用(这些有时被称为*部分*或 *curried* 函数)
*模式匹配*允许您查看标识符的值,然后根据其值进行不同的计算。它可能比得上 C++和 C#中的`switch`语句,但它要强大和灵活得多。以函数风格编写的程序倾向于被编写为应用于输入数据的一系列转换。模式匹配允许您分析输入数据并决定应该对其应用哪种转换,因此模式匹配非常适合函数式编程。
F#中的模式匹配构造允许您在各种类型和值上进行模式匹配。它也有几种不同的形式,出现在语言的几个地方。
F# 中的模式匹配构造允许您在各种类型和值上进行模式匹配。它也有几种不同的形式,出现在语言的几个地方。
模式匹配最简单的形式是对一个值进行匹配。你已经在本章的[递归](#_Recursion)部分看到了这一点,它被用来实现一个在斐波那契数列中生成数字的函数。为了说明语法,下一个例子显示了一个函数的实现,该函数将产生卢卡斯数,一个数字序列如下:1,3,4,7,11,18,29,47,76。卢卡斯序列的定义与斐波那契序列相同;只有起点不同。
......@@ -380,7 +380,7 @@ F#中的模式匹配构造允许您在各种类型和值上进行模式匹配。
前两个规则有两个字符串应该评估为相同的值,所以您可以在两个模式之间使用`|`而不是有两个单独的规则。
还可以在 F#定义的大多数类型上进行模式匹配。接下来的两个例子演示了元组上的模式匹配,两个函数使用模式匹配实现了布尔 And 和 Or。每种方法都略有不同。
还可以在 F# 定义的大多数类型上进行模式匹配。接下来的两个例子演示了元组上的模式匹配,两个函数使用模式匹配实现了布尔 And 和 Or。每种方法都略有不同。
```
let myOr b1 b2 =
......@@ -402,7 +402,7 @@ F#中的模式匹配构造允许您在各种类型和值上进行模式匹配。
`myOr`的第三个规则和`myAnd`的第二个规则显示了与单个`_`通配符匹配的整个元组。如果您想使用规则定义后半部分中的值,也可以用标识符替换它。
因为模式匹配在 F#中是如此常见的任务,所以该语言提供了替代的速记语法。如果一个函数的唯一目的是对某个东西进行模式匹配,那么使用这个语法可能是值得的。在这个版本的模式匹配语法中,您使用关键字`function`,将模式放在函数参数通常会去的地方,然后用`|`分隔所有可选规则。下一个示例在一个简单的函数中展示了这个语法,该函数递归地处理一系列字符串并将它们连接成一个字符串。
因为模式匹配在 F# 中是如此常见的任务,所以该语言提供了替代的速记语法。如果一个函数的唯一目的是对某个东西进行模式匹配,那么使用这个语法可能是值得的。在这个版本的模式匹配语法中,您使用关键字`function`,将模式放在函数参数通常会去的地方,然后用`|`分隔所有可选规则。下一个示例在一个简单的函数中展示了这个语法,该函数递归地处理一系列字符串并将它们连接成一个字符串。
```
// Concatenate a list of strings into a single string.
......@@ -423,13 +423,13 @@ F#中的模式匹配构造允许您在各种类型和值上进行模式匹配。
太棒了,还有滑动的杯子...
模式匹配是 F#的基本构造块之一,我们将在本书中多次回到它。在下一章中,我们将研究带有记录类型和联合类型的列表的模式匹配。
模式匹配是 F# 的基本构造块之一,我们将在本书中多次回到它。在下一章中,我们将研究带有记录类型和联合类型的列表的模式匹配。
## 控制流程
F#对*控制流*有很强的概念。在这方面,它不同于许多纯函数式语言,在这些语言中,控制流的概念非常松散,因为表达式可以按任何顺序进行计算。控制流的强概念在`if… then… else…`表达式中很明显。
F# 对*控制流*有很强的概念。在这方面,它不同于许多纯函数式语言,在这些语言中,控制流的概念非常松散,因为表达式可以按任何顺序进行计算。控制流的强概念在`if… then… else…`表达式中很明显。
在 F#中,`if… then… else…`构造是一个表达式,意味着它返回值。根据`if`和`then`关键字之间的布尔表达式的值,将返回两个不同值中的一个。下一个例子说明了这一点。根据程序是在偶数秒还是奇数秒运行,计算`if… then… else…`表达式以返回`"heads"`或`"tails"`。
在 F# 中,`if… then… else…`构造是一个表达式,意味着它返回值。根据`if`和`then`关键字之间的布尔表达式的值,将返回两个不同值中的一个。下一个例子说明了这一点。根据程序是在偶数秒还是奇数秒运行,计算`if… then… else…`表达式以返回`"heads"`或`"tails"`。
```
let result =
......@@ -454,7 +454,7 @@ F#对*控制流*有很强的概念。在这方面,它不同于许多纯函数
```fs
`if… then… else…`表达式有一些含义,如果你更熟悉命令式编程,你可能不会想到。F#的类型系统要求`if… then… else…`表达式返回的值必须是相同的类型,否则编译器会产生错误。因此,如果在前面的例子中,你用一个整数或布尔值替换了字符串`"tails"`,你会得到一个编译错误。如果您真的需要不同类型的值,您可以创建一个类型为`obj` (F#版本的`System.Object`)的`if… then… else…`表达式,如下例所示,它会将`"heads"`或`false`打印到控制台。
`if… then… else…`表达式有一些含义,如果你更熟悉命令式编程,你可能不会想到。F# 的类型系统要求`if… then… else…`表达式返回的值必须是相同的类型,否则编译器会产生错误。因此,如果在前面的例子中,你用一个整数或布尔值替换了字符串`"tails"`,你会得到一个编译错误。如果您真的需要不同类型的值,您可以创建一个类型为`obj` (F# 版本的`System.Object`)的`if… then… else…`表达式,如下例所示,它会将`"heads"`或`false`打印到控制台。
```
let result =
......@@ -471,7 +471,7 @@ F#对*控制流*有很强的概念。在这方面,它不同于许多纯函数
## 列表
F# *列表*是内置于 F#中的简单集合类型。一个 F#列表可以是一个*空列表*,用方括号(`[]`)表示,也可以是另一个连接了一个值的列表。您可以使用内置运算符将值连接到 F#列表的前面,该运算符由两个冒号(`::`)组成,发音为“cons”下一个示例显示了一些正在定义的列表,从第一行的空列表开始,后面是两个列表,字符串通过串联放在前面:
F# *列表*是内置于 F# 中的简单集合类型。一个 F# 列表可以是一个*空列表*,用方括号(`[]`)表示,也可以是另一个连接了一个值的列表。您可以使用内置运算符将值连接到 F# 列表的前面,该运算符由两个冒号(`::`)组成,发音为“cons”下一个示例显示了一些正在定义的列表,从第一行的空列表开始,后面是两个列表,字符串通过串联放在前面:
```
let emptyList = []
......@@ -487,14 +487,14 @@ F# *列表*是内置于 F#中的简单集合类型。一个 F#列表可以是一
```fs
另一个处理列表的 F#运算符是“at”符号(`@`),您可以使用它将两个列表连接在一起,如下所示:
另一个处理列表的 F# 运算符是“at”符号(`@`),您可以使用它将两个列表连接在一起,如下所示:
```
let twoLists = ["one, "; "two, "] @ ["buckle "; "my "; "shoe "]
```fs
F#列表中的所有项目必须属于同一类型。如果您试图将不同类型的项放在列表中,例如,您试图将一个字符串连接到一个整数列表,您将会得到一个编译错误。如果需要混合类型列表,可以创建类型列表`obj`(相当于`System.Object`的 F#),如下面的代码示例所示:
F# 列表中的所有项目必须属于同一类型。如果您试图将不同类型的项放在列表中,例如,您试图将一个字符串连接到一个整数列表,您将会得到一个编译错误。如果需要混合类型列表,可以创建类型列表`obj`(相当于`System.Object`的 F#),如下面的代码示例所示:
```
// The empty list.
......@@ -517,9 +517,9 @@ F#列表中的所有项目必须属于同一类型。如果您试图将不同类
```fs
我将在下一章[类型和类型推断](04.html#_Chapter_4_)中更详细地讨论 F#中的类型。
我将在下一章[类型和类型推断](04.html#_Chapter_4_)中更详细地讨论 F# 中的类型。
F#列表是*不可变的*。换句话说,一旦创建了列表,就不能更改。作用于列表的函数和操作符不会改变它们,但是它们会创建一个新的、修改过的列表版本,保留旧的列表供以后需要时使用。下一个例子说明了这一点。
F# 列表是*不可变的*。换句话说,一旦创建了列表,就不能更改。作用于列表的函数和操作符不会改变它们,但是它们会创建一个新的、修改过的列表版本,保留旧的列表供以后需要时使用。下一个例子说明了这一点。
```
// Create a list of one item.
......@@ -539,11 +539,11 @@ F#列表是*不可变的*。换句话说,一旦创建了列表,就不能更
```fs
创建一个包含单个字符串的 F#列表,然后再创建两个列表,每个列表使用前一个列表作为基础。最后,将`List.rev`功能应用于最后一个列表,创建一个新的反向列表。
创建一个包含单个字符串的 F# 列表,然后再创建两个列表,每个列表使用前一个列表作为基础。最后,将`List.rev`功能应用于最后一个列表,创建一个新的反向列表。
## 列表模式匹配
使用 F#列表的常规方法是使用模式匹配和递归。将标题项从列表中拉出的模式匹配语法与将项连接到列表的语法相同。模式由代表头部的标识符形成,后跟`::`,然后是列表其余部分的标识符。你可以在下一个例子的`concatList`的第一条规则中看到这一点。您还可以根据列表常量进行模式匹配;你可以在`concatList`的第二条规则中看到这一点,这里有一个空列表。
使用 F# 列表的常规方法是使用模式匹配和递归。将标题项从列表中拉出的模式匹配语法与将项连接到列表的语法相同。模式由代表头部的标识符形成,后跟`::`,然后是列表其余部分的标识符。你可以在下一个例子的`concatList`的第一条规则中看到这一点。您还可以根据列表常量进行模式匹配;你可以在`concatList`的第二条规则中看到这一点,这里有一个空列表。
```
// List to be concatenated.
......@@ -599,7 +599,7 @@ F#列表是*不可变的*。换句话说,一旦创建了列表,就不能更
列表中的最后 3 个数字是 3 2 1
尽管模式匹配是分析列表中数据的强大工具,但通常没有必要直接使用它。F#库提供了许多高阶函数来处理为您实现模式匹配的列表,因此您不需要重复代码。为了说明这一点,假设您需要编写一个函数,为列表中的每个项目添加一个。您可以使用模式匹配轻松地编写它:
尽管模式匹配是分析列表中数据的强大工具,但通常没有必要直接使用它。F# 库提供了许多高阶函数来处理为您实现模式匹配的列表,因此您不需要重复代码。为了说明这一点,假设您需要编写一个函数,为列表中的每个项目添加一个。您可以使用模式匹配轻松地编写它:
```
let rec addOneAll list =
......@@ -616,7 +616,7 @@ F#列表是*不可变的*。换句话说,一旦创建了列表,就不能更
(addOneAll[1;2;3]) = [2;3;4]
然而,对于这样一个简单的问题,代码可能比您希望的要详细一些。解决这个问题的线索来自于注意到向列表中的每个项目添加一个只是一个更一般问题的例子:需要对列表中的每个项目应用一些转换。F#核心库包含在`List`模块中定义的`map`函数。它有以下定义:
然而,对于这样一个简单的问题,代码可能比您希望的要详细一些。解决这个问题的线索来自于注意到向列表中的每个项目添加一个只是一个更一般问题的例子:需要对列表中的每个项目应用一些转换。F# 核心库包含在`List`模块中定义的`map`函数。它有以下定义:
```
let rec map func list =
......@@ -641,4 +641,4 @@ F#列表是*不可变的*。换句话说,一旦创建了列表,就不能更
## 总结
本章简要介绍了 F#的功能特性。这些为程序员提供了一种强大但灵活的方法来创建程序。
\ No newline at end of file
本章简要介绍了 F# 的功能特性。这些为程序员提供了一种强大但灵活的方法来创建程序。
\ No newline at end of file
# 第四章类型和类型推断
# 四、类型和类型推断
F#是一种*强类型*语言,这意味着你不能使用一个值不合适的函数。不能调用以字符串作为参数并带有整数参数的函数;您必须在两者之间显式转换。这种语言对待其价值类型的方式被称为其*类型系统*。F#有一个不会妨碍常规编程的类型系统。在 F#中,所有值都有一个类型,这包括作为函数的值。
F# 是一种*强类型*语言,这意味着你不能使用一个值不合适的函数。不能调用以字符串作为参数并带有整数参数的函数;您必须在两者之间显式转换。这种语言对待其价值类型的方式被称为其*类型系统*。F# 有一个不会妨碍常规编程的类型系统。在 F# 中,所有值都有一个类型,这包括作为函数的值。
## 类型推断
通常,您不需要显式声明类型;编译器将从函数中文字的类型和它调用的其他函数的结果类型中计算出一个值的类型。如果一切正常,编译器会将类型保留给自己;只有当存在类型不匹配时,编译器才会通过报告编译错误来通知您。这个过程一般被称为*式推理*。如果你想更多地了解程序中的类型,可以用`–i`开关让编译器显示所有推断的类型。Visual Studio 用户将鼠标指针悬停在标识符上时,会获得显示类型的工具提示。
类型推断在 F#中的工作方式相当容易理解。编译器在整个程序中工作,在定义标识符时为它们分配类型,从最左边的标识符开始,一直向下到最右边的标识符。它根据已经知道的类型分配类型,也就是文字的类型和(更常见的)在其他源文件或程序集中定义的函数的类型。
类型推断在 F# 中的工作方式相当容易理解。编译器在整个程序中工作,在定义标识符时为它们分配类型,从最左边的标识符开始,一直向下到最右边的标识符。它根据已经知道的类型分配类型,也就是文字的类型和(更常见的)在其他源文件或程序集中定义的函数的类型。
下一个例子定义了两个 F#标识符,然后用 F#编译器的`–i`开关在控制台上显示它们的推断类型。
下一个例子定义了两个 F# 标识符,然后用 F# 编译器的`–i`开关在控制台上显示它们的推断类型。
```fs
let aString = "Spring time in Paris"
......@@ -34,7 +34,7 @@ val makeMessage : int ->字符串
val half : int -> int
注意`makeMessage`函数的定义前缀是关键字`val`,就像你之前看到的两个值一样;即使它是一个函数,F#编译器仍然认为它是一个值。此外,类型本身使用符号`int -> string`,这意味着一个函数接受一个整数并返回一个字符串。类型名称之间的`->` (ASCII 箭头)表示正在应用的函数的转换。箭头表示值的转换,但不一定表示类型,因为它可以表示将值转换为相同类型值的函数,如第二行的`half`函数所示。
注意`makeMessage`函数的定义前缀是关键字`val`,就像你之前看到的两个值一样;即使它是一个函数,F# 编译器仍然认为它是一个值。此外,类型本身使用符号`int -> string`,这意味着一个函数接受一个整数并返回一个字符串。类型名称之间的`->` (ASCII 箭头)表示正在应用的函数的转换。箭头表示值的转换,但不一定表示类型,因为它可以表示将值转换为相同类型值的函数,如第二行的`half`函数所示。
可以部分应用的函数类型和接受元组的函数类型不同。以下功能`div1``div2`说明了这一点。
......@@ -63,9 +63,9 @@ val divremainder:int-> int-> int * int
瓦尔·多诺西:“a->”a
该函数的类型为`'a -> 'a`,即取一个类型的值,返回一个相同类型的值。任何以单引号(`'`)开头的类型都意味着一个*变量*类型。F#有一个类型`obj`,它映射到`System.Object`并代表任何类型的值——这个概念你可能会从其他基于公共语言运行时(CLR)的编程语言中熟悉(事实上,许多语言并不以 CLR 为目标)。但是,变量类型不一样。请注意该类型在箭头的两侧有一个`'a`。这意味着,即使编译器还不知道类型,它也知道返回值的类型将与参数的类型相同。类型系统的这一特性,有时被称为*类型参数化*,允许编译器在编译时发现更多的类型错误,并有助于避免强制转换。
该函数的类型为`'a -> 'a`,即取一个类型的值,返回一个相同类型的值。任何以单引号(`'`)开头的类型都意味着一个*变量*类型。F# 有一个类型`obj`,它映射到`System.Object`并代表任何类型的值——这个概念你可能会从其他基于公共语言运行时(CLR)的编程语言中熟悉(事实上,许多语言并不以 CLR 为目标)。但是,变量类型不一样。请注意该类型在箭头的两侧有一个`'a`。这意味着,即使编译器还不知道类型,它也知道返回值的类型将与参数的类型相同。类型系统的这一特性,有时被称为*类型参数化*,允许编译器在编译时发现更多的类型错误,并有助于避免强制转换。
| ![](img/note.png) | 注意:变量类型或类型参数化的概念与*泛型*的概念密切相关,后者是在 CLR 2.0 版本中引入的,现在已经成为 CLI 2.0 版本的 ECMA 规范的一部分。当 F#以启用了泛型的 CLI 为目标时,它会通过在找到未确定类型的任何地方使用它们来充分利用它们。F#的创建者 Don Syme 在。NET CLR 在他开始研究 F#之前。有人可能会想推断他这么做是为了创造 F#! |
| ![](img/note.png) | 注意:变量类型或类型参数化的概念与*泛型*的概念密切相关,后者是在 CLR 2.0 版本中引入的,现在已经成为 CLI 2.0 版本的 ECMA 规范的一部分。当 F# 以启用了泛型的 CLI 为目标时,它会通过在找到未确定类型的任何地方使用它们来充分利用它们。F# 的创建者 Don Syme 在。NET CLR 在他开始研究 F# 之前。有人可能会想推断他这么做是为了创造 F#! |
下一个示例中显示的函数`doNothingToAnInt`是一个被约束的值的例子——一个*类型的约束*。在这种情况下,功能参数`x`被约束为`int`。可以将任何标识符约束为某种类型,而不仅仅是函数参数,尽管更典型的是需要约束参数。这里的列表`stringList`显示了如何约束不是函数参数的标识符。
......@@ -87,13 +87,13 @@ val stringList:字符串列表
`intList`值是整数列表,标识符的类型是`int list`。这表明编译器已经识别出列表只包含整数,在这种情况下,其项目的类型不是未确定的,而是`int`。除了类型为`int`的值之外,任何向列表中添加任何内容的尝试都将导致编译错误。
标识符`stringList`有类型标注。虽然这是不必要的,因为编译器可以从值解析类型,但它用于显示处理未确定类型的替代语法。您可以将该类型放在与其关联的类型之后的尖括号之间,而不只是将其写在类型名称之前。请注意,即使`stringList`的类型被限制为`list<string>`(字符串列表),编译器在显示类型时仍然将其类型报告为`string list`,它们的含义完全相同。支持此语法以使带有类型参数的 F#类型看起来像其他类型的泛型类型。NET 库。
标识符`stringList`有类型标注。虽然这是不必要的,因为编译器可以从值解析类型,但它用于显示处理未确定类型的替代语法。您可以将该类型放在与其关联的类型之后的尖括号之间,而不只是将其写在类型名称之前。请注意,即使`stringList`的类型被限制为`list<string>`(字符串列表),编译器在显示类型时仍然将其类型报告为`string list`,它们的含义完全相同。支持此语法以使带有类型参数的 F# 类型看起来像其他类型的泛型类型。NET 库。
在编写纯 F#时,约束值通常不是必需的,尽管它偶尔会有用。使用时最有用。NET 库,用 F#以外的语言编写,用于与非托管库进行互操作。在这两种情况下,编译器的类型信息较少,因此通常需要给它足够的信息来消除值的歧义。
在编写纯 F# 时,约束值通常不是必需的,尽管它偶尔会有用。使用时最有用。NET 库,用 F# 以外的语言编写,用于与非托管库进行互操作。在这两种情况下,编译器的类型信息较少,因此通常需要给它足够的信息来消除值的歧义。
## 定义类型
F#中的类型系统提供了许多定义自定义类型的功能。所有 F#的类型定义都分为两类:
F# 中的类型系统提供了许多定义自定义类型的功能。所有 F# 的类型定义都分为两类:
* *元组**记录*,它们是组成复合类型的一组类型(类似于 C 中的结构或 C#中的类)。
* *求和*类型,有时称为*联合*类型。
......@@ -109,7 +109,7 @@ F#中的类型系统提供了许多定义自定义类型的功能。所有 F#的
```
元组不同于 F#中大多数用户定义的类型,因为您不需要使用`type`关键字显式声明它们。要定义类型,您可以使用`type`关键字,后跟类型名称、等号,然后是您正在定义的类型。在最简单的形式中,您可以使用它为任何现有类型(包括元组)提供别名。给单个类型赋予别名通常并不有用,但是给元组赋予别名可能非常有用,尤其是当您想要使用元组作为类型约束时。下一个示例显示了如何为单个类型和元组赋予别名,以及如何使用别名作为类型约束。
元组不同于 F# 中大多数用户定义的类型,因为您不需要使用`type`关键字显式声明它们。要定义类型,您可以使用`type`关键字,后跟类型名称、等号,然后是您正在定义的类型。在最简单的形式中,您可以使用它为任何现有类型(包括元组)提供别名。给单个类型赋予别名通常并不有用,但是给元组赋予别名可能非常有用,尤其是当您想要使用元组作为类型约束时。下一个示例显示了如何为单个类型和元组赋予别名,以及如何使用别名作为类型约束。
```fs
type Name = string
......@@ -150,7 +150,7 @@ F#中的类型系统提供了许多定义自定义类型的功能。所有 F#的
将字段定义放在大括号之间,并用分号分隔。字段定义由后跟冒号的字段名称和字段类型组成。类型定义`Organization1`是字段名称唯一的记录类型。这意味着您可以使用简单的语法来创建这种类型的实例,在创建时不需要提及类型名称。要创建记录,您需要将字段名称后跟等号,并将字段值放在大括号(`{}`)之间,如`Rainbow`标识符所示。
F#不强制字段名是唯一的,所以有时编译器不能仅从字段名推断字段的类型。在这种情况下,编译器无法推断记录的类型。要创建具有非唯一字段的记录,编译器需要静态地知道正在创建的记录的类型。如果编译器无法推断记录的类型,您需要使用类型注释,如[类型推断](#_Type_Inference)部分所述。使用类型注释由类型`Organization2``Organization3`以及它们的实例`thePlayers``wayneManor`来说明。您可以看到在其名称后面显式给出的标识符的类型。
F# 不强制字段名是唯一的,所以有时编译器不能仅从字段名推断字段的类型。在这种情况下,编译器无法推断记录的类型。要创建具有非唯一字段的记录,编译器需要静态地知道正在创建的记录的类型。如果编译器无法推断记录的类型,您需要使用类型注释,如[类型推断](#_Type_Inference)部分所述。使用类型注释由类型`Organization2``Organization3`以及它们的实例`thePlayers``wayneManor`来说明。您可以看到在其名称后面显式给出的标识符的类型。
访问记录中的字段相当简单。您只需使用语法`record`标识符名称,后跟一个点,后跟字段名称。下面的例子说明了这一点,展示了如何访问`Organization`记录的`chief`字段。
......@@ -168,7 +168,7 @@ F#不强制字段名是唯一的,所以有时编译器不能仅从字段名推
```
默认情况下,记录是不可变的。对于一个命令式程序员来说,这听起来好像记录不是很有用,因为不可避免地会出现需要更改字段值的情况。为此,F#提供了一种简单的语法,用于创建具有更新字段的记录副本。要创建记录的副本,请将该记录的名称放在大括号中,后跟关键字`with`,然后是要用其更新值进行更改的字段列表。这样做的好处是,您不需要重新键入未更改的字段列表。下面的示例演示了这种方法。它创建了`wayneManor`的初始版本,然后创建了`wayneManor'`,其中`"Robin"`已经被移除。
默认情况下,记录是不可变的。对于一个命令式程序员来说,这听起来好像记录不是很有用,因为不可避免地会出现需要更改字段值的情况。为此,F# 提供了一种简单的语法,用于创建具有更新字段的记录副本。要创建记录的副本,请将该记录的名称放在大括号中,后跟关键字`with`,然后是要用其更新值进行更改的字段列表。这样做的好处是,您不需要重新键入未更改的字段列表。下面的示例演示了这种方法。它创建了`wayneManor`的初始版本,然后创建了`wayneManor'`,其中`"Robin"`已经被移除。
```fs
// Define an organization type.
......@@ -316,7 +316,7 @@ wayneManor ' = { chief = "蝙蝠侠";
```
这个问题的另一个解决方案是使用 F#的度量单位,它允许将类型应用于数值。
这个问题的另一个解决方案是使用 F# 的度量单位,它允许将类型应用于数值。
## 带类型参数的类型定义
......@@ -374,4 +374,4 @@ wayneManor ' = { chief = "蝙蝠侠";
## 总结
这是对如何在 F#中创建类型的简单介绍。你会发现 F#的类型系统提供了一种灵活的方式来表示程序中的数据。
\ No newline at end of file
这是对如何在 F# 中创建类型的简单介绍。你会发现 F# 的类型系统提供了一种灵活的方式来表示程序中的数据。
\ No newline at end of file
# 第五章面向对象编程
# 五、面向对象编程
面向对象编程是第三种主要的编程范式。有一种倾向是试图将功能范式和面向对象范式显示为相互竞争的,但我相信它们是互补的技术,可以很好地协同工作,我将在本章中尝试演示这一点。在本质上,面向对象编程有一些简单的思想,有时被称为面向对象编程的原则:封装、多态和继承。
可能最重要的原则是*封装*—实现和状态应该是*封装*的想法,或者隐藏在定义良好的边界之后。这使得程序的结构更容易管理。在 F#中,通过使用模块和类型定义的签名,以及通过简单地将它们本地定义到表达式或类构造中,您可以隐藏一些东西(您将在本章中看到这两者的示例)。
可能最重要的原则是*封装*—实现和状态应该是*封装*的想法,或者隐藏在定义良好的边界之后。这使得程序的结构更容易管理。在 F# 中,通过使用模块和类型定义的签名,以及通过简单地将它们本地定义到表达式或类构造中,您可以隐藏一些东西(您将在本章中看到这两者的示例)。
第二个原则,*多态性*,是您可以用多种方式实现抽象实体的想法。您已经遇到了许多简单的抽象实体,例如函数类型。函数类型是抽象的,因为您可以用许多不同的方式实现具有特定类型的函数。例如,您可以将函数类型`int -> int`实现为给定参数递增的函数、参数递减的函数或数百万个数学序列中的任何一个。您还可以从现有的抽象组件中构建其他抽象实体,例如。NET 密件抄送。您还可以使用用户定义的接口类型来建模更复杂的抽象实体。接口类型的优点是可以分层排列;这叫*接口继承*。例如。NET BCL 包括集合类型的分层分类,在`System.Collections``System.Collections.Generic`名称空间中可用。
在 OOP 中,您有时可以分层排列实现片段。这被称为*实现继承*,在 F#编程中它往往不那么重要,因为函数式编程为定义和共享实现片段提供了灵活性。然而,它对于诸如图形用户界面(GUI)编程之类的领域是有意义的。
在 OOP 中,您有时可以分层排列实现片段。这被称为*实现继承*,在 F# 编程中它往往不那么重要,因为函数式编程为定义和共享实现片段提供了灵活性。然而,它对于诸如图形用户界面(GUI)编程之类的领域是有意义的。
虽然面向对象编程的原则很重要,但面向对象编程也已经成为围绕系统的值组织代码的代名词*名词*,然后提供对这些值的操作,作为对该值进行操作的成员、函数或方法。这通常就像取一个以函数应用于某个值的样式编写的函数(如`String.length s`)并用点符号重写它(如`s.Length`)一样简单。这个简单的动作通常可以让你的代码更加清晰。在本章中,您将看到 F#如何允许您将成员附加到它的任何类型,而不仅仅是它的类,如果您愿意的话,使您能够以面向对象的方式组织您的所有代码。
虽然面向对象编程的原则很重要,但面向对象编程也已经成为围绕系统的值组织代码的代名词*名词*,然后提供对这些值的操作,作为对该值进行操作的成员、函数或方法。这通常就像取一个以函数应用于某个值的样式编写的函数(如`String.length s`)并用点符号重写它(如`s.Length`)一样简单。这个简单的动作通常可以让你的代码更加清晰。在本章中,您将看到 F# 如何允许您将成员附加到它的任何类型,而不仅仅是它的类,如果您愿意的话,使您能够以面向对象的方式组织您的所有代码。
F#提供了丰富的面向对象编程模型,允许您创建行为类似于 C#和 VB.NET 创建的类、接口和对象。也许更重要的是,当打包到库中并由该库的用户查看时,您在 F#中创建的类与用其他语言创建的类是无法区分的。然而,面向对象编程不仅仅是简单地定义对象,当您开始研究如何使用 F#本机类型以面向对象的方式编程时,您将会看到这一点。
F# 提供了丰富的面向对象编程模型,允许您创建行为类似于 C#和 VB.NET 创建的类、接口和对象。也许更重要的是,当打包到库中并由该库的用户查看时,您在 F# 中创建的类与用其他语言创建的类是无法区分的。然而,面向对象编程不仅仅是简单地定义对象,当您开始研究如何使用 F# 本机类型以面向对象的方式编程时,您将会看到这一点。
## 带成员的 F#类型
## 带成员的 F# 类型
可以向 F#的记录和联合类型添加函数。您可以使用点标记法调用添加到记录或联合类型的函数,就像您可以从非 F#编写的库中调用类的成员一样。当您想要向其他人公开您在 F#中定义的类型时,它也证明是有用的。NET 语言。许多程序员更喜欢看到对实例值的函数调用,这种技术为所有 F#类型提供了一种很好的方法。
可以向 F# 的记录和联合类型添加函数。您可以使用点标记法调用添加到记录或联合类型的函数,就像您可以从非 F# 编写的库中调用类的成员一样。当您想要向其他人公开您在 F# 中定义的类型时,它也证明是有用的。NET 语言。许多程序员更喜欢看到对实例值的函数调用,这种技术为所有 F# 类型提供了一种很好的方法。
定义带有成员的 F#记录或联合类型的语法与您在[第 4 章](04.html#_Chapter_4_)中学习的语法相同,除了这里它包括总是出现在最后的成员定义,在`with`关键字之后。成员本身的定义以关键字`member`开始,后面跟着一个标识符,该标识符代表成员被附加到的类型的参数,后面跟着一个点、函数名,然后是函数采用的任何其他参数。在这之后是等号后面是函数定义,可以是任何 F#表达式。
定义带有成员的 F# 记录或联合类型的语法与您在[第 4 章](04.html#_Chapter_4_)中学习的语法相同,除了这里它包括总是出现在最后的成员定义,在`with`关键字之后。成员本身的定义以关键字`member`开始,后面跟着一个标识符,该标识符代表成员被附加到的类型的参数,后面跟着一个点、函数名,然后是函数采用的任何其他参数。在这之后是等号后面是函数定义,可以是任何 F# 表达式。
以下示例定义了记录类型`Point`。它有两个字段,`Left``Top`;和成员函数`Swap``Swap`函数是一个简单的函数,用交换的`Left``Top`的值创建一个新点。注意如何在函数定义中使用在函数名`Swap`之前给出的`x`参数来访问记录的其他成员:
......@@ -76,7 +76,7 @@ F#提供了丰富的面向对象编程模型,允许您创建行为类似于 C#
```
调用函数的值作为参数传递给函数。当您考虑到函数需要能够访问调用它的值的字段和方法时,这是合乎逻辑的。有些 OO 语言对此使用特定的关键字,例如`this``Me`,但是在这种情况下,F#允许您通过在关键字成员— `x`之后指定名称来选择该参数的名称。
调用函数的值作为参数传递给函数。当您考虑到函数需要能够访问调用它的值的字段和方法时,这是合乎逻辑的。有些 OO 语言对此使用特定的关键字,例如`this``Me`,但是在这种情况下,F# 允许您通过在关键字成员— `x`之后指定名称来选择该参数的名称。
联合类型也可以有成员函数。定义它们的方式与定义记录类型的方式相同。下一个示例显示了一个联合类型`DrinkAmount`,其中添加了一个函数:
......@@ -106,11 +106,11 @@ F#提供了丰富的面向对象编程模型,允许您创建行为类似于 C#
茶:2
注意这是如何使用关键字`override`代替关键字`member`的。这具有替换或*覆盖*基本类型的现有功能的效果。对于与 F#类型相关联的函数成员来说,这不是一个很常见的做法,因为只有四种方法可以被覆盖:`ToString``Equals``GetHashCode``Finalize`。每一个。NET 类型从`System.Object`继承了这些。由于这些方法中的一些与 CLR 交互的方式,我唯一建议覆盖的是`ToString`。只有四种方法可用于重写,因为记录和联合类型不能作为基类或派生类,所以您不能继承方法来重写(从`System.Object`开始除外)。
注意这是如何使用关键字`override`代替关键字`member`的。这具有替换或*覆盖*基本类型的现有功能的效果。对于与 F# 类型相关联的函数成员来说,这不是一个很常见的做法,因为只有四种方法可以被覆盖:`ToString``Equals``GetHashCode``Finalize`。每一个。NET 类型从`System.Object`继承了这些。由于这些方法中的一些与 CLR 交互的方式,我唯一建议覆盖的是`ToString`。只有四种方法可用于重写,因为记录和联合类型不能作为基类或派生类,所以您不能继承方法来重写(从`System.Object`开始除外)。
## 定义类别
您已经看到了很多使用。NET 密件库;接下来,您将学习如何定义自己的类。在面向对象编程中,一个类应该对您正在创建的程序或库中使用的一些概念进行建模。例如,`String`模拟一组字符,`Process`类模拟一个操作系统进程。
您已经看到了很多使用。NET 密件库;接下来,您将学习如何定义自己的类。在面向对象编程中,一个类应该对您正在创建的程序或库中使用的一些概念进行建模。例如,`String`仿真一组字符,`Process`类仿真一个操作系统进程。
类是一种类型,因此类定义以`type`关键字开始,后面是类的名称和类的构造函数的参数(位于括号之间)。接下来是等号,后面是类的成员定义。类中最基本的成员叫做*方法*,这是一个可以访问类参数的函数。
......@@ -178,7 +178,7 @@ F#提供了丰富的面向对象编程模型,允许您创建行为类似于 C#
请注意成员如何访问类“`let`绑定,以及成员`GetFullName`如何返回预先计算的`fullName`值。
通常需要能够在类中改变值。例如,您可能需要在`User`类中提供`ChangePassword`方法来重置用户的密码。F#为您提供了两种方法来实现这一点。您可以使对象不可变——在这种情况下,您可以复制对象的参数,同时更改适当的值。这种方法通常被认为更适合函数式编程,但是如果对象有很多参数或者创建成本很高,就会有点不方便。例如,这样做可能计算成本很高,或者可能需要大量的输入/输出来构建它。以下示例说明了这种方法。请注意,在`ChangePassword`方法中,您如何调用`password`参数上的`hash`函数,将此函数与用户名一起传递给`User`对象的构造函数:
通常需要能够在类中改变值。例如,您可能需要在`User`类中提供`ChangePassword`方法来重置用户的密码。F# 为您提供了两种方法来实现这一点。您可以使对象不可变——在这种情况下,您可以复制对象的参数,同时更改适当的值。这种方法通常被认为更适合函数式编程,但是如果对象有很多参数或者创建成本很高,就会有点不方便。例如,这样做可能计算成本很高,或者可能需要大量的输入/输出来构建它。以下示例说明了这种方法。请注意,在`ChangePassword`方法中,您如何调用`password`参数上的`hash`函数,将此函数与用户名一起传递给`User`对象的构造函数:
```fs
// A class that represents a user.
......@@ -316,7 +316,7 @@ F#提供了丰富的面向对象编程模型,允许您创建行为类似于 C#
```
这是必要的,因为接口是在 F#中显式实现的。在使用方法`GetLogonMessage`之前,您必须有一个类型为`IUser`的标识符,而不仅仅是实现`IUser`的类的标识符。在示例的最后,您将以不同的方式解决这个问题。功能`logon`采用`IUser`类型的参数:
这是必要的,因为接口是在 F# 中显式实现的。在使用方法`GetLogonMessage`之前,您必须有一个类型为`IUser`的标识符,而不仅仅是实现`IUser`的类的标识符。在示例的最后,您将以不同的方式解决这个问题。功能`logon`采用`IUser`类型的参数:
```fs
let logon (iuser: IUser) =
......@@ -327,7 +327,7 @@ F#提供了丰富的面向对象编程模型,允许您创建行为类似于 C#
## 铸造
强制转换是一种通过丢弃信息来显式改变值的静态类型的方式,称为*向上强制转换*;或者重新发现它,这就是众所周知的*下降*。在 F#中,上转换和下转换有自己的运算符。类型层次结构从顶部的`obj`(或`System.Object`)开始,其所有后代都在它下面。向上转换将类型在层次结构中上移,而向下转换将类型在层次结构中下移。
强制转换是一种通过丢弃信息来显式改变值的静态类型的方式,称为*向上强制转换*;或者重新发现它,这就是众所周知的*下降*。在 F# 中,上转换和下转换有自己的运算符。类型层次结构从顶部的`obj`(或`System.Object`)开始,其所有后代都在它下面。向上转换将类型在层次结构中上移,而向下转换将类型在层次结构中下移。
上转换将值的静态类型更改为其祖先类型之一。这是安全的操作。编译器总是能够判断一个上转换是否会工作,因为它总是知道一个类型的所有祖先,所以它能够使用静态分析来确定一个上转换是否会成功。向上转换由冒号表示,后跟大于号(`:>`)。以下代码向您展示了如何使用上转换将`string`转换为`obj`:
......@@ -380,4 +380,4 @@ F#提供了丰富的面向对象编程模型,允许您创建行为类似于 C#
## 总结
您现在已经看到了如何在 F#中使用三种主要编程范例中的两种,以及 F#在任何混合风格的编码中有多灵活。
\ No newline at end of file
您现在已经看到了如何在 F# 中使用三种主要编程范例中的两种,以及 F# 在任何混合风格的编码中有多灵活。
\ No newline at end of file
# 第六章模拟和图形
# 六、仿真和图形
我喜欢把屏幕上的像素分成两部分:
* 低级图形编程,其中程序员负责控制屏幕上出现的几何图形的算法。这方面的一个例子可能是创建一个游戏,程序员直接负责绘制出现在屏幕上的各种元素。
* 高级用户界面创建,其中程序员负责将标签和文本框等公共元素组合在一起,以快速创建实用的用户界面。
F#可用于完成这两个部分。在本章中,我们将了解如何在 F#中创建图形,在下一章中,我们将了解如何将表单和其他更高级别的用户界面组合在一起。
F# 可用于完成这两个部分。在本章中,我们将了解如何在 F# 中创建图形,在下一章中,我们将了解如何将表单和其他更高级别的用户界面组合在一起。
我已经将 F#中的图形创建与模拟创建配对。这是因为你想要展示的图形很可能是某种模拟的结果。在这种情况下,为了保持例子简单,我选择创建一个弹跳球的模拟。还有许多其他的模拟可能会让你感兴趣,从康威的生命游戏到许多种类的分形。这些可以用类似于本章讨论的弹跳球模拟的方式来实现。甚至 F#中游戏的实现也可以大量借鉴本章中描述的技术。
我已经将 F# 中的图形创建与仿真创建配对。这是因为你想要展示的图形很可能是某种仿真的结果。在这种情况下,为了保持例子简单,我选择创建一个弹跳球的仿真。还有许多其他的仿真可能会让你感兴趣,从康威的生命游戏到许多种类的分形。这些可以用类似于本章讨论的弹跳球仿真的方式来实现。甚至 F# 中游戏的实现也可以大量借鉴本章中描述的技术。
弹跳球模拟本身将是一个简短的 F#模块,只生成模拟的原始数据。然后我们将展示如何在 WPF 和 Silverlight 中进行渲染。
弹跳球仿真本身将是一个简短的 F# 模块,只生成仿真的原始数据。然后我们将展示如何在 WPF 和 Silverlight 中进行渲染。
通过以这种方式创建应用程序,我们可以很好地分离关注点。模拟负责应用程序的数学部分——尽管在这种情况下数学非常简单——图形层只负责渲染模拟产生的数据。
通过以这种方式创建应用,我们可以很好地分离关注点。仿真负责应用的数学部分——尽管在这种情况下数学非常简单——图形层只负责渲染仿真产生的数据。
## 弹跳球模拟
## 弹跳球仿真
我们将要看到的弹跳球模拟可能是你能想象到的最简单的模拟。我们的目标只是简单地模拟一个在由四面墙限定的区域之间无限弹跳的球。要开始我们的球模拟,我们需要选择一种表示球的方式。为此,我们必须决定球的哪些信息对模拟很重要。在这种情况下,我们关心两件事:球的位置和速度。由于这是一个二维模拟,这些值可以用 X 和 Y 坐标中的四个数字来表示:两个代表位置,两个代表球的速度。我们可以包括许多关于球的其他细节,例如帮助计算球受到多大阻力的摩擦系数,或者关于球的旋转方向和速度的细节,以帮助确定球撞击物体时将如何移动,但是我们的模拟足够简单,这些细节只是不相关,所以我们不包括它们。球可以用一个简单的 F#记录来表示:
我们将要看到的弹跳球仿真可能是你能想象到的最简单的仿真。我们的目标只是简单地仿真一个在由四面墙限定的区域之间无限弹跳的球。要开始我们的球仿真,我们需要选择一种表示球的方式。为此,我们必须决定球的哪些信息对仿真很重要。在这种情况下,我们关心两件事:球的位置和速度。由于这是一个二维仿真,这些值可以用 X 和 Y 坐标中的四个数字来表示:两个代表位置,两个代表球的速度。我们可以包括许多关于球的其他细节,例如帮助计算球受到多大阻力的摩擦系数,或者关于球的旋转方向和速度的细节,以帮助确定球撞击物体时将如何移动,但是我们的仿真足够简单,这些细节只是不相关,所以我们不包括它们。球可以用一个简单的 F# 记录来表示:
```fs
/// Represents a ball by its position and its velocity.
......@@ -41,7 +41,7 @@ F#可用于完成这两个部分。在本章中,我们将了解如何在 F#中
既然我们已经有了我们球的代表,是时候考虑球将如何移动了。我们将把它实现为一个创建球的新的更新版本的函数:X 和 Y 坐标将根据球的行进方向进行更新,如果球碰到墙壁,速度值也将更新。为了做到这一点,我们需要一组额外的数据:球所绕过的区域的尺寸。这些将作为参数传递给计算球运动的函数。
我们需要做的计算非常简单。我们更新球的位置,如果球在界外,我们反转行进方向,再次更新位置。否则,我们就把球移到新的位置。我已经将这个算法实现为`Ball`类型的成员方法,并调用了`Move`方法。`Move`方法接受四个参数:`xMin``yMin``xMax``yMax`。这些定义了球运动的区域。将它实现为一个成员函数是很方便的,因为模拟很简单,关于环境的信息以单个数字的形式传递。更复杂的模拟可能会有两种变化。首先,将球运动环境的信息分组的环境类型将传递给函数,而不是单个数字。其次,可能有必要在一个模块中有一个集中的功能来协调模拟的不同元素。由于模拟很简单,我们将坚持成员和单个参数的便利性:
我们需要做的计算非常简单。我们更新球的位置,如果球在界外,我们反转行进方向,再次更新位置。否则,我们就把球移到新的位置。我已经将这个算法实现为`Ball`类型的成员方法,并调用了`Move`方法。`Move`方法接受四个参数:`xMin``yMin``xMax``yMax`。这些定义了球运动的区域。将它实现为一个成员函数是很方便的,因为仿真很简单,关于环境的信息以单个数字的形式传递。更复杂的仿真可能会有两种变化。首先,将球运动环境的信息分组的环境类型将传递给函数,而不是单个数字。其次,可能有必要在一个模块中有一个集中的功能来协调仿真的不同元素。由于仿真很简单,我们将坚持成员和单个参数的便利性:
```fs
/// Creates a new ball that has moved one step by its velocity within
......@@ -68,13 +68,13 @@ F#可用于完成这两个部分。在本章中,我们将了解如何在 F#中
请注意,实际上只有一个计算要做,但是我们需要做两次,对于 X 和 Y 坐标。为了实现这一点而不重复,我们定义了一个局部辅助函数`updatePositionAndVelocity`,它封装了更新坐标的计算。然后我们可以调用这个函数,将 X 和 Y 坐标的相关细节传递给它,这个函数将返回更新后的位置和速度。函数需要做的最后一件事是创建新的球定义并返回它。
这很好地说明了 F#的一些简单但重要的特性的使用,即使用局部函数来避免重复和使用元组来返回多个值。在没有局部函数的语言中,重复位置的计算可能很有诱惑力,因为它相当短而且简单。然而,随着代码的维护,任何重复都会导致定义随着时间的推移而出现分歧,这很可能会导致错误。通过元组返回多个值的能力也有助于这个过程。如果我们不能做到这一点,另一种方法是直接从我们的计算中更新变量。这将降低代码的灵活性,因为它将与正在更新的变量定义相耦合。
这很好地说明了 F# 的一些简单但重要的特性的使用,即使用局部函数来避免重复和使用元组来返回多个值。在没有局部函数的语言中,重复位置的计算可能很有诱惑力,因为它相当短而且简单。然而,随着代码的维护,任何重复都会导致定义随着时间的推移而出现分歧,这很可能会导致错误。通过元组返回多个值的能力也有助于这个过程。如果我们不能做到这一点,另一种方法是直接从我们的计算中更新变量。这将降低代码的灵活性,因为它将与正在更新的变量定义相耦合。
现在我们有了一个定义球如何运动的模型,我们需要测试这个模型,以确保我们有我们期望的行为。
## 测试模型
这种基于模型的方法的优点之一是它使测试非常简单。模型消耗并产生数据,因此很容易创建所需的输入,并验证模型输出了我们期望的值。使用 F#提供了使用 F# Interactive 创建测试脚本的额外便利,我们可以用单个键击来执行测试脚本。
这种基于模型的方法的优点之一是它使测试非常简单。模型消耗并产生数据,因此很容易创建所需的输入,并验证模型输出了我们期望的值。使用 F# 提供了使用 F# Interactive 创建测试脚本的额外便利,我们可以用单个键击来执行测试脚本。
我们想测试四种情况:
......@@ -83,7 +83,7 @@ F#可用于完成这两个部分。在本章中,我们将了解如何在 F#中
* 球不是在 X 方向撞击墙壁,而是在 Y 方向撞击墙壁,因此会在 Y 方向发生反弹。
* 球在 X 和 Y 两个方向上都碰到了墙壁,所以在两个方向上都发生了反弹。
所有测试将共享相同的结构;只有数据和断言会改变,所以我们可以编写一个通用测试,并使用它来测试所有四个场景。但是首先,为了开始我们的测试,我们需要添加一个新的 F#脚本,并添加几个命令来加载要测试的代码:
所有测试将共享相同的结构;只有数据和断言会改变,所以我们可以编写一个通用测试,并使用它来测试所有四个场景。但是首先,为了开始我们的测试,我们需要添加一个新的 F# 脚本,并添加几个命令来加载要测试的代码:
```fs
#load "BallModel.fs"
......@@ -150,11 +150,11 @@ F#可用于完成这两个部分。在本章中,我们将了解如何在 F#中
与使用单元测试框架(如 NUnit)相比,以这种基本方式进行测试有一些缺点。两个主要的缺点是需要显式调用您想要测试的函数,并且所有测试都将在第一个错误时停止。然而,这种测试方法的主要优点是不需要依赖外部框架。此外,这两种方法并不是不兼容的——我经常发现我只是直接使用 F# Interactive 开始测试一个项目,然后随着项目的成熟将我的测试移植到一个单元测试框架中。
## 绘制模拟结果
## 绘制仿真结果
现在,我们已经确定我们的模拟行为符合预期,是时候在屏幕上绘制结果了。我们将使用 WPF,然后使用 Silverlight 来实现这一点。虽然 WPF 和 Silverlight 中可用的控件是相似的,但是低级 API 有很大的不同,所以这将是在 GUI 库之间移植模拟的一个有趣的练习。
现在,我们已经确定我们的仿真行为符合预期,是时候在屏幕上绘制结果了。我们将使用 WPF,然后使用 Silverlight 来实现这一点。虽然 WPF 和 Silverlight 中可用的控件是相似的,但是低级 API 有很大的不同,所以这将是在 GUI 库之间移植仿真的一个有趣的练习。
WPF 有一套很好的绘制形状的方法。要访问这些方法,程序员只需要创建一个从`System.Windows.FrameworkElement`派生的类型,并覆盖传递给`DrawingContext`参数的`OnRender`方法。我们将创建一个类型,`BallRender`,它将负责运行模拟和渲染结果。我们将这样声明类型:
WPF 有一套很好的绘制形状的方法。要访问这些方法,程序员只需要创建一个从`System.Windows.FrameworkElement`派生的类型,并覆盖传递给`DrawingContext`参数的`OnRender`方法。我们将创建一个类型,`BallRender`,它将负责运行仿真和渲染结果。我们将这样声明类型:
```fs
type BallRender() as br =
......@@ -162,7 +162,7 @@ WPF 有一套很好的绘制形状的方法。要访问这些方法,程序员
```
为了渲染我们的模拟结果,我们需要缩放它们。我们选择模拟球在 100×100 的网格上弹跳,球的宽度只有一个点。如果我们试图在不缩放的情况下渲染它,即使在今天分辨率最低的屏幕上,它也会显得很小。这正是用户界面应该处理的那种几何计算。模拟应该可以自由使用便于进行模拟的值和表示。图形用户界面有责任将其转化为对最终用户有吸引力的东西。为了进行这些计算,我们需要一些与控件布局相关的常量,因此我们将在类的隐式构造函数中直接在类型定义之后声明这些常量:
为了渲染我们的仿真结果,我们需要缩放它们。我们选择仿真球在 100×100 的网格上弹跳,球的宽度只有一个点。如果我们试图在不缩放的情况下渲染它,即使在今天分辨率最低的屏幕上,它也会显得很小。这正是用户界面应该处理的那种几何计算。仿真应该可以自由使用便于进行仿真的值和表示。图形用户界面有责任将其转化为对最终用户有吸引力的东西。为了进行这些计算,我们需要一些与控件布局相关的常量,因此我们将在类的隐式构造函数中直接在类型定义之后声明这些常量:
```fs
// Ball size and limits.
......@@ -184,7 +184,7 @@ WPF 有一套很好的绘制形状的方法。要访问这些方法,程序员
```
为了运行模拟,我们需要两件事:对当前球对象的引用,它的位置将随着时间的推移而更新,以及负责执行模拟和更新球位置的计时器。当前球将被保存在参考单元中。引用单元格是内置于 F#中的可变类型,支持随时间变化的值。参考单元格由`ref`功能创建:
为了运行仿真,我们需要两件事:对当前球对象的引用,它的位置将随着时间的推移而更新,以及负责执行仿真和更新球位置的计时器。当前球将被保存在参考单元中。引用单元格是内置于 F# 中的可变类型,支持随时间变化的值。参考单元格由`ref`功能创建:
```fs
// A reference cell that holds the current ball instance.
......@@ -192,7 +192,7 @@ WPF 有一套很好的绘制形状的方法。要访问这些方法,程序员
```
计时器我们将使用 WPF 的`DispatcherTimer`。这是一个`Tick`事件在图形用户界面线程上执行的时间,因此不需要担心将模拟数据封送回到图形用户界面线程。这对于我们的简单模拟来说非常有效,因为在后台线程上执行`Tick`事件的计算速度非常快,实际上会损害性能,因为线程同步的开销会比模拟本身的成本更高。这将是你创建的许多模拟的情况;然而,随着模拟的复杂性和执行时间的增长,有必要在后台线程上运行它来保持图形用户界面的响应。当模拟变得如此昂贵以至于运行它需要一个后台线程时,引入一个新的抽象——通常是一个新的类——来运行它可能是值得的。但是,对于许多情况,简单的时间就可以了,这可以声明为类成员,如下例所示:
计时器我们将使用 WPF 的`DispatcherTimer`。这是一个`Tick`事件在图形用户界面线程上执行的时间,因此不需要担心将仿真数据封送回到图形用户界面线程。这对于我们的简单仿真来说非常有效,因为在后台线程上执行`Tick`事件的计算速度非常快,实际上会损害性能,因为线程同步的开销会比仿真本身的成本更高。这将是你创建的许多仿真的情况;然而,随着仿真的复杂性和执行时间的增长,有必要在后台线程上运行它来保持图形用户界面的响应。当仿真变得如此昂贵以至于运行它需要一个后台线程时,引入一个新的抽象——通常是一个新的类——来运行它可能是值得的。但是,对于许多情况,简单的时间就可以了,这可以声明为类成员,如下例所示:
```fs
// Timer for updating the ball's position.
......@@ -225,7 +225,7 @@ WPF 有一套很好的绘制形状的方法。要访问这些方法,程序员
让我们仔细看看定时器`Tick`事件的初始化。这是通过向事件添加匿名 lambda 函数进行的初始化。在这个函数中,我们首先通过调用当前球上的`Move`将球移动到它的新位置,然后将结果球存储在`ball`参考单元中。其次,我们调用控件的`InvalidateVisual()`方法——这将导致控件被重绘,以便用户可以看到球的新位置。
为了允许我们在控件上绘制,我们需要覆盖它的`OnRender`方法。这将为我们提供一个`DrawingContext`参数,该参数有一套很好的绘制图元的方法,如直线、矩形和椭圆。这使得画球非常简单。只需将模拟输出乘以适当的比例因子,然后加上边框的偏移量,即可计算出其当前位置。然后我们可以通过调用`DrawEllipse`方法来绘制球。我们还绘制了一个边框,使用户更容易看到球移动的区域:
为了允许我们在控件上绘制,我们需要覆盖它的`OnRender`方法。这将为我们提供一个`DrawingContext`参数,该参数有一套很好的绘制图元的方法,如直线、矩形和椭圆。这使得画球非常简单。只需将仿真输出乘以适当的比例因子,然后加上边框的偏移量,即可计算出其当前位置。然后我们可以通过调用`DrawEllipse`方法来绘制球。我们还绘制了一个边框,使用户更容易看到球移动的区域:
```fs
/// The function that takes care of actually drawing the ball.
......@@ -260,23 +260,23 @@ WPF 有一套很好的绘制形状的方法。要访问这些方法,程序员
```
这里我们看到第一个`let`绑定创建了我们的球渲染控件的一个新实例,下一个创建了一个将托管控件的窗口,在创建时设置了窗口的一些属性。最后,我们创建一个 WPF 应用程序类的新实例,并使用它通过调用其 run 方法来启动 WPF 事件循环。关于这一点最需要注意的是,我们在对 run 方法的调用中附加了一个`STAThread`属性,以确保事件循环使用了正确的线程模型。下图显示了程序执行时窗口的外观:
这里我们看到第一个`let`绑定创建了我们的球渲染控件的一个新实例,下一个创建了一个将托管控件的窗口,在创建时设置了窗口的一些属性。最后,我们创建一个 WPF 应用类的新实例,并使用它通过调用其 run 方法来启动 WPF 事件循环。关于这一点最需要注意的是,我们在对 run 方法的调用中附加了一个`STAThread`属性,以确保事件循环使用了正确的线程模型。下图显示了程序执行时窗口的外观:
![](img/image004.jpg)
图 3:弹跳球模拟
图 3:弹跳球仿真
现在,我们将快速了解如何将控件移植到 Silverlight,以利用 Silverlight 在网络浏览器中轻松下载和执行的能力,从而允许您创建的模拟或游戏轻松分发给用户。
现在,我们将快速了解如何将控件移植到 Silverlight,以利用 Silverlight 在网络浏览器中轻松下载和执行的能力,从而允许您创建的仿真或游戏轻松分发给用户。
Silverlight 的低级接口与 WPF 的不同;没有用于绘图的 API。如果要创建自定义形状,最好将形状写入可写位图,然后使用用户控件显示位图。
Visual Studio 2012 附带了一个用于创建已安装的 F# Silverlight 应用程序的模板。然而,使用在线提供的模板之一创建一个 Silverlight 应用程序更容易。本示例基于 Daniel Mohl 的 F# Web Application (Silverlight),可从[http://visualstudiogallery . msdn . Microsoft . com/f0e 9 a 557-3 FD 6-41 d9-8518-c 1735 b 382 c 73](http://visualstudiogallery.msdn.microsoft.com/f0e9a557-3fd6-41d9-8518-c1735b382c73)下载或在**新建项目**对话框中在线搜索找到,如下图所示:
Visual Studio 2012 附带了一个用于创建已安装的 F# Silverlight 应用的模板。然而,使用在线提供的模板之一创建一个 Silverlight 应用更容易。本示例基于 Daniel Mohl 的 F# Web Application (Silverlight),可从[http://visualstudiogallery . msdn . Microsoft . com/f0e 9 a 557-3 FD 6-41 d9-8518-c 1735 b 382 c 73](http://visualstudiogallery.msdn.microsoft.com/f0e9a557-3fd6-41d9-8518-c1735b382c73)下载或在**新建项目**对话框中在线搜索找到,如下图所示:
![](img/image005.jpg)
图 4: F#网络应用程序
图 4: F# 网络应用
这个应用程序附带了一些预定义的 XAML 页面,您需要删除。只保留模板附带的**appllogic . fs**文件的基本内容。
这个应用附带了一些预定义的 XAML 页面,您需要删除。只保留模板附带的**appllogic . fs**文件的基本内容。
我们将像构建 WPF 版本一样构建代码。我们将创建一个负责呈现弹跳球的控件,然后添加在 Silverlight 中显示这个控件所需的样板代码。球渲染控件从声明一个继承自`UserControl`的新类型开始:
......@@ -357,7 +357,7 @@ Visual Studio 2012 附带了一个用于创建已安装的 F# Silverlight 应用
```
`drawBall`功能取一个球和一种颜色来画。该函数使用 F#的`Seq.iteri`函数枚举`ballTemplate`,该函数枚举任何`IEnumerable<T>`集合并将给定的函数应用于该集合的每个成员。应用于每个成员的函数还被传递一个代表当前值索引的整数(`iter`是迭代的缩写,`iteri`末尾的`i`是索引的缩写)。因此,我们首先枚举行,然后是单个的列,并且每当我们找到一个星号时,就用这两个索引作为给定球位置的偏移量来绘制一个像素。你可能想通过改变球中的星号来尝试不同的球布局,看看你是否能找到一个更令人愉快的球形状。
`drawBall`功能取一个球和一种颜色来画。该函数使用 F# `Seq.iteri`函数枚举`ballTemplate`,该函数枚举任何`IEnumerable<T>`集合并将给定的函数应用于该集合的每个成员。应用于每个成员的函数还被传递一个代表当前值索引的整数(`iter`是迭代的缩写,`iteri`末尾的`i`是索引的缩写)。因此,我们首先枚举行,然后是单个的列,并且每当我们找到一个星号时,就用这两个索引作为给定球位置的偏移量来绘制一个像素。你可能想通过改变球中的星号来尝试不同的球布局,看看你是否能找到一个更令人愉快的球形状。
现在我们可以画一个球,我们需要能够在球将反弹的区域周围画一个边界。我们将通过创建一个`drawSquare`函数来做到这一点:
......@@ -434,7 +434,7 @@ Visual Studio 2012 附带了一个用于创建已安装的 F# Silverlight 应用
```
这就完成了我们展示弹跳球的控制工作。剩下的就是 Silverlight 管道,它将在应用程序启动时显示控件。这很简单:
这就完成了我们展示弹跳球的控制工作。剩下的就是 Silverlight 管道,它将在应用启动时显示控件。这很简单:
```fs
type App() as this =
......@@ -444,14 +444,14 @@ Visual Studio 2012 附带了一个用于创建已安装的 F# Silverlight 应用
```
当然,仍然需要创建一个测试 HTML 页面,该页面将加载 Silverlight 运行时以及我们刚刚创建的应用程序,但是我们使用的模板应该考虑到这一点。查看结果模拟应该只需按 F5 即可。您可以在下图中看到在我的机器上运行的模拟
当然,仍然需要创建一个测试 HTML 页面,该页面将加载 Silverlight 运行时以及我们刚刚创建的应用,但是我们使用的模板应该考虑到这一点。查看结果仿真应该只需按 F5 即可。您可以在下图中看到在我的机器上运行的仿真
![](img/image006.jpg)
图 5:弹跳球模拟
图 5:弹跳球仿真
尽管 Silverlight 的 API 提供的低级绘图功能少得多,但我们已经看到,通过创建一些简单易用的抽象,创建带有自定义图形的应用程序仍然相当容易。
尽管 Silverlight 的 API 提供的低级绘图功能少得多,但我们已经看到,通过创建一些简单易用的抽象,创建带有自定义图形的应用仍然相当容易。
## 总结
在本章中,我们已经看到了如何在 F#中构建一个简单的模拟,以及如何使用低级绘图 API 在 F#中创建自定义图形。
\ No newline at end of file
在本章中,我们已经看到了如何在 F# 中构建一个简单的仿真,以及如何使用低级绘图 API 在 F# 中创建自定义图形。
\ No newline at end of file
# 第 7 章表单用户界面
# 七、表单用户界面
前一章讨论了使用 F#创建自定义图形。在本章中,我们将了解如何在 F#中创建基于表单的应用程序。有许多不同的技术可以在中创建基于表单的应用程序。NET,包括 Windows 窗体、WPF、Silverlight 和 GTK#。由于这些技术都是基于相似的想法,我们将继续关注一个:WPF。
前一章讨论了使用 F# 创建自定义图形。在本章中,我们将了解如何在 F# 中创建基于表单的应用。有许多不同的技术可以在中创建基于表单的应用。NET,包括 Windows 窗体、WPF、Silverlight 和 GTK#。由于这些技术都是基于相似的想法,我们将继续关注一个:WPF。
## 简单的形式
WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,第二种是用一种叫做 XAML 的 XML 方言定义表单。XAML 是有用的,因为有几个不同的用户界面设计包可用,允许设计师在 XAML 创造丰富的用户体验。然后,设计人员可以将它们传递给开发人员,将它们连接到应用程序中。我们将在本章的后面讨论 XAML;现在,我们将了解如何直接使用 WPF 对象创建表单。
WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,第二种是用一种叫做 XAML 的 XML 方言定义表单。XAML 是有用的,因为有几个不同的用户界面设计包可用,允许设计师在 XAML 创造丰富的用户体验。然后,设计人员可以将它们传递给开发人员,将它们连接到应用中。我们将在本章的后面讨论 XAML;现在,我们将了解如何直接使用 WPF 对象创建表单。
当您想要创建仅由几个控件组成的简单表单时,直接使用对象是一种很好的方法。在这种情况下,表单的创建可能足够简单,不值得使用设计师的努力。此外,以这种方式创建表单是一种很好的学习体验,因为它可以帮助您更好地理解每种控件类型是如何组合在一起的。
我们希望创建一个具有三个控件的窗体:一个文本框、文本框前面的描述性标签和紧接其下的按钮。为了创建我们想要的布局,我们将使用两个堆栈面板:一个水平面板用于保存标签和文本框,一个垂直面板用于保存水平堆栈面板及其正下方的按钮。
为了创建应用程序,我们将启动一个新的 F#项目,并添加对**PresentationCore.dll****PresentationFramework.dll****System.XAML.dll**(尽管我们在此阶段没有使用任何 XAML,但这是必需的)以及最后的**WindowsBase.dll**的引用。然后,我们需要一些开放语句来提供对名称空间的简单访问:
为了创建应用,我们将启动一个新的 F# 项目,并添加对**PresentationCore.dll****PresentationFramework.dll****System.XAML.dll**(尽管我们在此阶段没有使用任何 XAML,但这是必需的)以及最后的**WindowsBase.dll**的引用。然后,我们需要一些开放语句来提供对名称空间的简单访问:
```fs
open System
......@@ -19,7 +19,7 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
我们几乎已经准备好创建我们的控件了,但是首先我们需要整理一下 WPF 的一个有点烦人的方面。在 WPF,有一个名为`UIElementCollection`的集合,用于存储子控件。这个集合有一个`Add`方法,它既有副作用——将控件添加到集合中——又有返回值——新添加的控件的索引。我们对控件的索引几乎没有任何兴趣,所以可以忽略返回值;然而,F#会给我们一个警告,我们忽略了一个返回值,因为这通常是函数式编程中错误的指示。为了解决这个问题,我们需要添加一个简短的助手函数,在不返回索引的情况下将该项添加到控件集合中。在 WPF,并非所有控件都可以有子集合;只有继承自`Panel`的控件拥有`Children`集合。这就是为什么我们在下面的示例中向 helper 函数的第二个参数添加类型约束。该函数将只接受来自`Panel`的控制作为其第二个参数。`addChild`助手功能的实现也显示在下面的代码中。它只是将给定的控件添加到`Children`集合中,并忽略结果。
我们几乎已经准备好创建我们的控件了,但是首先我们需要整理一下 WPF 的一个有点烦人的方面。在 WPF,有一个名为`UIElementCollection`的集合,用于存储子控件。这个集合有一个`Add`方法,它既有副作用——将控件添加到集合中——又有返回值——新添加的控件的索引。我们对控件的索引几乎没有任何兴趣,所以可以忽略返回值;然而,F# 会给我们一个警告,我们忽略了一个返回值,因为这通常是函数式编程中错误的指示。为了解决这个问题,我们需要添加一个简短的助手函数,在不返回索引的情况下将该项添加到控件集合中。在 WPF,并非所有控件都可以有子集合;只有继承自`Panel`的控件拥有`Children`集合。这就是为什么我们在下面的示例中向 helper 函数的第二个参数添加类型约束。该函数将只接受来自`Panel`的控制作为其第二个参数。`addChild`助手功能的实现也显示在下面的代码中。它只是将给定的控件添加到`Children`集合中,并忽略结果。
```fs
// Adds a child to a panel control.
......@@ -61,7 +61,7 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
这就是创建表单的全部内容。为了显示表单,我们需要在一个窗口中托管它,然后创建一个 WPF 应用程序来创建一个事件循环并显示控件。这样做的代码如下:
这就是创建表单的全部内容。为了显示表单,我们需要在一个窗口中托管它,然后创建一个 WPF 应用来创建一个事件循环并显示控件。这样做的代码如下:
```fs
// Create the window that will hold the controls.
......@@ -76,15 +76,15 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
正如我们在上一章中看到的,当在 WPF 事件循环中启动时,我们需要确保`STAThread`属性被附加到启动方法调用中。在执行这个应用程序时,我们应该会看到以下表单:
正如我们在上一章中看到的,当在 WPF 事件循环中启动时,我们需要确保`STAThread`属性被附加到启动方法调用中。在执行这个应用时,我们应该会看到以下表单:
![](img/image007.png)
图 6:在 F#中创建的 WPF 表单
图 6:在 F# 中创建的 WPF 表单
## 使用 XAML 的表单
虽然自己创建用户界面的方法可以很好地适用于简单的应用程序,但是如果我们想要更复杂的风格和效果,最好使用 XAML。在本例中,我们将看到创建一个与前一个表单具有完全相同的布局和功能的表单。唯一改变的是布局现在将在 XAML 定义,表单的行为将使用 F#定义。我们的形式的 XAML 定义如下:
虽然自己创建用户界面的方法可以很好地适用于简单的应用,但是如果我们想要更复杂的风格和效果,最好使用 XAML。在本例中,我们将看到创建一个与前一个表单具有完全相同的布局和功能的表单。唯一改变的是布局现在将在 XAML 定义,表单的行为将使用 F# 定义。我们的形式的 XAML 定义如下:
```fs
<Window
......@@ -101,11 +101,11 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
从 XAML 的定义中很容易看出,布局像以前一样由两个堆栈面板、一个标签、一个文本框和一个按钮组成。我们给文本框和按钮起了名字,因为这些是我们需要从代码中访问的对象。文本框称为`MessageTextBox`,按钮称为`PressMeButton`。与 C#相比,F#与 XAML 的集成稍少,因此我们需要定义几个助手函数来帮助我们加载 XAML 并访问其中定义的控件。在这样的一个小例子中,这些助手所需的额外代码看起来是很大的开销,但是在一个更实际的应用程序中,随着助手函数在整个应用程序中被重用,这些额外函数的成本将很快分摊。
从 XAML 的定义中很容易看出,布局像以前一样由两个堆栈面板、一个标签、一个文本框和一个按钮组成。我们给文本框和按钮起了名字,因为这些是我们需要从代码中访问的对象。文本框称为`MessageTextBox`,按钮称为`PressMeButton`。与 C#相比,F# 与 XAML 的集成稍少,因此我们需要定义几个助手函数来帮助我们加载 XAML 并访问其中定义的控件。在这样的一个小例子中,这些助手所需的额外代码看起来是很大的开销,但是在一个更实际的应用中,随着助手函数在整个应用中被重用,这些额外函数的成本将很快分摊。
我们刚刚看到的 XAML 定义需要作为`MainWindow.xaml`添加到 F#项目中。在该文件的属性窗口中,您需要将构建操作设置为`EmbeddedResource`,这样它将作为资源流嵌入到程序集清单中。这几乎是我们在 XAML 部分所需要做的。
我们刚刚看到的 XAML 定义需要作为`MainWindow.xaml`添加到 F# 项目中。在该文件的属性窗口中,您需要将构建操作设置为`EmbeddedResource`,这样它将作为资源流嵌入到程序集清单中。这几乎是我们在 XAML 部分所需要做的。
在我们开始 F#部分之前,我们需要引用与之前应用程序相同的组件(**PresentationCore.dll****PresentationFramework.dll****System.XAML.dll****SystemXML.dll**),并且我们需要以下`open`语句。
在我们开始 F# 部分之前,我们需要引用与之前应用相同的组件(**PresentationCore.dll****PresentationFramework.dll****System.XAML.dll****SystemXML.dll**),并且我们需要以下`open`语句。
```fs
open System
......@@ -139,7 +139,7 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
虽然这是一种完全可以接受的方法,但是 F#提供了一种有趣的替代方法,可以为您节省一点打字时间。F#允许您定义一个自定义的动态运算符,其行为类似于 C#中的动态关键字。动态运算符是一个问号(`?`),它允许您进行看起来像方法或属性调用的操作,但将方法或属性名称恢复为字符串,以便您可以进行动态查找。下面是我们如何定义动态运算符:
虽然这是一种完全可以接受的方法,但是 F# 提供了一种有趣的替代方法,可以为您节省一点打字时间。F# 允许您定义一个自定义的动态运算符,其行为类似于 C#中的动态关键字。动态运算符是一个问号(`?`),它允许您进行看起来像方法或属性调用的操作,但将方法或属性名称恢复为字符串,以便您可以进行动态查找。下面是我们如何定义动态运算符:
```fs
// Dynamic lookup operator for controls.
......@@ -150,7 +150,7 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
这里我们看到一个自定义操作符,后跟两个参数:第一个是 type object,第二个是 type string。第一个对象是我们动态调用的对象,第二个是被调用的属性或方法的名称。运算符模式的实现与对象相匹配,以检查它是否属于`FrameworkElement`类型。如果不是,我们抛出一个异常。如果是,我们称之为框架元素的`FindName`方法。如果目前还没有 100%清楚,不要担心;动态自定义操作符是 F#更高级的功能之一。需要保留的是,现在定义这个运算符可以让您在窗口上进行动态查找,这可以用来找到我们感兴趣使用的按钮:
这里我们看到一个自定义操作符,后跟两个参数:第一个是 type object,第二个是 type string。第一个对象是我们动态调用的对象,第二个是被调用的属性或方法的名称。运算符模式的实现与对象相匹配,以检查它是否属于`FrameworkElement`类型。如果不是,我们抛出一个异常。如果是,我们称之为框架元素的`FindName`方法。如果目前还没有 100%清楚,不要担心;动态自定义操作符是 F# 更高级的功能之一。需要保留的是,现在定义这个运算符可以让您在窗口上进行动态查找,这可以用来找到我们感兴趣使用的按钮:
```fs
let pressMeButton: Button = win?PressMeButton
......@@ -192,11 +192,11 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
在 WPF 实现表单的一种常见方式是使用 MVVM 设计模式。对于那些可能不熟悉 MVVM 的人,让我们快速回顾一下这个设计模式是什么。MVVM 代表模型-视图-视图模型。在这种设计模式中,模型是表示数据或域的对象。视图是用户界面——在这种情况下,我们将使用 XAML,但是如果我们愿意,我们可以直接使用 WPF 对象。视图模型是位于视图和模型之间的对象层,用于提供它们之间的映射,并对图形用户界面中的事件和变化做出反应。视图通过 WPF 强大的数据绑定机制与视图模型通信。
我们将看看如何以 MVVM 风格实现一个简单的表单。MVVM 是一个很大的话题,这个例子并不是为了展示实现 MVVM 设计模式所涉及的所有技术。相反,它旨在让你尝试在 F#中做 MVVM,并允许你应用你现有的 MVVM 知识,或来自其他 MVVM 文章的信息,到 F# MVVM 实现。我们要看的例子是如何用 MVVM 风格实现一个简单的主细节页面。我们正在构建的应用程序将用于查看我们的机器人库存。我们将看到一个机器人列表,并被允许点击一个机器人来查看更多关于它的细节。
我们将看看如何以 MVVM 风格实现一个简单的表单。MVVM 是一个很大的话题,这个例子并不是为了展示实现 MVVM 设计模式所涉及的所有技术。相反,它旨在让你尝试在 F# 中做 MVVM,并允许你应用你现有的 MVVM 知识,或来自其他 MVVM 文章的信息,到 F# MVVM 实现。我们要看的例子是如何用 MVVM 风格实现一个简单的主细节页面。我们正在构建的应用将用于查看我们的机器人库存。我们将看到一个机器人列表,并被允许点击一个机器人来查看更多关于它的细节。
正如我们已经说过的,MVVM 将代码分成三个不同的组件:模型、视图和视图模型。除此之外,我们将添加一个“存储库”,它将抽象数据访问逻辑。我们将按照以下顺序浏览应用程序的各个部分:我们将查看模型和存储库,然后是视图模型,最后是视图。
正如我们已经说过的,MVVM 将代码分成三个不同的组件:模型、视图和视图模型。除此之外,我们将添加一个“存储库”,它将抽象数据访问逻辑。我们将按照以下顺序浏览应用的各个部分:我们将查看模型和存储库,然后是视图模型,最后是视图。
因为我们的应用程序是一些数据的简单只读视图,所以没有域逻辑。这意味着应用程序的模型部分非常简单——只需要一个数据容器。对于数据容器,我们将使用 F#记录类型:
因为我们的应用是一些数据的简单只读视图,所以没有域逻辑。这意味着应用的模型部分非常简单——只需要一个数据容器。对于数据容器,我们将使用 F# 记录类型:
```fs
type Robot =
......@@ -206,7 +206,7 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
如你所见,我们将只存储关于我们机器人的三条信息:它的名字、它如何移动以及它的武器。我们的应用程序非常简单,这就是我们需要的模型。为了简单起见,我们将对存储库中的数据进行硬编码,但是在一个更实际的例子中,这将来自数据库。
如你所见,我们将只存储关于我们机器人的三条信息:它的名字、它如何移动以及它的武器。我们的应用非常简单,这就是我们需要的模型。为了简单起见,我们将对存储库中的数据进行硬编码,但是在一个更实际的例子中,这将来自数据库。
```fs
type RobotsRepository() =
......@@ -301,7 +301,7 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
我们在这里看到,事件是作为`INotifyPropertyChanged`接口实现的一部分公开的,该接口只有一个成员:事件`PropertyChanged`。我们需要用`[<CLIEvent>]`属性标记`PropertyChanged`,这样 F#编译器就知道它应该生成一个与其他 CLR 语言兼容的事件,比如 C#,因为 F#有自己优化的事件系统。既然我们已经公开了事件,我们需要能够调用事件。我们通过创造一个`OnPropertyChanged`方法来触发事件。
我们在这里看到,事件是作为`INotifyPropertyChanged`接口实现的一部分公开的,该接口只有一个成员:事件`PropertyChanged`。我们需要用`[<CLIEvent>]`属性标记`PropertyChanged`,这样 F# 编译器就知道它应该生成一个与其他 CLR 语言兼容的事件,比如 C#,因为 F# 有自己优化的事件系统。既然我们已经公开了事件,我们需要能够调用事件。我们通过创造一个`OnPropertyChanged`方法来触发事件。
```fs
// Helper method to raise the property changed event.
......@@ -312,7 +312,7 @@ WPF 允许您以两种方式定义表单:第一种是直接操作 WPF 对象,
```
类的这三个成员,`propertyChangedEvent`字段,`INotifyPropertyChanged`的接口实现,`OnPropertyChanged`方法通常放在一个基类中,这样它们就可以在应用程序的所有视图模型之间共享。为了简化这个例子,我将它们与视图模型放在同一个类中,因为在这个应用程序中我们只有一个视图模型。接下来的两个字段与允许视图绑定到视图模型的属性相关。第一个字段`robots`保存所有机器人数据的集合:
类的这三个成员,`propertyChangedEvent`字段,`INotifyPropertyChanged`的接口实现,`OnPropertyChanged`方法通常放在一个基类中,这样它们就可以在应用的所有视图模型之间共享。为了简化这个例子,我将它们与视图模型放在同一个类中,因为在这个应用中我们只有一个视图模型。接下来的两个字段与允许视图绑定到视图模型的属性相关。第一个字段`robots`保存所有机器人数据的集合:
```fs
// Our collection of robots.
......@@ -357,13 +357,13 @@ getter 只是返回我们的字段,但是 setter 必须做两件事。它必
我们已经查看了构成视图模型的所有内容,现在我们将查看视图本身。由于这个视图比我们之前看到的 XAML 视图更复杂,我认为首先看一下布局的图像,然后看一下完整的 XAML 列表,最后浏览一下 XAML 的要点会有所帮助。XAML 的上市时间相当长,但不要太担心这个。大多数 XAML 只是描述了控件的位置;只有几个重要的部分我们需要更详细地研究。
首先,让我们看一下应用程序的截图:
首先,让我们看一下应用的截图:
![](img/image008.png)
图 7:机器人库存应用
这里我们看到应用程序由左侧的列表框和右侧显示机器人细节的一些标签组成。随着用户改变选择,机器人的细节也会改变。
这里我们看到应用由左侧的列表框和右侧显示机器人细节的一些标签组成。随着用户改变选择,机器人的细节也会改变。
完整视图列表是这样的:
......@@ -537,4 +537,4 @@ xmlns:ViewModel = " clr-namespace:fs 简洁。RobotsMvvm . ViewModel 组装=表
## 总结
我们现在已经看到了 F#如何以几种不同的方式用于 WPF,包括使用 WPF 强大的 MVVM 设计模式。希望这给了你一个很好的想法,如何使用 F#来创建需要用户输入结构化数据的业务应用程序。我们关注的是 WPF,但是在。NET 框架。虽然我们没有看到这些库的具体例子,但它们中的大多数都与 WPF 有相似的概念。希望你会发现这一章中的一些想法对其他库有用。
\ No newline at end of file
我们现在已经看到了 F# 如何以几种不同的方式用于 WPF,包括使用 WPF 强大的 MVVM 设计模式。希望这给了你一个很好的想法,如何使用 F# 来创建需要用户输入结构化数据的业务应用。我们关注的是 WPF,但是在。NET 框架。虽然我们没有看到这些库的具体例子,但它们中的大多数都与 WPF 有相似的概念。希望你会发现这一章中的一些想法对其他库有用。
\ No newline at end of file
# 第 8 章创建应用程序
# 八、创建应用
在这一章中,我想展示用 F#编写一个应用程序涉及到什么。对于我们将要实现的东西来说,“应用程序”可能是一个过于宏大的词,但重点是展示 F#与其他技术和框架的交互方式,就像你要创建一个真实世界的应用程序一样。
在这一章中,我想展示用 F# 编写一个应用涉及到什么。对于我们将要实现的东西来说,“应用”可能是一个过于宏大的词,但重点是展示 F# 与其他技术和框架的交互方式,就像你要创建一个真实世界的应用一样。
我们将在 HTML 网页中创建一个自动完成下拉列表。想法是输入你感兴趣的村庄、城镇或城市,自动完成功能将帮助你在系统中找到正确的。我们将使用 RavenDB 作为我们的数据存储,使用 PicoMvc 作为 Mvc 网络框架来处理应用层。PicoMvc 是我专门为 F#创建的一个 Mvc 框架。它很大程度上是受 OpenRasta、FubuMvc 和一点点 ASP.NET MVC 的启发,所以如果您不喜欢使用 PicoMvc,这里给出的例子可能很容易移植到这些框架中的任何一个。RavenDB 是一个用 C#实现的 NoSQL 数据库。我喜欢这个数据库,不是因为它速度快,并且通过分片为可伸缩性提供了很好的支持,这是毫无疑问的,而是因为它的实现者真正关注的是让开发人员非常容易使用它。
我们将在 HTML 网页中创建一个自动完成下拉列表。想法是输入你感兴趣的村庄、城镇或城市,自动完成功能将帮助你在系统中找到正确的。我们将使用 RavenDB 作为我们的数据存储,使用 PicoMvc 作为 Mvc 网络框架来处理应用层。PicoMvc 是我专门为 F# 创建的一个 Mvc 框架。它很大程度上是受 OpenRasta、FubuMvc 和一点点 ASP.NET MVC 的启发,所以如果您不喜欢使用 PicoMvc,这里给出的例子可能很容易移植到这些框架中的任何一个。RavenDB 是一个用 C#实现的 NoSQL 数据库。我喜欢这个数据库,不是因为它速度快,并且通过分片为可伸缩性提供了很好的支持,这是毫无疑问的,而是因为它的实现者真正关注的是让开发人员非常容易使用它。
如今,在 HTML 表单中创建自动完成下拉列表已经相当普遍了,有一个很好的 jQuery 插件可以处理 UI 方面的事情。将数据加载到 RavenDB 中,然后公开一个服务,该服务将返回与用户搜索相对应的 JSON 记录,这将是本章的重点。
......@@ -18,7 +18,7 @@
**Common** 项目包含我们的类型的定义,这些类型将存储在 RavenDB 中。通用项目将参考**负载社区****网络**项目。**加载社区**项目将包含将数据加载到 RavenDB 的 ETL 逻辑。**网页**项目将包含驱动网页的逻辑。 **WebHost** 项目是一个 c#“web 项目”——它只是为了保存项目的 HTML 部分,并使启动 web 服务器进行调试变得更容易。
现在项目已经建立到我们满意的程度,是时候看看应用程序的 ETL(提取、转换和加载)部分的一些实际代码了。
现在项目已经建立到我们满意的程度,是时候看看应用的 ETL(提取、转换和加载)部分的一些实际代码了。
## 提取/转换/加载
......@@ -82,7 +82,7 @@
现在我们在 RavenDB 中有了数据,我们希望能够向用户显示数据。为此,我们需要创建一个服务,将 JSON 文档返回给客户端。用 PicoMvc 和 RavenDB 实现服务很简单,但是要实现它,我们需要配置 PicoMvc 并在 RavenDB 中创建一个索引,以便我们可以查询它。
PicoMvc 旨在将 F#函数映射到 URL 和 HTTP 动词。这个想法是,基本框架独立于 web 服务器和主机平台,但是我们提供了钩子,允许您将 PicoMvc 插入现有平台。目前,唯一存在的钩子是将 PicoMvc 插入 ASP.NET 平台,这是通过一个名为`PicoMvcRouteHandler`的类来完成的,它是一个 ASP.NET 路由处理器。这个想法是,您向 ASP.NET 运行时注册这个路由处理程序,它提供了所有的管道,用于将 ASP.NET HTTP 处理程序将接收的调用和请求映射到您通过 PicoMvc 定义的处理程序函数。
PicoMvc 旨在将 F# 函数映射到 URL 和 HTTP 动词。这个想法是,基本框架独立于 web 服务器和主机平台,但是我们提供了钩子,允许您将 PicoMvc 插入现有平台。目前,唯一存在的钩子是将 PicoMvc 插入 ASP.NET 平台,这是通过一个名为`PicoMvcRouteHandler`的类来完成的,它是一个 ASP.NET 路由处理器。这个想法是,您向 ASP.NET 运行时注册这个路由处理程序,它提供了所有的管道,用于将 ASP.NET HTTP 处理程序将接收的调用和请求映射到您通过 PicoMvc 定义的处理程序函数。
因为`PicoMvcRouteHandler`只是一个普通的 HTTP 处理程序。它要求您在`global.asax`中向 ASP.NET 运行时注册它:
......@@ -91,7 +91,7 @@ PicoMvc 旨在将 F#函数映射到 URL 和 HTTP 动词。这个想法是,基
```
`PicoMvcRouteHandler`也需要一点配置。路由处理程序的第一个参数是一个字符串,它告诉它添加路由处理程序时匹配的 URL 的名称。然后,它将使用这个作为 URL 来解析将调用哪个函数。下一个参数是路由表,它保存着应该为哪些 URL 调用哪些函数的信息。您可以通过调用静态方法`LoadFromCurrentAssemblies`,使 PicoMvc 自动在所有加载的程序集中搜索标记有`[<Controller>]`属性的 F#模块。
`PicoMvcRouteHandler`也需要一点配置。路由处理程序的第一个参数是一个字符串,它告诉它添加路由处理程序时匹配的 URL 的名称。然后,它将使用这个作为 URL 来解析将调用哪个函数。下一个参数是路由表,它保存着应该为哪些 URL 调用哪些函数的信息。您可以通过调用静态方法`LoadFromCurrentAssemblies`,使 PicoMvc 自动在所有加载的程序集中搜索标记有`[<Controller>]`属性的 F# 模块。
```fs
let routingTables = RoutingTable.LoadFromCurrentAssemblies()
......@@ -163,7 +163,7 @@ PicoMvc 旨在将 F#函数映射到 URL 和 HTTP 动词。这个想法是,基
首先我们定义一个类型,`AutoCompleteResult`,来保存我们想要发送回客户端的结果。这将被直接翻译成 JSON。
接下来我们定义一个 PicoMvc 控制器。这只是一个标有`[<Controller>]`属性的 F#模块。因为模块的名字是`Commune`,所以它会暴露在 **~/commune.xxx** 的网址上,其中 xxx 用于确定哪个视图将用于渲染结果(我们如何选择视图来处理结果将在后面讨论)。在这种情况下,我们的网址将是 **~/commune.json** ,视图将把结果呈现为 json。
接下来我们定义一个 PicoMvc 控制器。这只是一个标有`[<Controller>]`属性的 F# 模块。因为模块的名字是`Commune`,所以它会暴露在 **~/commune.xxx** 的网址上,其中 xxx 用于确定哪个视图将用于渲染结果(我们如何选择视图来处理结果将在后面讨论)。在这种情况下,我们的网址将是 **~/commune.json** ,视图将把结果呈现为 json。
我们的控制器定义了处理它可能接收到的不同 HTTP 动词的函数。在这种情况下,我们只想处理 GET 动词请求,所以我们定义了一个`get`函数。该函数的参数项将由查询字符串中的项目`term`填充,`store`将由对 RavenDB 文档存储的引用填充。
......@@ -179,4 +179,4 @@ PicoMvc 旨在将 F#函数映射到 URL 和 HTTP 动词。这个想法是,基
## 总结
就这样。这需要一点解释,但是我们在这个过程中学习了很多关于 PicoMvc 的知识,最终的解决方案根本没有太多代码。事实上,整个解决方案大约有 150 行 F#和 C#。你可以在[的【github 上的 PicoMvc 的**示例**目录中看到最终的解决方案。](https://github.com/robertpi/PicoMvc/tree/master/examples/AutoComplete)
\ No newline at end of file
就这样。这需要一点解释,但是我们在这个过程中学习了很多关于 PicoMvc 的知识,最终的解决方案根本没有太多代码。事实上,整个解决方案大约有 150 行 F# 和 C#。你可以在[的【github 上的 PicoMvc 的**示例**目录中看到最终的解决方案。](https://github.com/robertpi/PicoMvc/tree/master/examples/AutoComplete)
\ No newline at end of file
# 进一步阅读
# 九、进一步阅读
希望你喜欢这篇关于 F#的介绍。如果它启发了你去学习更多关于 F#的知识,还有很多其他的材料可以用来进一步阅读。官方的 for F#开发中心有大量关于 F #的资料和许多指向 http://fsharp.net社区资源的跳转点。
希望你喜欢这篇关于 F# 的介绍。如果它启发了你去学习更多关于 F# 的知识,还有很多其他的材料可以用来进一步阅读。官方的 for F# 开发中心有大量关于 F #的资料和许多指向 http://fsharp.net社区资源的跳转点。
关于 F#的另一个很好的信息来源是包含 F#内容的开发者博客。各种各样的博主用不同的观点写 F#的文章。这里只是几个开始:
关于 F# 的另一个很好的信息来源是包含 F# 内容的开发者博客。各种各样的博主用不同的观点写 F# 的文章。这里只是几个开始:
* 唐·赛姆在 http://blogs.msdn.com/b/dsyme/的博客。F#项目的 F#创建者和首席架构师。
* F#团队博客:[http://blogs.msdn.com/b/fsharpteam/](http://blogs.msdn.com/b/fsharpteam/)
* 唐·赛姆在 http://blogs.msdn.com/b/dsyme/的博客。F# 项目的 F# 创建者和首席架构师。
* F# 团队博客:[http://blogs.msdn.com/b/fsharpteam/](http://blogs.msdn.com/b/fsharpteam/)
* 我自己的博客,虽然更新可能有点零星:[http://strangelights.com/blog](http://strangelights.com/blog)
F#社区有很多人活跃在推特上。定期关注推特的 *fsharp* 标签是了解最新 F#新闻的另一个好方法。
F# 社区有很多人活跃在推特上。定期关注推特的 *fsharp* 标签是了解最新 F# 新闻的另一个好方法。
如果你对更多关于 F#的书感兴趣,可以考虑:
如果你对更多关于 F# 的书感兴趣,可以考虑:
*始于 F#* 罗伯特·皮克林,阿珀斯,2009 年 12 月。我自己对 F#的介绍。它包括对语言语法的更完整和详细的解释,以及关于并行编程和面向语言编程等主题的章节。
*始于 F#* 罗伯特·皮克林,阿珀斯,2009 年 12 月。我自己对 F# 的介绍。它包括对语言语法的更完整和详细的解释,以及关于并行编程和面向语言编程等主题的章节。
*唐·赛姆等人的专家 F#* ,Apress,2010 年 6 月。这是一个优秀的 F#指南,由语言的创造者编写,毫不羞耻地针对专业程序员。
\ No newline at end of file
*唐·赛姆等人的专家 F#* ,Apress,2010 年 6 月。这是一个优秀的 F# 指南,由语言的创造者编写,毫不羞耻地针对专业程序员。
\ No newline at end of file
......@@ -2,4 +2,4 @@
在这个时代,地理信息系统无处不在,但大多数人,甚至开发人员,都不知道它的内部。我们中的许多人通过基于网络的系统使用地理信息系统,如谷歌地图或必应地图;作为推动地图和地址搜索的 GPS 数据;甚至在追踪你最新的亚马逊包裹时。
地理信息系统世界使用制图、统计分析和数据库技术的复杂组合来驱动驱动我们都使用和喜欢的所有流行外部应用程序的内部。在本指南中,我将向您展示这个世界的内部,以及它是如何应用的。NET 开发人员,他们可能对在最新的应用程序中使用一些地理信息系统功能感兴趣。
\ No newline at end of file
地理信息系统世界使用制图、统计分析和数据库技术的复杂组合来驱动驱动我们都使用和喜欢的所有流行外部应用的内部。在本指南中,我将向您展示这个世界的内部,以及它是如何应用的。NET 开发人员,他们可能对在最新的应用中使用一些地理信息系统功能感兴趣。
\ No newline at end of file
......@@ -2,9 +2,9 @@
对大多数人来说,他们所看到的地理信息系统实际上只是前端输出层,比如谷歌地图中生成的地图,或者汤姆汤姆导航设备上的屏幕。这一切的现实远不止于此;输出层通常是许多互连程序和大量数据的最终结果。
典型的地理信息系统将包括用于可视化、编辑和管理数据的桌面应用程序、存储数据的几种不同类型的后端数据库,以及在许多情况下大量定制的软件工具。事实上,地理信息系统是顶尖的行业之一,程序员可以编写大量其他公司没有的定制工具。
典型的地理信息系统将包括用于可视化、编辑和管理数据的桌面应用、存储数据的几种不同类型的后端数据库,以及在许多情况下大量定制的软件工具。事实上,地理信息系统是顶尖的行业之一,程序员可以编写大量其他公司没有的定制工具。
我们将很快详细探讨一些应用程序,但现在我们将继续 100 英尺的视图。典型的地理信息系统处理设置如下所示:
我们将很快详细探讨一些应用,但现在我们将继续 100 英尺的视图。典型的地理信息系统处理设置如下所示:
![](img/image001.png)
......@@ -48,7 +48,7 @@
由于大多数地理信息系统的中心都有一个大型数据库,所以 SQL 仍然扮演着重要的角色,而且可能永远都是。然而,从地理信息系统的角度来看,这些查询不仅涉及到您在数据库管理系统中习惯看到的普通 SQL,还涉及到地理空间 SQL。稍后我们将讨论特定于地理信息系统的 SQL 目前,这里的输入通常是从类似搜索查询的东西中生成的。
例如,当您在谷歌、必应或雅虎地图中键入地名或邮政编码时,您正在查看的网络应用程序很可能会将您的搜索转化为使用地理空间 SQL 来检查核心数据库中数据的查询。反过来,这将与其他过程结合产生一个输出,在这种情况下,它通常是一个显示您搜索的位置的地图。另一个例子可能是紧急服务控制室中的操作员进入事故地点,并将其与附近紧急车辆的已知地点相结合,以帮助决定将哪辆车辆送往事故地点。
例如,当您在谷歌、必应或雅虎地图中键入地名或邮政编码时,您正在查看的网络应用很可能会将您的搜索转化为使用地理空间 SQL 来检查核心数据库中数据的查询。反过来,这将与其他过程结合产生一个输出,在这种情况下,它通常是一个显示您搜索的位置的地图。另一个例子可能是紧急服务控制室中的操作员进入事故地点,并将其与附近紧急车辆的已知地点相结合,以帮助决定将哪辆车辆送往事故地点。
### 位置感知输入
......@@ -126,7 +126,7 @@ MySQL 也有类似的限制,但对许多核心数据类型的处理方式也
所有符合 OGC 标准的地理信息系统数据库必须支持两个核心元数据表,称为**几何 _ 列****空间 _ 参考 _ 系统**。大多数支持地理信息系统的软件将使用这些表的存在来确定它是否正在与真正的地理信息系统数据库系统对话。如果这些表不存在,软件通常会退出。
这方面的一个很好的例子是早期版本的 MySQL,其中表名由数据库引擎保留,但实际上并不作为表存在。这将导致 MapInfo 应用程序尝试创建缺失的表,但在尝试这样做时会收到一个错误,从而阻止软件正确使用数据库。
这方面的一个很好的例子是早期版本的 MySQL,其中表名由数据库引擎保留,但实际上并不作为表存在。这将导致 MapInfo 应用尝试创建缺失的表,但在尝试这样做时会收到一个错误,从而阻止软件正确使用数据库。
**geometry_columns** 表用于记录数据库中哪些表列包含地理空间数据以及它们的数据类型、坐标系、尺寸和一些其他相关信息。
......@@ -221,7 +221,7 @@ MySQL 也有类似的限制,但对许多核心数据类型的处理方式也
54.852726, -1.832299
如果你的手机内置了全球定位系统,启动它并观看显示屏。你会看到类似于这个坐标对的东西。请注意,在某些设备和应用程序上,坐标可能会互换。
如果你的手机内置了全球定位系统,启动它并观看显示屏。你会看到类似于这个坐标对的东西。请注意,在某些设备和应用上,坐标可能会互换。
这个坐标对被称为纬度和经度。第一个数字,纬度,是从赤道向北或向南的度数,北为正,南为负。第二个数字,经度,是本初子午线以东或以西的度数,西为负,东为正。该坐标系的正确地理空间名称为 **WGS84** 。在**空间参考系统**表中,其 SRID 号为 **4326**
......@@ -237,9 +237,9 @@ MySQL 也有类似的限制,但对许多核心数据类型的处理方式也
**srtext** 字段保存投影、椭球体、椭球体和其他基本信息,允许任何软件从一个坐标集转换到另一个坐标集。稍后我们将对此进行更多的介绍,但是对这一领域的所有内容的完整描述远远超出了这本小书的范围。事实上,我看过的描述基础知识的最小的一本书超过了 500 页!
**proj4text** 字段有类似的用途,但被使用开源 Proj.4 库的应用程序使用。
**proj4text** 字段有类似的用途,但被使用开源 Proj.4 库的应用使用。
Proj.4 和 Geos 是第一批由许多不同的空间数据库和地理信息系统应用程序使用的两个开源库。这两个库现在几乎 100%用于所有商业和开源软件,用于任何类型的空间或地理信息系统工作。这两个库仍在积极维护中,可用于您期望使用的每个平台。稍后,当我们简单了解一下可用于的一些地理信息系统软件时,我们将再次与他们见面。NET 开发人员。
Proj.4 和 Geos 是第一批由许多不同的空间数据库和地理信息系统应用使用的两个开源库。这两个库现在几乎 100%用于所有商业和开源软件,用于任何类型的空间或地理信息系统工作。这两个库仍在积极维护中,可用于您期望使用的每个平台。稍后,当我们简单了解一下可用于的一些地理信息系统软件时,我们将再次与他们见面。NET 开发人员。
目前,您需要注意的是,为了支持不同的空间坐标系,您必须在 **spatial_ref_sys** 表中有条目。
......@@ -261,7 +261,7 @@ SRID WGS84:4326-全球纬度/经度,带分钟/小时/秒偏移的度数,原
我想愉快地送你进入你的第一次地理信息系统冒险,并说这东西真的不重要;然而,事实是我不能,这很重要。事实上,这很重要。
如果你不能正确理解这个坐标,就有可能把一辆汽车的轨迹绘制成位于大西洋中部。虽然这对于您正在处理的应用程序可能无关紧要,例如,您可能正在查看客户分散的总体概述,但您仍然应该尽量确保您的应用程序尽可能准确。
如果你不能正确理解这个坐标,就有可能把一辆汽车的轨迹绘制成位于大西洋中部。虽然这对于您正在处理的应用可能无关紧要,例如,您可能正在查看客户分散的总体概述,但您仍然应该尽量确保您的应用尽可能准确。
所以这个价值百万的问题的答案是,“为什么我们要处理所有这些坐标的东西?”归结为一件事,也只有一件事:
......
......@@ -2,9 +2,9 @@
拥有一个设计良好的地理信息系统数据库很好,但是除此之外,你还需要什么软件呢?
除非您从头开始做所有事情,否则您将需要某种编辑应用程序、某种加载数据的方式,并且很可能还需要某种实时数据。
除非您从头开始做所有事情,否则您将需要某种编辑应用、某种加载数据的方式,并且很可能还需要某种实时数据。
主要问题是费用。你会很快发现,地理信息系统软件可能是地球上最昂贵的软件市场之一。物超所值,大多数这些应用程序的总体成本远远超过小型办公室典型的年度操作系统站点许可成本—通常一个应用程序中只有一个用户需要使用 6 个月。
主要问题是费用。你会很快发现,地理信息系统软件可能是地球上最昂贵的软件市场之一。物超所值,大多数这些应用的总体成本远远超过小型办公室典型的年度操作系统站点许可成本—通常一个应用中只有一个用户需要使用 6 个月。
对我们来说幸运的是,围绕地理信息系统也有一场巨大的开源和自由软件运动,大部分由开源地理空间基金会(OSGeo)运营和管理。
......@@ -66,7 +66,7 @@ SQL Server 官方网站是[www.microsoft.com/sqlserver/en/us/default.aspx](http:
### SQLite 和 SpatiaLite
SQLite 严格来说不是数据库服务器,而是新一代单文件数据库引擎之一,旨在直接嵌入到您的应用程序中。SQLite 在大量平台上都有惊人的支持,可能是我有幸使用过的最跨平台的工具包之一。
SQLite 严格来说不是数据库服务器,而是新一代单文件数据库引擎之一,旨在直接嵌入到您的应用中。SQLite 在大量平台上都有惊人的支持,可能是我有幸使用过的最跨平台的工具包之一。
与已经提到的三大巨头相比,SQLite 相对来说是一个新手,但它运行得非常好,效率非常高,尤其是在移动平台上。事实上,它在移动平台上表现如此出色,以至于被选为安卓设备和苹果 iOS 上的首选数据库引擎,并通过使用完全托管的。NET 接口。
......@@ -105,9 +105,9 @@ SQLite 网站是[sqlite.org](http://sqlite.org/)。它的。NET 接口可在[htt
## 地理信息系统桌面软件
为了操作您的地理信息系统资产,您需要一个好的桌面应用程序——最好是一个不仅允许您查看和操作数据,而且允许您相对容易地导入和导出数据的应用程序
为了操作您的地理信息系统资产,您需要一个好的桌面应用——最好是一个不仅允许您查看和操作数据,而且允许您相对容易地导入和导出数据的应用
后一点也很重要,因为有些应用程序的唯一目的是将数据移入和移出系统,而其他应用程序仅用于查看数据。移动数据的应用程序通常被称为 ETL(提取、转换和加载)包。ETL 包通常可用于许多数据库引擎,而不仅仅是那些用于操作地理空间数据的引擎。
后一点也很重要,因为有些应用的唯一目的是将数据移入和移出系统,而其他应用仅用于查看数据。移动数据的应用通常被称为 ETL(提取、转换和加载)包。ETL 包通常可用于许多数据库引擎,而不仅仅是那些用于操作地理空间数据的引擎。
幸运的是,大多数软件都允许你两者兼得。从这些包开始,下面是一些比较著名的包:
......@@ -147,9 +147,9 @@ SQLite 网站是[sqlite.org](http://sqlite.org/)。它的。NET 接口可在[htt
### 量子地理信息系统
我不能说量子地理信息系统(QGIS)公正。这个包可以做任何事情。它与 ESRI 和 MapInfo 等应用程序不相上下,完全开源,并得到了 OSGeo 基金会的官方支持。
我不能说量子地理信息系统(QGIS)公正。这个包可以做任何事情。它与 ESRI 和 MapInfo 等应用不相上下,完全开源,并得到了 OSGeo 基金会的官方支持。
主应用程序是使用 Python 编写的,因此将在 Linux、Mac OS、Windows 以及桌面环境中支持 Python 的任何其他设备上运行。
主应用是使用 Python 编写的,因此将在 Linux、Mac OS、Windows 以及桌面环境中支持 Python 的任何其他设备上运行。
现在在 1.8.0 版本上,量子地理信息系统的发展在它可用的相对短的时间内建立了强大的基础。系统公开的扩展 API 简直令人惊叹,可以在每个级别进行定制——从重新设计主 UI,到公开实时 GPS 跟踪等内容的插件,再到通过将算法应用到包中的不同层来创建全新的矢量层。
......@@ -171,13 +171,13 @@ SQLite 网站是[sqlite.org](http://sqlite.org/)。它的。NET 接口可在[htt
### MapWindow
MapWindow 的设计与 QGIS 非常相似。它的主要目的是做桌面地理信息系统应用程序可以做的一切,具有各种功能。
MapWindow 的设计与 QGIS 非常相似。它的主要目的是做桌面地理信息系统应用可以做的一切,具有各种功能。
这也是唯一一个专门为视窗平台编写的,旨在通过其丰富的开发人员应用编程接口和工具集涵盖视窗开发人员社区。
MapWindow 有两个版本:MapWindow 4 和 MapWindow 6。MapWindow 4 是最初的第一代 C++版本,MapWindow 6 是最新的、最先进的重写,完全使用 C#和标准编写。NET 运行时。
根据应用程序的 Codeplex 页面,目前这两个版本是同步更新和发布的。这是因为地图窗口 6 还没有达到与地图窗口 4 相同的功能级别。正如您在下面的截图中看到的,它与 QGIS 非常相似:
根据应用的 Codeplex 页面,目前这两个版本是同步更新和发布的。这是因为地图窗口 6 还没有达到与地图窗口 4 相同的功能级别。正如您在下面的截图中看到的,它与 QGIS 非常相似:
![](img/image008.jpg)
......@@ -187,11 +187,11 @@ MapWindow 有两个版本:MapWindow 4 和 MapWindow 6。MapWindow 4 是最初的
### 地理水壶
另一个值得一提的应用是 GeoKettle。虽然这不是一个桌面地理信息系统应用程序,但它同样重要。
另一个值得一提的应用是 GeoKettle。虽然这不是一个桌面地理信息系统应用,但它同样重要。
GeoKettle 是一个 ETL 工具。它的主要目的是在多种格式和多种类型的数据库之间转换和加载数据。源自一个名为 Pentaho 数据套件的包,GeoKettle 得到了增强,以支持行业标准的 shapefiles、KML 文件以及前面提到的所有数据库的空间特征。
一个简单易用的开源应用程序可以做得多好的鲜明例子是,我看到许多人用基于 GeoKettle 的解决方案取代了高价应用程序,如安全 FME。
一个简单易用的开源应用可以做得多好的鲜明例子是,我看到许多人用基于 GeoKettle 的解决方案取代了高价应用,如安全 FME。
用 Java 编写,它有一个非常容易扩展的插件架构,使得它很容易处理未来的文件格式和数据库。它还可以处理数据库和文件中的正常数据,而不仅仅是地理空间数据。如果您曾经使用过微软商业智能开发工作室,那么使用 GeoKettle 就像在家里一样,您会在下面的截图中注意到这一点:
......@@ -207,15 +207,15 @@ GeoKettle 是一个 ETL 工具。它的主要目的是在多种格式和多种
我个人最喜欢的两个是 QGIS 和 GeoKettle,但是我鼓励你尽你所能去尝试。这些年,我用了很多桌面 GIS 包;有些学习曲线非常陡峭,有些你不到五分钟就能学会。和任何事情一样,你应该选择一个工具,尽可能以最好和最简单的方式完成你需要完成的工作。
请注意,我列出的应用程序主要在英国和欧洲使用。应用程序的受欢迎程度在世界各地各不相同。例如,我认为 IDRISI 是加拿大流行的软件包。本节列出的应用程序是我在日常地理信息系统工作中使用的应用程序
请注意,我列出的应用主要在英国和欧洲使用。应用的受欢迎程度在世界各地各不相同。例如,我认为 IDRISI 是加拿大流行的软件包。本节列出的应用是我在日常地理信息系统工作中使用的应用
正如我已经提到的,除了你选择的软件,你还有很多其他的事情要处理。
## 开发套件
因为这本书是针对。NET 开发人员,这是唯一的权利,我们包括一个部分的套件可用的地理空间数据在您自己的。NET 应用程序
因为这本书是针对。NET 开发人员,这是唯一的权利,我们包括一个部分的套件可用的地理空间数据在您自己的。NET 应用。
稍后我们将介绍一些实际的例子,但现在我将列出我使用过或看到过的工具包。然而,请注意,这不是一个详尽的清单。我描述的工具包都是为在下使用而设计的。NET 在 Windows 平台上实现。正如我所提到的,像 QGIS 这样的应用程序可以被极大地扩展,在 Linux 和 Mac 系统下有许多工具包是我还没有也可能不会涉及的。如果你正在开始一个项目,你知道你将要写定制的用户界面,提前做你的研究。您可以修改现有的应用程序来满足您的需求,而不是从头开始编写它们。
稍后我们将介绍一些实际的例子,但现在我将列出我使用过或看到过的工具包。然而,请注意,这不是一个详尽的清单。我描述的工具包都是为在下使用而设计的。NET 在 Windows 平台上实现。正如我所提到的,像 QGIS 这样的应用可以被极大地扩展,在 Linux 和 Mac 系统下有许多工具包是我还没有也可能不会涉及的。如果你正在开始一个项目,你知道你将要写定制的用户界面,提前做你的研究。您可以修改现有的应用来满足您的需求,而不是从头开始编写它们。
### 地图赢取
......@@ -223,7 +223,7 @@ MapWinGis 是 MapWindow 4 和 MapWindow 6 背后的中央 GUI 组件。这是一
过去,我使用这个组件的原始版本。我已经有一段时间没有使用它进行任何开发了。与许多这些组件一样,它在 mapwingis.codeplex.com 的 Codeplex 上有一个永久的家。
它旨在为您完成大部分繁重的工作,让您可以自由地专注于应用程序的图形用户界面方面。请注意,它是为桌面应用程序设计的,而不是基于网络的应用程序,据我所知,不能在 WPF 或 Silverlight 中使用。
它旨在为您完成大部分繁重的工作,让您可以自由地专注于应用的图形用户界面方面。请注意,它是为桌面应用设计的,而不是基于网络的应用,据我所知,不能在 WPF 或 Silverlight 中使用。
### 点空间
......@@ -267,7 +267,7 @@ BruTile 可以用于任何类型的项目,从 web 和 Silverlight,到高端
对于编程示例,我将在 Visual Studio 2010 Professional 中使用 C#语言的夏普映射。样品可从[bitbucket.org/syncfusion/gis-succinctly](https://bitbucket.org/syncfusion/gis-succinctly)下载。
请注意,我不会介绍如何安装和初始设置这些应用程序。在大多数情况下,这是一个相当简单的操作,我假设这本书的读者都很熟悉。如果您对安装软件有任何问题,本书中提到的所有应用程序都有非常活跃的社区和帮助论坛。
请注意,我不会介绍如何安装和初始设置这些应用。在大多数情况下,这是一个相当简单的操作,我假设这本书的读者都很熟悉。如果您对安装软件有任何问题,本书中提到的所有应用都有非常活跃的社区和帮助论坛。
对于熟悉堆栈溢出的人来说,可以在[gis.stackexchange.com](http://gis.stackexchange.com/)找到它的地理信息系统专用站点。
......
......@@ -10,11 +10,11 @@
在我们开始向系统中添加任何数据之前,我们首先需要创建一个数据库来存储数据。对于本书中的示例,我将创建一个简单的三表数据库,而不是前面描述的整个地理信息系统模型。
如果您正在处理一个大型企业应用程序,我再怎么强调规划和设计在地理信息系统数据库解决方案中的重要性也不为过。在许多方面,这方面的计划比普通数据库中的相同步骤要重要得多。与一般的企业数据解决方案相比,地理信息系统解决方案的故障和变更往往更昂贵、更复杂。
如果您正在处理一个大型企业应用,我再怎么强调规划和设计在地理信息系统数据库解决方案中的重要性也不为过。在许多方面,这方面的计划比普通数据库中的相同步骤要重要得多。与一般的企业数据解决方案相比,地理信息系统解决方案的故障和变更往往更昂贵、更复杂。
为了创建数据库,我们将使用 Postgres 提供的数据库管理工具 pgAdmin。要启动 pgAdmin,点击桌面上的 **pgAdmin III** 图标。如果看不到图标,请确保在安装服务器时安装了管理工具。
安装应用程序并创建与数据库服务器的初始连接后,您可以开始在该服务器连接中创建数据库,如下图所示。
安装应用并创建与数据库服务器的初始连接后,您可以开始在该服务器连接中创建数据库,如下图所示。
请注意,出于安全原因,我已经从本书中显示 pgAdmin 的许多图中删除了服务器和表名,只留下了那些您理解所必需的名称。在您使用 pgAdmin 的过程中,当您完成我在这里介绍的步骤时,您会看到更多的信息。
......@@ -64,7 +64,7 @@ Postgres **Contrib** 目录(位于您选择安装 Postgres 的位置)中有脚
如果您使用 Postgres 帐户创建所有表,您不会有问题,但是如果您创建数据库,然后将这些数据库的所有权分配给您在服务器中创建的其他用户名,您可能会发现 **Postgres** 用户帐户无法使用它们。
在 QGIS 中打开数据库图层时,最有可能出现这个问题。如果使用给定的一组凭据创建数据库连接,并使用 **Postgres** 用户帐户在 pgAdmin 中创建数据库,您会发现空间元数据表将有 **Postgres** 作为其所有者。发生这种情况时,QGIS 将无法打开元数据表,并且不会显示可供您在应用程序中使用的图层。
在 QGIS 中打开数据库图层时,最有可能出现这个问题。如果使用给定的一组凭据创建数据库连接,并使用 **Postgres** 用户帐户在 pgAdmin 中创建数据库,您会发现空间元数据表将有 **Postgres** 作为其所有者。发生这种情况时,QGIS 将无法打开元数据表,并且不会显示可供您在应用中使用的图层。
这个问题的解决方法很简单。使用 pgAdmin,右键单击其中一个元数据表,选择**属性**选项,如下图所示:
......@@ -78,7 +78,7 @@ Postgres **Contrib** 目录(位于您选择安装 Postgres 的位置)中有脚
图 17:编辑几何列表属性
**所有者**字段提供了服务器中定义的用户的下拉列表。选择您在应用程序连接中使用的所有者。
**所有者**字段提供了服务器中定义的用户的下拉列表。选择您在应用连接中使用的所有者。
### 重访元数据表
......@@ -116,7 +116,7 @@ Postgres **Contrib** 目录(位于您选择安装 Postgres 的位置)中有脚
这个接口是相当不言自明的。您可以在 **PostgreSQL** **连接**区域找到与您列出的 SQL 数据库的任何连接。**导入选项**用于指定数据的 SRID 等内容,以及数据导入的其他选项。
您的 PostgreSQL 连接列表在此处和主应用程序之间共享。如果您已经在 QGIS 中创建了一个连接,您只需从下拉列表中选择它并单击**连接**,就可以在这里重用它。
您的 PostgreSQL 连接列表在此处和主应用之间共享。如果您已经在 QGIS 中创建了一个连接,您只需从下拉列表中选择它并单击**连接**,就可以在这里重用它。
然而,在本练习中,我们将创建一个新的连接来保存我们的数据。首先,点击**邮箱连接**下的**新建**。将出现**创建新的邮政地理信息系统连接**对话框。
......
......@@ -8,13 +8,13 @@
在下一节中,我不打算涵盖所有可能的排列和函数调用。根据最新统计,OGC 规范中有 300 多个独立函数,涵盖了从空间距离关系到复杂几何结构的构建,再到为预定义矢量路径裁剪栅格的几乎所有可能情况。
相反,使用我们放在数据库中的数据,我将指导您完成一些简单但常见的操作——任何编写支持地理信息系统的应用程序的人都可能使用的类型。
相反,使用我们放在数据库中的数据,我将指导您完成一些简单但常见的操作——任何编写支持地理信息系统的应用的人都可能使用的类型。
在讨论这些操作之前,让我们先快速了解一下第一章中描述的输入和输出阶段。
## 创建和检索几何图形
即使我们已经将一些数据导入到我们的数据库中,任何编写地理信息系统应用程序的人也需要能够在数据库中创建几何图形,尤其是如果应用程序允许编辑的话。
即使我们已经将一些数据导入到我们的数据库中,任何编写地理信息系统应用的人也需要能够在数据库中创建几何图形,尤其是如果应用允许编辑的话。
大多数几何图形创建是以三种格式之一执行的:
......@@ -152,7 +152,7 @@ Postgres 最新版本中提供的其他几何功能包括:
这将返回一个可直接插入输出或 SVG 文件的 SVG 标记。
如果您正在创建用于谷歌地球或任何其他支持锁眼标记语言的应用程序的输出,请使用以下功能:
如果您正在创建用于谷歌地球或任何其他支持锁眼标记语言的应用的输出,请使用以下功能:
```sql
SELECT ST_AsKML(geometry)
......@@ -182,7 +182,7 @@ Postgres 最新版本中提供的其他几何功能包括:
```
这将输出 NMEA 秒、分和度格式的坐标,但这种格式的使用被认为是非标准的,可能会使您的应用程序很难在数据库平台之间移植。
这将输出 NMEA 秒、分和度格式的坐标,但这种格式的使用被认为是非标准的,可能会使您的应用很难在数据库平台之间移植。
让我们使用添加到数据库中的城市点数据来快速查看其中的一两个函数。为此,我们将使用 pgAdmin 中的 SQL 编辑器。要打开 SQL 编辑器,请选择您希望使用的数据库,在我们的例子中是**注册表**如果您一直在关注的话,请选择它,然后单击工具栏上的 SQL 放大镜图标。
......
# 第 5 章在中创建地理信息系统应用程序。网
# 第 5 章在中创建地理信息系统应用。网
所以最后我们来到了你们大多数人一直在等待的部分:在. NET 中创建一个支持 GIS 的小型桌面应用程序
所以最后我们来到了你们大多数人一直在等待的部分:在. NET 中创建一个支持 GIS 的小型桌面应用
在这一章中,我使用 Visual Studio 2010 Ultimate 进行任何截图,我的代码是用 C#编写的。
......@@ -26,11 +26,11 @@
## 创建我们自己的夏普地图解决方案
运行 Visual Studio 的一个实例或重用您仍然打开的实例,并创建一个新的 Windows 窗体应用程序
运行 Visual Studio 的一个实例或重用您仍然打开的实例,并创建一个新的 Windows 窗体应用。
![](img/image069.jpg)
图 69:启动新的 Windows 窗体应用程序
图 69:启动新的 Windows 窗体应用
确保你正在使用。NET 4。SharpMap 的当前版本针对的是。NET 4 及以上版本。
......@@ -39,7 +39,7 @@
| ![](img/image070.png) | | ![](img/image071.png) |
| 图 70:改变目标框架 |
现在我们需要将夏普地图用户界面组件添加到我们的工具箱中。双击**解决方案资源管理器**中的**表单 1.cs** 加载工具栏选项板。在**通用**下方的空白区域点击右键,选择**添加标签**。给标签起一个名字。在我的应用程序中,我调用了选项卡夏普地图。
现在我们需要将夏普地图用户界面组件添加到我们的工具箱中。双击**解决方案资源管理器**中的**表单 1.cs** 加载工具栏选项板。在**通用**下方的空白区域点击右键,选择**添加标签**。给标签起一个名字。在我的应用中,我调用了选项卡夏普地图。
创建调色板后,将其展开并右键单击**调色板**区域。从出现的菜单中选择**选择项目**。将出现**选择工具箱项目**对话框。
......@@ -116,7 +116,7 @@
图 76:pgAdmin 中的对象树
如图所示,**表格**节点下是一个名为**视图**的绿色小图标。您可能以前在其他数据库中使用过这些。总的想法是,它们将其他表中的数据投影到不同的模式中,但在客户端应用程序看来,它们就像一个实际的表。
如图所示,**表格**节点下是一个名为**视图**的绿色小图标。您可能以前在其他数据库中使用过这些。总的想法是,它们将其他表中的数据投影到不同的模式中,但在客户端应用看来,它们就像一个实际的表。
一个典型的用法是从通过外键链接的不同表中获取行,并呈现组合数据的简单平面视图,其中所有呈现的项形成一行而不是层次结构。
......@@ -184,7 +184,7 @@
图 80:带有新视图的表格
像任何符合 OGC 标准的地理信息系统数据库客户端的像样的实现一样,夏普地图将检查**几何 _ 列**表,找出我们添加的任何图层的细节。如果我们添加一个在 geometry_columns 表中找不到的视图,它将取消并产生一个异常。一旦我们手动添加了数据,我们就可以将这些视图加载到我们的应用程序中。
像任何符合 OGC 标准的地理信息系统数据库客户端的像样的实现一样,夏普地图将检查**几何 _ 列**表,找出我们添加的任何图层的细节。如果我们添加一个在 geometry_columns 表中找不到的视图,它将取消并产生一个异常。一旦我们手动添加了数据,我们就可以将这些视图加载到我们的应用中。
### 回到代码...
......@@ -320,7 +320,7 @@
最后,在对`InitializeComponent()`的调用下面添加对表单构造函数`InitializeMap()`的调用,这就是初始化函数所需的全部内容。
如果此时运行您的应用程序,并且所有设置都正确,您应该会看到您的地图图层出现在屏幕上。您应该能够通过在地图表面拖动指针来平移它们,并使用鼠标滚轮进行缩放。
如果此时运行您的应用,并且所有设置都正确,您应该会看到您的地图图层出现在屏幕上。您应该能够通过在地图表面拖动指针来平移它们,并使用鼠标滚轮进行缩放。
应该是这样的:
......@@ -360,7 +360,7 @@
SharpMap 有许多不同的工具模式,从缩放和平移到在加载的地图顶部绘制线条和多边形。
我们将在这个应用程序中使用的两个工具是默认的缩放和平移工具,以及查询工具。
我们将在这个应用中使用的两个工具是默认的缩放和平移工具,以及查询工具。
更改工具就像给表单上的`MapBox`控件的`ActiveTool`属性赋予一个新值一样简单。要分配的值是来自`MapBox.Tools`类型枚举的任何值。
......@@ -379,7 +379,7 @@ SharpMap 有许多不同的工具模式,从缩放和平移到在加载的地
```
如果您现在运行该应用程序,您应该能够使用表单左上角的两个按钮来更改模式。缩放和平移是自动处理的。对于查询,我们必须响应地图框上的点击事件,并添加一些代码来获得我们需要的结果。
如果您现在运行该应用,您应该能够使用表单左上角的两个按钮来更改模式。缩放和平移是自动处理的。对于查询,我们必须响应地图框上的点击事件,并添加一些代码来获得我们需要的结果。
### 添加我县信息查询码
......@@ -523,7 +523,7 @@ SharpMap 有许多不同的工具模式,从缩放和平移到在加载的地
```
在我们继续之前,你们中的一些人会说,“哇,多长的方法啊”,并想知道为什么我要使用`Application.DoEvents`来确保标签在事件处理程序中得到更新。请记住,这只是示例代码;它不应该是一个完美的例子,或者简单地复制并逐字粘贴来制作生产质量的应用程序。它的目的纯粹是向您展示如何使用夏普地图创建一个简单的地理信息系统应用程序
在我们继续之前,你们中的一些人会说,“哇,多长的方法啊”,并想知道为什么我要使用`Application.DoEvents`来确保标签在事件处理程序中得到更新。请记住,这只是示例代码;它不应该是一个完美的例子,或者简单地复制并逐字粘贴来制作生产质量的应用。它的目的纯粹是向您展示如何使用夏普地图创建一个简单的地理信息系统应用
### 结论
......@@ -608,7 +608,7 @@ SharpMap 完全能够获取我们之前找到的多边形轮廓,并查询地
我们在嵌套的`using`语句中完成了所有这些,这意味着一切都是`IDisposable`,一旦完成,就应该被正确释放,只需将结果的字符串列表返回给调用方法。
如果一切按计划进行,在点击**运行**时,您应该会看到一个应用程序,该应用程序可以缩放和平移数据库中定义的地图,还可以切换到`County Query Mode`并检索选定县的城镇列表。您完成的应用程序应该如下所示:
如果一切按计划进行,在点击**运行**时,您应该会看到一个应用,该应用可以缩放和平移数据库中定义的地图,还可以切换到`County Query Mode`并检索选定县的城镇列表。您完成的应用应该如下所示:
![](img/image085.jpg)
......@@ -616,4 +616,4 @@ SharpMap 完全能够获取我们之前找到的多边形轮廓,并查询地
我们找到了。希望我已经激起了你的食欲,并向你展示了地理信息系统在日常生活中可以帮助你的许多事情中的一些。
请记住,有可能买到足够大功率的手机来小规模地做这种事情,并且有数据库系统来支持它们,比如 SQL Compact。夏普地图允许你做任何事情,从矢量到光栅地图和更远。一旦你开始进一步观察,你会发现一些系统具有读取全球定位系统设备的内置能力,使你能够将实时位置信息拉入你的应用程序。走出去,探索现实世界和数字世界。这是一次刚刚开始的冒险。
\ No newline at end of file
请记住,有可能买到足够大功率的手机来小规模地做这种事情,并且有数据库系统来支持它们,比如 SQL Compact。夏普地图允许你做任何事情,从矢量到光栅地图和更远。一旦你开始进一步观察,你会发现一些系统具有读取全球定位系统设备的内置能力,使你能够将实时位置信息拉入你的应用。走出去,探索现实世界和数字世界。这是一次刚刚开始的冒险。
\ No newline at end of file
......@@ -2,7 +2,7 @@
| ![](img/00007.jpeg) | 提示:如果你有兴趣了解更多关于 Git 的知识,一定要下载 Ryan Hodson 的电子书《Git 简洁》。 |
在本地计算机上安装 Git 的第一步是访问 [GitHub Desktop](https://desktop.github.com/ "蘞뉘뿰ṝĽ纀篓") 网站并下载桌面应用程序
在本地计算机上安装 Git 的第一步是访问 [GitHub Desktop](https://desktop.github.com/ "蘞뉘뿰ṝĽ纀篓") 网站并下载桌面应用。
![](img/00010.jpeg)
......
......@@ -32,7 +32,7 @@
您可以看到存储库名称旁边的星星数量,如图 20 所示。如您所见,blueimp 的文件上传 jQuery 模块有超过 22,000 颗星。这可能是一个安全的赌注,它已经被大量开发人员测试和使用。
你也可以按照最少的星星来排序,但是你不应该认为星星少的项目是一个必要的坏项目。事实上,它拥有较少的恒星也可能证明这是一个未知的项目或新的贡献。这可能是一个伟大的项目,但很少有人需要它或已经发现它。在这些类型的项目中可能有一些隐藏的宝石,特别是如果你正在处理一个不常见的应用程序
你也可以按照最少的星星来排序,但是你不应该认为星星少的项目是一个必要的坏项目。事实上,它拥有较少的恒星也可能证明这是一个未知的项目或新的贡献。这可能是一个伟大的项目,但很少有人需要它或已经发现它。在这些类型的项目中可能有一些隐藏的宝石,特别是如果你正在处理一个不常见的应用。
虽然我们将在后面的章节中讨论 GitHub 分叉,但这里的简单描述应该会有所帮助。fork 是某人下载的存储库的副本,很可能是为了建议变更或者作为他们自己项目的起点。
......@@ -48,11 +48,11 @@
最近更新最少的首先显示最早的(就活动而言)。同样,项目更新日期仅提供项目活跃程度的概念。Scriptaculous(一个 JavaScript 框架)已经六个月没有更新了,但是已经有将近 2400 颗星了。这表明一个框架仍在使用,并且相当成熟。它实际上是在 2005 年写的(在互联网时代是古老的),在 2010 年被功能冻结,然而仍然在使用,并且仍然有贡献者被列在项目上,即使它被冻结了。
随着新浏览器和新技术的频繁出现,旧的网络项目可能无法与新技术兼容。请记住,E. 9 直到 2011 年才发布,所以这个特殊的框架(Scriptaculous)在 E. 8 版本中运行良好。这可能是一个很好的网络应用程序框架,其中的计算机较旧,并且没有运行最新的浏览器。
随着新浏览器和新技术的频繁出现,旧的网络项目可能无法与新技术兼容。请记住,E. 9 直到 2011 年才发布,所以这个特殊的框架(Scriptaculous)在 E. 8 版本中运行良好。这可能是一个很好的网络应用框架,其中的计算机较旧,并且没有运行最新的浏览器。
在排序选项、星星(使用项目后有多少开发人员喜欢它)、分叉(有多少开发人员对增强项目足够感兴趣)和活动(项目最近是如何工作的)之间,您应该能够为您的应用程序选择一个好的项目。
在排序选项、星星(使用项目后有多少开发人员喜欢它)、分叉(有多少开发人员对增强项目足够感兴趣)和活动(项目最近是如何工作的)之间,您应该能够为您的应用选择一个好的项目。
除了对结果进行排序,GitHub 搜索结果还提供了一些基本的过滤选项,主要是让您专注于特定的编程语言。例如,假设我们正在进行一个项目,将 QuickBooks 与您正在开发的某个应用程序集成在一起。在 GitHub 中搜索“QuickBooks”会得到 300 多个结果。如果我按照大多数星星对它们进行排序,我发现 PHP 和 Ruby 实现占据了前 10 名的结果。
除了对结果进行排序,GitHub 搜索结果还提供了一些基本的过滤选项,主要是让您专注于特定的编程语言。例如,假设我们正在进行一个项目,将 QuickBooks 与您正在开发的某个应用集成在一起。在 GitHub 中搜索“QuickBooks”会得到 300 多个结果。如果我按照大多数星星对它们进行排序,我发现 PHP 和 Ruby 实现占据了前 10 名的结果。
然而,在搜索结果的左侧,GitHub 显示了所有找到的存储库的语言组的分类。
......@@ -64,4 +64,4 @@
| ![](img/00003.gif) | 注意:请记住,用 C#编写的库可以从任何。NET 语言,如 Visual Basic。 |
GitHub 中的基本搜索以及排序和过滤选项应该允许您在存储库中移动并找到库、应用程序等。你需要开始你的发展努力。
\ No newline at end of file
GitHub 中的基本搜索以及排序和过滤选项应该允许您在存储库中移动并找到库、应用等。你需要开始你的发展努力。
\ No newline at end of file
......@@ -24,7 +24,7 @@ GitHub 的搜索引擎隐藏了搜索中的高级选项,但在搜索字符串
时区语言:javascript 星号:> 500
通过组合选项,您可以将返回的结果缩小到仅满足应用程序需求的解决方案。
通过组合选项,您可以将返回的结果缩小到仅满足应用需求的解决方案。
| ![](img/00007.jpeg) | 提示:虽然您可以创建一些相当大的搜索请求(例如所有超过 500 颗星的项目),但是除了高级搜索选项之外,您通常应该至少包含一个搜索词。 |
......
......@@ -232,6 +232,6 @@ Pulse 选项卡让您了解存储库的活跃程度;这个例子代表了 Boot
至少,我会提供一个简短(一段)的代码摘要,以及如何安装它(最低要求)。这至少可以刺激用户的食欲。然后,您可以提供更多的详细信息(在自述文件的剩余部分),或者在您的存储库中作为一个单独的文档文件。
本章详细介绍了 GitHub 提供的关于存储库的信息。您应该知道如何使用这些信息来帮助决定特定的存储库是否可以帮助您的应用程序,以及您是否可以为代码库做出贡献。
本章详细介绍了 GitHub 提供的关于存储库的信息。您应该知道如何使用这些信息来帮助决定特定的存储库是否可以帮助您的应用,以及您是否可以为代码库做出贡献。
在下一章中,我们将讨论如何获取代码供您个人使用或参与项目。
\ No newline at end of file
......@@ -28,7 +28,7 @@ NPM–v
如果您只是使用文本编辑器和终端窗口,就像本书的前几章一样,您可以按照以下步骤操作:
1. 创建一个用于制作应用程序的文件夹
1. 创建一个用于制作应用的文件夹
2. 打开该文件夹并键入 npm init。这将在您完成所有设置步骤后创建一个 **package.json** 文件。在设置过程中,您可以填写每一行,也可以通过按下**进入**键回答每一个问题并在最后回答**是**来快速浏览。
3. You can now install Gulp and Gulp plugins via the command:
......
......@@ -2,7 +2,7 @@
前一章完全是关于狼吞虎咽的谋生之道:任务。本章将讲述当文件或文件夹改变时的反应,然后相应地采取行动。这给了她相当大的力量,并使某些场景对开发人员来说更容易,这样她就可以更专注于工作,而不会被重复的步骤分散注意力。
在开发您的网站或 web 应用程序时,您知道在浏览器中看到您的更改之前,您必须进行一些手动操作。即使大口已经让任务变得更容易,它仍然需要一些手动干预,比如打开一些外壳,键入一个命令,比如大口生成宏文件。
在开发您的网站或 web 应用时,您知道在浏览器中看到您的更改之前,您必须进行一些手动操作。即使大口已经让任务变得更容易,它仍然需要一些手动干预,比如打开一些外壳,键入一个命令,比如大口生成宏文件。
如果我们能跳过这一部分,让大口自己解决问题,那不是很好吗?欢迎来到大口看 API。
......
这一章的目的是让读者进一步了解大口。我们将通过展示开发人员可能需要的小代码片段和日常任务的用法来做到这一点。
在过去的几年里,网络上 JavaScript 的使用有了惊人的增长。最初,它只是用来响应某个点击事件,以切换某个 HTML 元素(如图片)的可见性。如今,看到 web 应用程序使用数千行 JavaScript 代码,在浏览器中执行一堆任务,而不是将所有内容都回发到服务器并等待答案,这种情况也不例外。所谓的单页应用程序已经越来越受欢迎。但是它也有一个缺点:复杂性会变得非常棘手,而且很容易犯小错误。因此,我们也看到越来越多的工具和语言将你用它们写的代码转换成 JavaScript。通常,这是一项手动任务,过一段时间后可能会变得乏味。幸运的是,我们正在使用 glaugh . js,它通过自动化让生活变得更容易。
在过去的几年里,网络上 JavaScript 的使用有了惊人的增长。最初,它只是用来响应某个点击事件,以切换某个 HTML 元素(如图片)的可见性。如今,看到 web 应用使用数千行 JavaScript 代码,在浏览器中执行一堆任务,而不是将所有内容都回发到服务器并等待答案,这种情况也不例外。所谓的单页应用已经越来越受欢迎。但是它也有一个缺点:复杂性会变得非常棘手,而且很容易犯小错误。因此,我们也看到越来越多的工具和语言将你用它们写的代码转换成 JavaScript。通常,这是一项手动任务,过一段时间后可能会变得乏味。幸运的是,我们正在使用 glaugh . js,它通过自动化让生活变得更容易。
| ![](img/00021.gif) | 注意:Transpiling 是一个特定的术语,用来表示在保持相同抽象级别的同时将一种语言转换成另一种语言的过程——基本上是从 CoffeeScript、TypeScript…到 JavaScript。编译是从一种语言转换成另一种语言,就像将 C#编译成二进制代码(在这种情况下是 IL)。 |
......@@ -180,7 +180,7 @@ CoffeeScript 可能是我遇到的第一种在翻译后生成 JavaScript 的语
### 打字稿
TypeScript 是微软开发的语言,就像 CoffeeScript 一样,编译成 JavaScript。自从谷歌的 Angular.js 团队宣布密切合作以来,它现在获得了很多兴趣和动力。TypeScript 被选为编写即将到来的基于 Angular.js 的应用程序的首选语言。正如你已经感觉到的,这将是未来几年的大事。
TypeScript 是微软开发的语言,就像 CoffeeScript 一样,编译成 JavaScript。自从谷歌的 Angular.js 团队宣布密切合作以来,它现在获得了很多兴趣和动力。TypeScript 被选为编写即将到来的基于 Angular.js 的应用的首选语言。正如你已经感觉到的,这将是未来几年的大事。
如果你想了解更多关于 TypeScript 的知识,我建议你看看[这个网站](http://www.typescriptlang.org/),尤其是操场版块在工作的时候看看。这里使用的例子就是其中之一。
......@@ -711,7 +711,7 @@ Safari:
请注意,源映射不仅适用于 CSS,还可以用于从另一个源文件编译或传输的 JavaScript 文件。浏览器可以利用这一点来显示一些代码最初来自哪里。我们已经在[类型脚本](#_TypeScript_1)部分看到了一个 JavaScript 源图的例子。
载入特定任务要处理的文件,这是一个很好的工具。这些文件的加载顺序与文件夹中的顺序相同。这意味着如果你有文件需要在结果文件中以特定的顺序输出,你将不得不做一些操作。可能出现这种情况的情况是一堆 CSS 文件的串联。在这样的文件中,顺序非常重要,因为它可能会使您的应用程序看起来与您想要的不同。
载入特定任务要处理的文件,这是一个很好的工具。这些文件的加载顺序与文件夹中的顺序相同。这意味着如果你有文件需要在结果文件中以特定的顺序输出,你将不得不做一些操作。可能出现这种情况的情况是一堆 CSS 文件的串联。在这样的文件中,顺序非常重要,因为它可能会使您的应用看起来与您想要的不同。
拿下面三个小的来说。css 文件和大口文件:
......@@ -939,7 +939,7 @@ glow . js 是一个任务运行器,这意味着它在闭门造车的情况下
图 29:带有额外选项集的吞咽记录器模块的输出
great 非常擅长生成输出,并覆盖之前的输出。当在应用程序中设置一个*大口架构*时,你可能会在每一步都测试它。这最终可能会给出输出文件,而你可能不再需要这些文件,或者这些文件只是成为了一堆垃圾。潜在地,您可能会将剩余部分集成到您的实际解决方案中,降低性能,或者更糟,导致 bug。
great 非常擅长生成输出,并覆盖之前的输出。当在应用中设置一个*大口架构*时,你可能会在每一步都测试它。这最终可能会给出输出文件,而你可能不再需要这些文件,或者这些文件只是成为了一堆垃圾。潜在地,您可能会将剩余部分集成到您的实际解决方案中,降低性能,或者更糟,导致 bug。
因此,在执行“吞咽魔法”之前,最好从头开始,清除输出文件夹及其子文件夹。通常这是作为默认任务的从属任务来完成的,例如:
......
......@@ -220,13 +220,13 @@ Grunt 讲的都是配置而不是编码,而 glaugh 讲的都是通过代码进
图 33: Visual Studio 2015 开始屏幕
从接下来出现的模态窗口中(图 34),选择**ASP.NET 网络应用程序**。给它一个有意义的名称,并在您的机器上选择一个路径。确保选择了选项**为解决方案**创建目录。
从接下来出现的模态窗口中(图 34),选择**ASP.NET 网络应用**。给它一个有意义的名称,并在您的机器上选择一个路径。确保选择了选项**为解决方案**创建目录。
![](img/00038.jpeg)
图 34:选择 ASP.NET 网络应用程序
图 34:选择 ASP.NET 网络应用
点击**确定**按钮后,你会看到一个如图 35 所示的窗口。从 ASP.NET 5 预览模板中选择第三个选项,**网络应用程序**。点击**确定**
点击**确定**按钮后,你会看到一个如图 35 所示的窗口。从 ASP.NET 5 预览模板中选择第三个选项,**网络应用**。点击**确定**
![](img/00039.jpeg)
......@@ -240,9 +240,9 @@ Visual Studio 2015 现在将基于所选模板创建一个新的解决方案。
在项目的根**gulf invs**中,可以看到文件 gulpfile.js 也被添加到解决方案中。为了您的方便,它已经设置了一些初始代码。我们接下来会看到。
创建新的 ASP.NET 5 web 应用程序后,解决方案中已经有一个包含以下内容的 gulpfile.js:
创建新的 ASP.NET 5 web 应用后,解决方案中已经有一个包含以下内容的 gulpfile.js:
代码清单 60:新创建的 ASP.NET 5 应用程序的内容
代码清单 60:新创建的 ASP.NET 5 应用的内容
```js
///
......@@ -521,13 +521,13 @@ project.webroot 来自 project.json 文件,该文件是通过 require 语句
到目前为止,我们主要看到了如何利用大口来捆绑、缩小和翻译脚本和 CSS。这些都是很棒的特性,并且它从开发人员那里带走了很多手工工作。我们将这些结果直接包含到我们的 HTML 文件中,并将其发送到浏览器以执行他们的工作。
开发时,您实际上会编写代码。在创建下一个伟大应用程序的过程中,您可能会不断构建、捆绑或缩小代码。进行这些更新最终会产生新的文件,我们希望将这些文件包含在我们的页面中。然而,浏览器试图缓存尽可能多的静态内容。这是您的应用程序在生产周期中所需要的,因为您会看到到达服务器的请求越来越少,从而节省了服务器上的资源。在开发或升级生产中部署的应用程序期间,您可能希望通知浏览器,他们需要忽略他们拥有的缓存版本并获取最新版本。
开发时,您实际上会编写代码。在创建下一个伟大应用的过程中,您可能会不断构建、捆绑或缩小代码。进行这些更新最终会产生新的文件,我们希望将这些文件包含在我们的页面中。然而,浏览器试图缓存尽可能多的静态内容。这是您的应用在生产周期中所需要的,因为您会看到到达服务器的请求越来越少,从而节省了服务器上的资源。在开发或升级生产中部署的应用期间,您可能希望通知浏览器,他们需要忽略他们拥有的缓存版本并获取最新版本。
有几种技术,最常见的是要么在文件名中放一个版本号,要么在它后面添加一个唯一的 querystring,使它整体上是唯一的。
因为这是经常被请求的,所以有不同的大口插件试图解决这个常见的问题。
在插件的帮助下,我们可以完成我们的任务。在我们之前使用的 ASP.NET MVC 应用程序中(或者一个新的),我们可以通过将以下内容放入 Razor 页面的<头>部分来调整 **_Layout.cshtml** 文件。
在插件的帮助下,我们可以完成我们的任务。在我们之前使用的 ASP.NET MVC 应用中(或者一个新的),我们可以通过将以下内容放入 Razor 页面的<头>部分来调整 **_Layout.cshtml** 文件。
代码清单 65: _Layout.cshtml 头部
......
......@@ -125,10 +125,10 @@ fn 可以是串联、并联、串联和并联的组合或函数。
npm 作为构建工具?到目前为止,我们看到 npm 被用来下载和管理 now 插件。嗯,在网上可以找到一些文章,关于人们利用 npm 来做我们在这本书里谈到的所有事情。在这个不断变化的世界里,这可能会成为一种新的做事方式。就目前而言,大口仍然是一个伟大的新兴工具,微软的采用也表明了它的重要性。
到目前为止,网络开发人员做了很多努力来最小化和连接脚本或 CSS 文件。有了 HTTP 2,这将不再是必要的。根据一些消息来源,这样做甚至可能适得其反,并损害性能。因此,您可能需要修改您精心制作的大口文件,以跟上不断进步的网络技术和协议,从而最大限度地发挥应用程序的性能。
到目前为止,网络开发人员做了很多努力来最小化和连接脚本或 CSS 文件。有了 HTTP 2,这将不再是必要的。根据一些消息来源,这样做甚至可能适得其反,并损害性能。因此,您可能需要修改您精心制作的大口文件,以跟上不断进步的网络技术和协议,从而最大限度地发挥应用的性能。
现代浏览器(也经常被称为“常青树”浏览器,因为它们总是最新的)已经支持 HTTP 2。服务器软件也在迅速赶上潮流,在未来的几年里,我们将看到对这种改进协议的支持无处不在,因此请确保您的应用程序对此进行密切监控。
现代浏览器(也经常被称为“常青树”浏览器,因为它们总是最新的)已经支持 HTTP 2。服务器软件也在迅速赶上潮流,在未来的几年里,我们将看到对这种改进协议的支持无处不在,因此请确保您的应用对此进行密切监控。
这是一个较短的章节,因为很难预测未来。然而,我们可以预见,HTTP 2 将在未来几年迅速变大。(网络)应用程序的数量将会增加,我们会看到它们有不同的形状,就像在智能设备上打包成一个应用程序,有像 Ionic、Cordova 和 multiple . js 这样的东西。
这是一个较短的章节,因为很难预测未来。然而,我们可以预见,HTTP 2 将在未来几年迅速变大。(网络)应用的数量将会增加,我们会看到它们有不同的形状,就像在智能设备上打包成一个应用,有像 Ionic、Cordova 和 multiple . js 这样的东西。
我希望你喜欢读这本书。
\ No newline at end of file
......@@ -12,9 +12,9 @@
纱提供了资源管理器和作业监视器之间的角色逻辑分离,资源管理器决定任务应该在哪里运行,作业监视器报告任务进度。这种分离是 Hadoop 2 中新架构的核心驱动力,因为它允许作业管理任务分布在集群中,这意味着平台可以在更高的规模上运行。
资源管理器是集中式主组件,它通常运行在 Hadoop 集群中与 HDFS 名称节点相同的节点上。资源管理器作为一个服务运行,当它启动一个新的作业时,它会创建一个应用程序主模块——一个负责确保作业(可以是一个 MapReduce 程序或一个交互式 Spark 会话)成功运行的软件组件。
资源管理器是集中式主组件,它通常运行在 Hadoop 集群中与 HDFS 名称节点相同的节点上。资源管理器作为一个服务运行,当它启动一个新的作业时,它会创建一个应用主模块——一个负责确保作业(可以是一个 MapReduce 程序或一个交互式 Spark 会话)成功运行的软件组件。
当应用程序主节点有工作要做时,它们会与资源管理器一起请求资源(例如 MapReduce 中的地图任务),它们还会与节点管理器一起工作,节点管理器是 Hadoop 集群中每个数据节点上的计算服务,以便监控任务。图 9 显示了作业的组件如何在集群中分布。
当应用主节点有工作要做时,它们会与资源管理器一起请求资源(例如 MapReduce 中的地图任务),它们还会与节点管理器一起工作,节点管理器是 Hadoop 集群中每个数据节点上的计算服务,以便监控任务。图 9 显示了作业的组件如何在集群中分布。
![yarn-work](img/00013.jpeg)
......@@ -24,31 +24,31 @@
| ![](img/00010.gif) | 注意:纱容器在逻辑上类似于 Docker 容器,但是到目前为止 Docker 还没有与纱集成,这意味着任务不能被调度为作为 Docker 容器实例运行。 |
当任务容器运行时,它们与应用程序主服务器通信进度,应用程序主服务器与提交作业的客户端通信整体进度。纱线允许容器使用自己的协议进行通信,这允许纱线在不知道框架内部工作的情况下运行不同类型的作业。然而,应用程序主机必须使用纱协议与资源管理器通信。
当任务容器运行时,它们与应用主服务器通信进度,应用主服务器与提交作业的客户端通信整体进度。纱线允许容器使用自己的协议进行通信,这允许纱线在不知道框架内部工作的情况下运行不同类型的作业。然而,应用主机必须使用纱协议与资源管理器通信。
当所有任务容器都完成时,应用程序主机将向客户端和资源管理器标记整个作业为完成,以便应用程序主机使用的资源可以被释放并用于其他工作。
当所有任务容器都完成时,应用主机将向客户端和资源管理器标记整个作业为完成,以便应用主机使用的资源可以被释放并用于其他工作。
纱的一个关键特征是,应用程序主机本身运行在一个容器中,该容器可以托管在集群中的任何节点管理器上,这样大型集群的容量就不会受到资源管理器容量的限制(因为应用程序主机分布在集群的工作节点周围)。
纱的一个关键特征是,应用主机本身运行在一个容器中,该容器可以托管在集群中的任何节点管理器上,这样大型集群的容量就不会受到资源管理器容量的限制(因为应用主机分布在集群的工作节点周围)。
## 【MapReduce 如何在纱中工作
我们已经用`hadoop jar`命令行向纱提交了一些 MapReduce 作业。使用该命令,我们所做的就是指定包含我们想要运行的应用程序和我们自己的代码所期望的参数的 JAR 文件。这会将作业提交给运行在名称节点上的资源管理器,资源管理器会检查集群中的容量,并在其中一个数据节点上分配一个容器来运行作业的应用程序主机。
我们已经用`hadoop jar`命令行向纱提交了一些 MapReduce 作业。使用该命令,我们所做的就是指定包含我们想要运行的应用和我们自己的代码所期望的参数的 JAR 文件。这会将作业提交给运行在名称节点上的资源管理器,资源管理器会检查集群中的容量,并在其中一个数据节点上分配一个容器来运行作业的应用主机。
应用程序主机可以在任何数据节点上启动,因此注册过程会返回到资源管理器,然后资源管理器会通知客户端应用程序主机正在哪里运行。从那时起,客户端直接与运行在数据节点上的应用程序主机通信。
应用主机可以在任何数据节点上启动,因此注册过程会返回到资源管理器,然后资源管理器会通知客户端应用主机正在哪里运行。从那时起,客户端直接与运行在数据节点上的应用主机通信。
应用程序主机启动作业,作业使用自己的框架来确定要执行的计算任务。在我们的字数统计中,MapReduce 框架将生成多个地图任务——每个输入文件一个,一个 Reduce 任务一个。当任务运行时,应用程序主机向资源管理器请求新的容器分配。请求可以具体说明其要求,请求所需的内存和 CPU 数量,甚至是首选节点名或机架名。
应用主机启动作业,作业使用自己的框架来确定要执行的计算任务。在我们的字数统计中,MapReduce 框架将生成多个地图任务——每个输入文件一个,一个 Reduce 任务一个。当任务运行时,应用主机向资源管理器请求新的容器分配。请求可以具体说明其要求,请求所需的内存和 CPU 数量,甚至是首选节点名或机架名。
如果有可用的容量,资源管理器会识别应用程序应该使用哪个节点管理器,应用程序主节点会直接与该节点通信,以创建容器并启动任务。当任务正在运行时,应用程序主机监视它们,当有更多任务排队等待调度时,应用程序主机继续运行。
如果有可用的容量,资源管理器会识别应用应该使用哪个节点管理器,应用主节点会直接与该节点通信,以创建容器并启动任务。当任务正在运行时,应用主机监视它们,当有更多任务排队等待调度时,应用主机继续运行。
如果任何任务失败,应用程序主机将决定如何处理失败。这可能意味着通过对资源管理器的另一次调用来重新安排任务,或者,在多次失败的情况下,这可能意味着将整个作业标记为失败并退出应用程序。在应用程序主机和资源管理器之间的通信失败的情况下,如在数据节点失败的情况下,资源管理器本身可以终止应用程序
如果任何任务失败,应用主机将决定如何处理失败。这可能意味着通过对资源管理器的另一次调用来重新安排任务,或者,在多次失败的情况下,这可能意味着将整个作业标记为失败并退出应用。在应用主机和资源管理器之间的通信失败的情况下,如在数据节点失败的情况下,资源管理器本身可以终止应用
无论应用程序主机如何结束,资源管理器的工作都是整理应用程序的容器分配,以便计算资源可以用于其他容器,例如新的应用程序主机或由另一个应用程序启动的任务容器。
无论应用主机如何结束,资源管理器的工作都是整理应用的容器分配,以便计算资源可以用于其他容器,例如新的应用主机或由另一个应用启动的任务容器。
资源管理器是根据需要分配计算资源的核心组件。计算资源是有限的,这意味着可能会有一个时间点无法满足资源请求,而纱有一个可插拔的框架,允许不同的方法来满足请求。这是调度器组件,它是在整个集群级别配置的,目前有三种实现与 Hadoop 捆绑在一起。
最简单的实现是先进先出调度器,它纯粹根据哪个应用程序先请求来分配资源。如果您使用先进先出调度器向 Hadoop 集群提交大型作业,该作业将使用所有可用资源,直到它完成或达到不再需要所有资源的程度。
最简单的实现是先进先出调度器,它纯粹根据哪个应用先请求来分配资源。如果您使用先进先出调度器向 Hadoop 集群提交大型作业,该作业将使用所有可用资源,直到它完成或达到不再需要所有资源的程度。
当应用程序停止请求资源并释放现有资源时,队列中的下一个应用程序将启动。图 10 显示了两个大型作业和一个小型作业如何使用先进先出调度器运行。
当应用停止请求资源并释放现有资源时,队列中的下一个应用将启动。图 10 显示了两个大型作业和一个小型作业如何使用先进先出调度器运行。
![fifo](img/00014.jpeg)
......@@ -90,13 +90,13 @@
13:纱线资源管理器界面
资源管理器的默认视图是显示所有正在运行的作业,并链接到它们各自的应用程序主用户界面。请记住,作业的应用程序主节点作为容器在集群中的工作节点上运行,但是应用程序主节点将进度反馈给资源管理器。当您打开应用程序链接时,您将看到图 14 中的用户界面。
资源管理器的默认视图是显示所有正在运行的作业,并链接到它们各自的应用主用户界面。请记住,作业的应用主节点作为容器在集群中的工作节点上运行,但是应用主节点将进度反馈给资源管理器。当您打开应用链接时,您将看到图 14 中的用户界面。
![](img/00018.jpeg)
14:应用主界面
图 14 显示了应用程序运行的工作分解。在这种情况下,是第 2 章的`grep` MapReduce 作业,UI 显示地图数量,按状态减少任务。您可以通过跟踪链接并获得所有任务的列表,然后查看特定任务的日志,来深入了解更多细节,如图 15 所示。
图 14 显示了应用运行的工作分解。在这种情况下,是第 2 章的`grep` MapReduce 作业,UI 显示地图数量,按状态减少任务。您可以通过跟踪链接并获得所有任务的列表,然后查看特定任务的日志,来深入了解更多细节,如图 15 所示。
![](img/00019.jpeg)
......@@ -106,11 +106,11 @@
有了资源管理器和节点管理器 web 服务器,您还将获得很多关于机器和 Hadoop 设置的细节。您可以查看节点的硬件分配,查看 Hadoop 服务日志,以及从网络用户界面查看指标和配置,这些都值得深入了解。
在这一章中,我们看了 Hadoop 的计算部分,了解了 part 是如何工作的,以及 Hadoop 中资源协商的关键部分。纱线是一个通用的作业调度器,支持不同的应用程序框架——主要是 MapReduce,但也支持较新的应用程序类型,如 Spark 和 Tez。纱允许应用程序请求资源,它将满足基于集群的容量,它让应用程序决定他们需要哪些资源。
在这一章中,我们看了 Hadoop 的计算部分,了解了 part 是如何工作的,以及 Hadoop 中资源协商的关键部分。纱线是一个通用的作业调度器,支持不同的应用框架——主要是 MapReduce,但也支持较新的应用类型,如 Spark 和 Tez。纱允许应用请求资源,它将满足基于集群的容量,它让应用决定他们需要哪些资源。
纱的灵活性和可扩展性来自于主/从框架和关注点的分离。在主节点上,资源管理器的角色是接受来自客户端的作业请求,通过分配应用程序主节点来启动作业,以及响应来自正在运行的应用程序的资源请求。
纱的灵活性和可扩展性来自于主/从框架和关注点的分离。在主节点上,资源管理器的角色是接受来自客户端的作业请求,通过分配应用主节点来启动作业,以及响应来自正在运行的应用的资源请求。
应用程序主控在资源容器中运行(不是 Docker 容器!)放在数据节点上,这样,纱中的管理开销就分布在整个集群中。当请求资源时,可以将纱配置为使用各种调度策略,这意味着您可以将集群设置为使用基本的先进先出策略、固定容量策略或公平策略,在活动作业之间尽可能公平地共享计算。
应用主控在资源容器中运行(不是 Docker 容器!)放在数据节点上,这样,纱中的管理开销就分布在整个集群中。当请求资源时,可以将纱配置为使用各种调度策略,这意味着您可以将集群设置为使用基本的先进先出策略、固定容量策略或公平策略,在活动作业之间尽可能公平地共享计算。
我们还简要地注意到,纱资源管理器和节点管理器服务嵌入了网络服务器。网络用户界面告诉我们许多关于集群中节点的信息以及它们所做的工作,这意味着它们对管理和故障排除很有用。
......
......@@ -4,9 +4,9 @@ Hadoop Streaming 使用标准的输入和输出流与执行进程进行通信,
流允许您使用 MapReduce,而不必为 mappers 和 Reduce 编写 Java 代码。这对于已经投资非 Java 平台的用户尤其有吸引力。如果您有一个用 Python 编写的自定义分析代码库,或者您想在中编写 MapReduce 代码。NET,你可以用 Hadoop Streaming 做到这一点。您甚至可以混合和匹配,使用带有 C++缩减器的 Java 映射器或带有 Python 缩减器的 R 映射器。
流式作业是一种 MapReduce 作业,但作业任务的执行模式不同。它仍然从客户端提交给纱,并且仍然遵循纱架构,应用程序主开始管理作业。Hadoop 附带了一个标准的 JAR 作为驱动程序,我们通过`hadoop`命令传递我们想要流式传输的可执行文件的细节。
流式作业是一种 MapReduce 作业,但作业任务的执行模式不同。它仍然从客户端提交给纱,并且仍然遵循纱架构,应用主开始管理作业。Hadoop 附带了一个标准的 JAR 作为驱动程序,我们通过`hadoop`命令传递我们想要流式传输的可执行文件的细节。
当任务容器运行时,Java 虚拟机生成可执行进程。与应用程序主机的通信是在 Java 虚拟机中完成的,它充当了 Hadoop 和流可执行文件之间的桥梁。
当任务容器运行时,Java 虚拟机生成可执行进程。与应用主机的通信是在 Java 虚拟机中完成的,它充当了 Hadoop 和流可执行文件之间的桥梁。
`hadoop-streaming.jar`取四个参数(至少;可以使用更多参数配置作业),指定输入和输出目录以及要为映射器和缩减器运行的可执行文件。代码清单 20 显示了一个有效的 Hadoop Streaming 作业提交,它使用标准的 Linux shell 命令作为映射器和缩减器。
......@@ -36,7 +36,7 @@ Hadoop Streaming 使用标准的输入和输出流与执行进程进行通信,
`wc`命令以固定的顺序写入输出,显示输入的行数、字数和总字节数,这里我们有 2022 行,包含 75KB 存储空间中的 7600 个字。这是一个微不足道的例子,但它展示了流的力量——这里我们有一些由 Hadoop 生成的真实分析,我们根本不需要编写任何代码。
Hadoop Streaming 使用一个非常简单的接口与可执行的映射器和缩减器进行通信。请记住,所有数据都是作为键值对通过 Hadoop 传输的,这也适用于流式应用程序。输入从 Hadoop 以一系列制表符分隔的行的形式提供给可执行文件,其中:
Hadoop Streaming 使用一个非常简单的接口与可执行的映射器和缩减器进行通信。请记住,所有数据都是作为键值对通过 Hadoop 传输的,这也适用于流式应用。输入从 Hadoop 以一系列制表符分隔的行的形式提供给可执行文件,其中:
* 第一个制表符前的字符串是键。
* 第一个制表符后的字符串是值。
......@@ -153,9 +153,9 @@ Python 也是一种非常简洁的语言。完整的源代码在 GitHub 上的[
为了。NET 程序员,微软提供了一个模仿 Hadoop Java API 的库,让我们可以在其中构建 MapReduce 作业。NET,使用类似于 Java 中使用的结构。映射器和缩减器都有基类,我们可以用键和值对以类型安全的方式工作。
然而,这只是 Hadoop Streaming 的一个语法包装——最终,. NET 项目被构建为一个可执行文件,它以与普通控制台应用程序相同的方式通过标准输入和输出流进行读写。那个。NET API 也有一段时间没有刷新了,这意味着您必须决定依赖一个旧的 API 是否值得您从包装流接口中获得的价值。
然而,这只是 Hadoop Streaming 的一个语法包装——最终,. NET 项目被构建为一个可执行文件,它以与普通控制台应用相同的方式通过标准输入和输出流进行读写。那个。NET API 也有一段时间没有刷新了,这意味着您必须决定依赖一个旧的 API 是否值得您从包装流接口中获得的价值。
在本章中,我们将继续讨论 Hadoop 流和。NET 源代码在 GitHub 上的[六眼/Hadoop-简洁地说/dotnet](https://github.com/sixeyed/hadoop-succinctly/tree/master/dotnet) ,它有两个控制台应用程序——一个用于映射器,一个用于减速器。代码与 Python 变体基本相同——将为每个任务创建一个映射器实例,并将输入文件中的行输入到 **stdin** 中。
在本章中,我们将继续讨论 Hadoop 流和。NET 源代码在 GitHub 上的[六眼/Hadoop-简洁地说/dotnet](https://github.com/sixeyed/hadoop-succinctly/tree/master/dotnet) ,它有两个控制台应用——一个用于映射器,一个用于减速器。代码与 Python 变体基本相同——将为每个任务创建一个映射器实例,并将输入文件中的行输入到 **stdin** 中。
映射器类中的`Main()`方法循环通过 **stdin** ,同时有更多的行,如代码清单 27 中的代码。
......@@ -271,7 +271,7 @@ reducer 类以同样的方式工作,通过控制台输入流循环的`Main()`
借助流接口,我们使用自己选择的语言构建相同类型的 MapReduce 组件。不需要显式的驱动程序,因为 Hadoop 安装提供的`hadoop-streaming.jar`充当驱动程序。我们也可以用我们选择的语言编写映射器和缩减器,并且我们可以包含我们通常使用的任何单元测试工具。
因为流可执行文件只是控制台应用程序,所以我们也可以使用输入数据的子集运行集成测试——将单个文件馈送到映射器并捕获输出,该输出将作为减速器的输入进行馈送。而且,因为运行连接 Hadoop 和我们的流程的 Java 虚拟机会产生成本,所以使用流的决定将包括性能考虑。
因为流可执行文件只是控制台应用,所以我们也可以使用输入数据的子集运行集成测试——将单个文件馈送到映射器并捕获输出,该输出将作为减速器的输入进行馈送。而且,因为运行连接 Hadoop 和我们的流程的 Java 虚拟机会产生成本,所以使用流的决定将包括性能考虑。
然而,要注意流媒体应用的互操作性。Hadoop 和您的可执行文件之间的协议总是以字符串的形式传递数据,因此,如果您试图将非 Java 映射器与 Java reducer 混合,您必须确保您的映射器以预期的格式序列化键和值输出,以便 reducer 可以反序列化。
......@@ -300,7 +300,7 @@ reducer 类以同样的方式工作,通过控制台输入流循环的`Main()`
当此作业运行时,Hadoop 将以高复制因子(默认值为 10)将归档文件复制到 HDFS,目标是它已经位于运行作业任务的节点的本地。当任务运行时,它将归档文件提取到作业的工作目录中,这样您的可执行文件将在当前目录中找到它的依赖项。
当一个 Java MapReduce 任务在 Hadoop 中的一个节点上运行时,它首先创建一个 Java 虚拟机来运行构建映射器或 Reduce 的 JAR 文件。对于流应用程序,没有 Java 代码,但是 JVM 仍然会被创建,除非您使用系统命令,否则可执行文件还需要引导自己的运行时。加载可执行运行时和 JVM 的成本将影响作业的整体运行时。
当一个 Java MapReduce 任务在 Hadoop 中的一个节点上运行时,它首先创建一个 Java 虚拟机来运行构建映射器或 Reduce 的 JAR 文件。对于流应用,没有 Java 代码,但是 JVM 仍然会被创建,除非您使用系统命令,否则可执行文件还需要引导自己的运行时。加载可执行运行时和 JVM 的成本将影响作业的整体运行时。
评估这个成本可能很困难,因为您不能使用典型的插装技术——当您的插装代码运行时,运行时已经被加载,并且成本已经发生。这种差异通常并不重要,因为您选择使用流是出于非功能性的原因(拥有想要重用的现有逻辑,或者在非 Java 平台中拥有更丰富的经验)。然而,了解做这个决定的成本总是有用的。
......
......@@ -14,8 +14,8 @@ Hadoop 具有内在的可扩展性和丰富的可配置性,这意味着您可
主服务器和从服务器的硬件要求在以下方面有所不同:
* 主节点是一个关键组件。它需要可靠,所以它应该有硬件 RAID 来存储,即使它不需要大量的磁盘空间。作为名称节点,它维护 HDFS 文件命名空间(存储在群集上的所有文件块的位置和元数据),为了提高性能,它将这些信息保存在内存中。作为资源管理器,它是所有应用程序主机和客户端连接的中心联系点。名称节点和资源管理器服务具有多种功能,但 CPU 能力不是主要因素。
* 工作节点并不重要,因为 Hadoop 的架构考虑到了故障。作为 HDFS 数据节点,工作人员应该有大量的磁盘存储,并且不需要 RAID(HDFS 使用循环方法进行磁盘分配,对于典型的 Hadoop 工作负载,该方法的基准速度快于 RAID-0)。作为纱节点管理人员,工作人员应该有大量的中央处理器核心和大量的内存供应。如果您计划只运行 MapReduce 应用程序,每个任务将需要分配一个 CPU 内核和 1 GB 内存,您可以使用这些数据来粗略计算集群的容量。
* 主节点是一个关键组件。它需要可靠,所以它应该有硬件 RAID 来存储,即使它不需要大量的磁盘空间。作为名称节点,它维护 HDFS 文件命名空间(存储在群集上的所有文件块的位置和元数据),为了提高性能,它将这些信息保存在内存中。作为资源管理器,它是所有应用主机和客户端连接的中心联系点。名称节点和资源管理器服务具有多种功能,但 CPU 能力不是主要因素。
* 工作节点并不重要,因为 Hadoop 的架构考虑到了故障。作为 HDFS 数据节点,工作人员应该有大量的磁盘存储,并且不需要 RAID(HDFS 使用循环方法进行磁盘分配,对于典型的 Hadoop 工作负载,该方法的基准速度快于 RAID-0)。作为纱节点管理人员,工作人员应该有大量的中央处理器核心和大量的内存供应。如果您计划只运行 MapReduce 应用,每个任务将需要分配一个 CPU 内核和 1 GB 内存,您可以使用这些数据来粗略计算集群的容量。
服务器规格不断提高,但在撰写本文时,适合用作 Hadoop 从机的典型商用服务器将具有 12-16 个 CPU 内核、48-128 GB RAM 和最多 12 个磁盘,每个磁盘最多有 4 TB 的存储空间。合理的预算应该可以让您满足于拥有 48 GB 内存和 36 TB 存储的 16 核机器。
......@@ -71,13 +71,13 @@ Hadoop 具有在不停机的情况下跨集群重新平衡数据的功能,因
您可以运行命令`hadoop balancer`开始跨集群重新平衡数据,但是,为了节省集群容量,Hadoop 在决定数据块应该移动到哪里时使用运行平衡器的机器的计算能力,这意味着您应该在能够快速访问集群的快速机器上调用它。即便如此,重新平衡数万亿字节的数据也需要几天时间。
正如 HDFS 监控数据可用性和补救任何丢失的块一样,纱监控作业进度,并可以解决丢失任务和失败作业的问题。当任务运行时,它们由应用程序主节点负责,它们发送心跳消息的方式与数据节点向名称节点发送心跳的方式相同。
正如 HDFS 监控数据可用性和补救任何丢失的块一样,纱监控作业进度,并可以解决丢失任务和失败作业的问题。当任务运行时,它们由应用主节点负责,它们发送心跳消息的方式与数据节点向名称节点发送心跳的方式相同。
如果任务意外失败,或者心跳停止,应用程序主机会将任务标记为失败,并请求资源管理器(理想情况下,资源管理器运行在与任务失败的节点不同的节点上)提供另一个容器来重复失败的任务。
如果任务意外失败,或者心跳停止,应用主机会将任务标记为失败,并请求资源管理器(理想情况下,资源管理器运行在与任务失败的节点不同的节点上)提供另一个容器来重复失败的任务。
纱让你配置多少重试允许映射和减少任务。默认值允许四次任务重试,但是如果任何一个任务失败四次,应用程序将被标记为失败。这也是可配置的,这意味着您可以设置纱,以允许一定百分比的任务失败,同时保持作业运行。
纱让你配置多少重试允许映射和减少任务。默认值允许四次任务重试,但是如果任何一个任务失败四次,应用将被标记为失败。这也是可配置的,这意味着您可以设置纱,以允许一定百分比的任务失败,同时保持作业运行。
尽管是资源管理器的责任,运行的应用程序也发送心跳消息。如果应用程序主机以失败状态结束或停止发送心跳,资源管理器将结束应用程序,并使用新的应用程序主机重试。应用程序的重试次数也是可配置的,这意味着纱允许大量的调整,以确保短暂的故障不会阻止作业完成,坏的作业(可能是由于代码或数据的缺陷)不会阻塞集群。
尽管是资源管理器的责任,运行的应用也发送心跳消息。如果应用主机以失败状态结束或停止发送心跳,资源管理器将结束应用,并使用新的应用主机重试。应用的重试次数也是可配置的,这意味着纱允许大量的调整,以确保短暂的故障不会阻止作业完成,坏的作业(可能是由于代码或数据的缺陷)不会阻塞集群。
我们已经看到了 Hadoop 行为可配置的几个方面,我们可以调整大量开关来改变集群的特性。
......@@ -173,7 +173,7 @@ HDFS 配置属性允许您指定服务应该侦听的端口、数据传输是否
在本章中,我们更仔细地研究了 Hadoop 集群的细节。我们研究了主节点和从节点的硬件要求,以及 Hadoop 在不同大小的集群中的表现。因为 Hadoop 集群能够优雅地扩展,所以确定新实施规模的一个好方法是估计数据捕获率,并为已知时期内具有足够容量的集群做好规划。
我们看到了 Hadoop 节点如何相互通信,发送心跳和状态报告,我们还看到了这如何提高集群的可靠性。对于 HDFS,数据节点的丢失会触发其余节点之间的额外数据复制。对于纱,节点管理器的丢失意味着在该节点上运行的任务和应用程序将在集群上被重新调度。
我们看到了 Hadoop 节点如何相互通信,发送心跳和状态报告,我们还看到了这如何提高集群的可靠性。对于 HDFS,数据节点的丢失会触发其余节点之间的额外数据复制。对于纱,节点管理器的丢失意味着在该节点上运行的任务和应用将在集群上被重新调度。
最后,我们查看了 Hadoop 中的配置框架,并看到了一些我们可以覆盖的属性。Hadoop 操作中的所有关键决策点都有我们可以指定的设置,这意味着我们可以调整我们的集群,以最有效地满足我们的需求。
......
......@@ -6,7 +6,7 @@
虽然从头开始安装 Hadoop 并不是一项令人生畏的任务,但建立多节点集群要复杂得多,而且它为扩展提供了更大的表面积。如果您决定将 Hive 或 Spark 添加到您的集群中,那么当您有 10 个数据节点要部署并保持同步时,这并不是小事。Ambari 是解决这个问题的管理控制台,还有更多。
Ambari 是一个网络应用程序,旨在为您的 Hadoop 集群提供单一访问点。它有一个可扩展的体系结构,具有不断增长的组件库,提供管理、监控和使用集群的功能。除了提供 web 视图,它还公开了一个 REST API,用于自动访问您的集群。
Ambari 是一个网络应用,旨在为您的 Hadoop 集群提供单一访问点。它有一个可扩展的体系结构,具有不断增长的组件库,提供管理、监控和使用集群的功能。除了提供 web 视图,它还公开了一个 REST API,用于自动访问您的集群。
在管理领域,安巴里负责所有主要任务:
......
......@@ -6,7 +6,7 @@
[本 HBase 文档](http://hbase.apache.org/)涵盖了在本地安装和运行 HBase,因此我在此不再赘述,但是在本地运行 HBase 最简单的方法是使用 Docker。Docker Hub 上有几个 HBase 映像,包括我自己的一个,这是我为了配合本课程而构建的。
Docker 是一种应用程序容器技术。容器是一个快速、轻量级的计算单元,允许您在一台机器上运行多个负载。容器在概念上类似于虚拟机,但在磁盘、CPU 和内存使用方面要轻得多。Docker 运行在 Linux、OS/X 和 Windows 机器上。您可以在这里获得安装说明[。Docker Hub 是一个预建图像的公共注册中心,我为这本书准备的图像可以在](http://www.docker.com/)[这里](https://hub.docker.com/r/sixeyed/hbase-succinctly)找到。
Docker 是一种应用容器技术。容器是一个快速、轻量级的计算单元,允许您在一台机器上运行多个负载。容器在概念上类似于虚拟机,但在磁盘、CPU 和内存使用方面要轻得多。Docker 运行在 Linux、OS/X 和 Windows 机器上。您可以在这里获得安装说明[。Docker Hub 是一个预建图像的公共注册中心,我为这本书准备的图像可以在](http://www.docker.com/)[这里](https://hub.docker.com/r/sixeyed/hbase-succinctly)找到。
使用 Docker 容器的好处是,您可以用很少的开销加速和终止实例,并且您不需要担心任何软件或服务与您的开发机器冲突。
......
......@@ -19,7 +19,7 @@ HBase Java 客户端可在 Maven Central 存储库中获得,并且经过版本
```
使用 Java API,您可以从一个 Configuration 对象开始,该对象包含服务器的连接详细信息,当您为表或管理创建客户机对象时,可以使用该对象。当您创建一个配置对象时,默认情况下,它会在包含配置设置的正在运行的应用程序的资源中查找一个 hbase-site.xml 文件。
使用 Java API,您可以从一个 Configuration 对象开始,该对象包含服务器的连接详细信息,当您为表或管理创建客户机对象时,可以使用该对象。当您创建一个配置对象时,默认情况下,它会在包含配置设置的正在运行的应用的资源中查找一个 hbase-site.xml 文件。
hbase-site.xml 配置文件也存在于服务器上,您可以将相同的内容用于客户端连接—它指定了服务器端口和 Zookeeper 仲裁地址等关键细节。代码清单 22 显示了站点文件的一些示例属性:
......
......@@ -4,7 +4,7 @@ HBase 非常适合跨平台的解决方案,而节俭 API 是 Java API 的替
默认情况下,节俭服务器监听端口 9090,并且它已经在简洁的 Docker 映像上运行。
节俭比 REST 更轻量级,因此它可以提供更好的性能,但它并不那么用户友好。要使用节俭应用编程接口,在大多数情况下,您需要从源构建节俭,从公共生成到应用编程接口的绑定。描述接口的节俭文件,然后为您的客户端应用程序导入节俭传输和绑定。
节俭比 REST 更轻量级,因此它可以提供更好的性能,但它并不那么用户友好。要使用节俭应用编程接口,在大多数情况下,您需要从源构建节俭,从公共生成到应用编程接口的绑定。描述接口的节俭文件,然后为您的客户端应用导入节俭传输和绑定。
![](img/00013.jpeg)注意:节俭原料药记录在。节俭档案。该文件没有附带 HBase 二进制文件,因此您需要从源代码中获取正确的版本。对于版本 1.1.2,文件在 GitHub [这里](https://github.com/apache/hbase/blob/cc2b70cf03e3378800661ec5cab11eb43fafe0fc/hbase-thrift/src/main/resources/org/apache/hadoop/hbase/thrift2/hbase.thrift)上。
......@@ -173,7 +173,7 @@ HappyBase 表对象上的 put()方法的工作方式很像 HBase Shell 中的 pu
```
put()方法仅限于单个行,但是 HappyBase 为批处理更新提供了一个有用的机制。这是 HBase 客户端的一个常见要求,尤其是在事件流应用程序中,您每秒可能会收到数百甚至数千个要在处理器中缓冲的事件。
put()方法仅限于单个行,但是 HappyBase 为批处理更新提供了一个有用的机制。这是 HBase 客户端的一个常见要求,尤其是在事件流应用中,您每秒可能会收到数百甚至数千个要在处理器中缓冲的事件。
HappyBase 中的 Batch 类允许您这样做,而无需编写定制代码来维护挂起更新的缓冲区。您可以从表中创建批处理对象,并在上下文管理器块中使用它。当块结束时,在批处理上调用 send()方法,该方法将所有更新发送到节俭服务器,如代码清单 46 所示:
......
......@@ -107,11 +107,11 @@ HTTP GET 请求相当于 HBase 外壳中的 GET 命令。您可以添加一个
您可以使用 cURL 做更多的事情,比如发送删除请求来删除数据,以及创建扫描仪来获取多行,但是语法变得很麻烦。在星际之门中使用 RESTful API 的包装器是一个更好的选择。
NuGet 是的包管理器。NET 应用程序,还有几个开源包是访问星际之门的包装器。微软有一个专门针对运行在 Azure 云上的 HBase 集群的包,还有一个来自作者“部落”的第三方包,用于一般地使用 Stargate。
NuGet 是的包管理器。NET 应用,还有几个开源包是访问星际之门的包装器。微软有一个专门针对运行在 Azure 云上的 HBase 集群的包,还有一个来自作者“部落”的第三方包,用于一般地使用 Stargate。
这个包很好地抽象了星际之门的内部,让你可以直观地处理 HBase 数据。它是 IoC 感知的(默认情况下使用自动调整),因此您可以轻松地调整 HTTP 设置并构建一个数据访问层,您可以模拟出来进行测试。
将该包及其依赖项添加到。NET 应用程序中,您可以使用代码清单 52 中的 NuGet 包管理器控制台命令:
将该包及其依赖项添加到。NET 应用中,您可以使用代码清单 52 中的 NuGet 包管理器控制台命令:
52:为星门添加一个 NuGet 引用
......@@ -120,7 +120,7 @@ NuGet 是的包管理器。NET 应用程序,还有几个开源包是访问星
```
在本书的 GitHub 存储库中,有一个. NET 控制台应用程序,它使用部落的客户端连接到星门,运行在简洁的 Docker 容器中。
在本书的 GitHub 存储库中,有一个. NET 控制台应用,它使用部落的客户端连接到星门,运行在简洁的 Docker 容器中。
要设置星门客户端,您需要配置服务器网址并构建容器。代码清单 53 展示了如何使用 Autofac 实现这一点:
......
......@@ -14,7 +14,7 @@
## 数据管理和存储
数据管理需求已经从传统的关系存储发展到关系和非关系存储,全方位的信息管理平台需要支持所有类型的数据。为了提供对任何数据的洞察,需要一个平台,为跨关系、非关系和流数据的数据管理提供一整套功能。该平台需要能够无缝地将数据从一种类型移动到另一种类型,并且能够监控和管理所有数据,而不管它是什么类型的数据或数据结构。这必须在应用程序不必担心规模、性能、安全性和可用性的情况下进行。
数据管理需求已经从传统的关系存储发展到关系和非关系存储,全方位的信息管理平台需要支持所有类型的数据。为了提供对任何数据的洞察,需要一个平台,为跨关系、非关系和流数据的数据管理提供一整套功能。该平台需要能够无缝地将数据从一种类型移动到另一种类型,并且能够监控和管理所有数据,而不管它是什么类型的数据或数据结构。这必须在应用不必担心规模、性能、安全性和可用性的情况下进行。
除了支持所有类型的数据之外,在非关系存储(如 Hadoop)和关系数据仓库之间来回移动数据也是大数据客户的主要使用模式之一。为了支持这种常见的使用模式,微软为存储在 Hadoop 和现有的 SQL Server 数据仓库环境(包括 SQL Server 并行数据仓库)中的数据之间的高速数据移动提供了连接器。
......@@ -34,7 +34,7 @@
对于 Windows Azure 上的服务,微软将通过 Azure 管理门户的易用组件实现 Hadoop 集群的无缝设置和配置,从而进一步降低部署障碍。
最后,他们不仅发布了基于开源的 Hadoop 发行版,还承诺将这些更新回馈给 Hadoop 社区。微软致力于提供与 Apache Hadoop 应用程序编程接口(APIs)100%的兼容性,以便为 Apache Hadoop 编写的应用程序应该在 Windows 上工作。
最后,他们不仅发布了基于开源的 Hadoop 发行版,还承诺将这些更新回馈给 Hadoop 社区。微软致力于提供与 Apache Hadoop 应用编程接口(APIs)100%的兼容性,以便为 Apache Hadoop 编写的应用应该在 Windows 上工作。
微软与 [Hortonworks](http://www.hortonworks.com/) 紧密合作,提交了一份正式提案,作为对 Apache 代码库的更改,在 Windows Azure 和 Windows Server 上贡献基于 Hadoop 的发行版。[<sup>【4】</sup>](12.html#_ftn4)此外,他们还在合作开发其他功能,如 Hive 连接,以及由微软和 Hortonworks 开发的创新 JavaScript 库,将被提议作为对 Apache 软件基金会的贡献。
......
......@@ -194,7 +194,7 @@ Reducer 相当于 ETL 范例的转换组件。它的工作是处理提供的数
使用压缩输入和压缩输出会带来一些性能影响,需要根据存储和网络流量考虑进行平衡。要在 HDInsight 中全面回顾这些注意事项,建议您阅读微软关于题为“Hadoop 中的压缩”的主题的白皮书(上面表格中的信息取自该白皮书)。[<sup>【17】</sup>](12.html#_ftn17)
映射器是作为标准的 C#控制台应用程序可执行文件构建的。为了让 Hadoop 作业能够使用它,它需要被加载到作业可以引用该文件的某个地方。Azure Blob 存储显然是处理这个问题的一个方便的地方。
映射器是作为标准的 C#控制台应用可执行文件构建的。为了让 Hadoop 作业能够使用它,它需要被加载到作业可以引用该文件的某个地方。Azure Blob 存储显然是处理这个问题的一个方便的地方。
加载数据和映射器后,使用 Hadoop 命令行来指定和启动作业。也可以通过 SDK 或 PowerShell 提交作业。
......
......@@ -14,11 +14,11 @@
### 为蜂巢建立一个 DSN
连接到 Hive 的先决条件是设置 Excel 可以引用的 64 位系统数据源名称(DSN)。搜索 ODBC 会出现两个应用程序:
连接到 Hive 的先决条件是设置 Excel 可以引用的 64 位系统数据源名称(DSN)。搜索 ODBC 会出现两个应用:
![](img/image010.png)
图 8: ODBC 应用程序
图 8: ODBC 应用
从这里,这将启动 ODBC 数据源管理员。在系统 DSN 下,选择“添加”。选择“创建新数据源”时,配置单元驱动程序被列为可用选项:
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册