实现基于数据结构的语言

创建任何 DSL 都应该从定义需要解决的问题开始。这里,我们需要定义一个 DSL 库(有时也称为组合库,combinators library),用于二维图形,这是一个很明显的选择。这个示例演示如何用大量简单的基本图形构建出复杂的结构。在计算机屏幕上的图像本质上就是线条和多边形的集合,尽管显示出来的图形可能极其复杂。这个示例用四个模块表现:第一,清单 12-1,提供创建图片的基本操作(primitives);第二,清单12-2,如何实现解释图片;清单 12-3 和清单 12-4 用示例演示如何使用这些库,需要把清单12-1
和12-2 与清单 12-3 或12-4 一起使用,才能看到结果。我们先浏览一下设计过程的要点,看一下整个清单的结论。

注意

这个示例是受使用 A6 系统(http://a6systems.com/)的人的启发,这是一个相似而更复杂的系统,用来渲染三维动画场景,他们把这个库广泛地用于工业用途。

我们首先设计几个描述图形的类型,这些类型构成图形的基本操作(primitives):

// represents the basic shapes that willmake up the scene

type Shape =

|Line of Position * Position

|Polygon of List<Position>

|CompersiteShape of List<Shape>

这个类型是递归的,CompersiteShape 联合情况包含了开头列表,这将构成树形结构。在编译器开发领域,这种树形结构被称为抽象语法树(Abstract Syntax Tree (AST),在本章的最后,我们会看到另一个示例,使用抽象语法树来表示程序。

至此,我们已经创建了图形的三个基本元素:线、多边形和形状。用三种简单元素组成类型的事实是一种重要的设计思想,把基本操作简单化,使得实现渲染图形的引擎更简单;基本操作的简单也意谓着不需要用户花时间与之进行直接交互,相反,提供了一组高级包装函数,返回形状(Shape)类型的值,这就是组合(combinators)。联合中的CompersiteShape 情况是一个很重要的示例,它可以通过简单的元素构建出复杂的形状。通过 compose 函数把它公开:

// allows us to compose a list of elementsinto a

// single shape

let compose shapes = CompersiteShape shapes

用这个函数可以实现许多高级函数,例如,函数 lines,参数为位置列表,返回的形状是由这些位置经过的路径,利用 compose 函数把大量单独的线组合成一条线:

// a line composed of two or more points

let lines posList =

//grab first value in the list

letinitVal =

matchposList with

|first :: _ -> first

|_ -> failwith "must give more than one point"

//creates a new link in the line

letcreateList (prevVal, acc) item =

letnewVal = Line(prevVal, item)

item,newVal :: acc

//folds over the list accumlating all points into a

//list of line shapes

let_, lines = List.fold createList (initVal, []) posList

//compose the list of lines into a single shape

composelines

接下来,再用 lines 函数去实现几个高级形状,比如square 函数:

let square filled (top, right) size =

letpos1, pos2 = (top, right), (top, right + size)

letpos3, pos4 = (top + size, right + size), (top + size, right)

iffilled then

polygon[ pos1; pos2; pos3; pos4; pos1 ]

else

lines[ pos1; pos2; pos3; pos4; pos1 ]

square 函数使用 lines 函数画出经过计算的点的正方形轮廓。可以在清单 12-1 中看到完整的模块,虽然更实际的库实现可能包含更多的基本形状,以供用户选择。要编译这个程序,需要引用System.Drawing.dll 和System.Windows.Forms.dll:

清单 12-1 创建图形的组合库

namespace Strangelights.GraphicDSL

open System.Drawing

// represents a point within the scene

type Position = int * int

// represents the basic shapes that willmake up the scene

type Shape =

|Line of Position * Position

|Polygon of List<Position>

|CompersiteShape of List<Shape>

// allows us to give a color to a shape

type Element = Shape * Color

module Combinators =

//allows us to compose a list of elements into a

//single shape

letcompose shapes = CompersiteShape shapes

//a simple line made from two points

letline pos1 pos2 = Line (pos1, pos2)

//a line composed of two or more points

letlines posList =

//grab first value in the list

letinitVal =

match posList with

| first :: _ -> first

| _ -> failwith "must give more than one point"

//creates a new link in the line

letcreateList (prevVal, acc) item =

let newVal = Line(prevVal, item)

item, newVal :: acc

//folds over the list accumlating all points into a

//list of line shapes

let_, lines = List.fold createList (initVal, []) posList

//compose the list of lines into a single shape

composelines

//a polygon defined by a set of points

letpolygon posList = Polygon posList

//a triangle that can be either hollow or filled

lettriangle filled pos1 pos2 pos3 =

iffilled then

polygon[ pos1; pos2; pos3; pos1 ]

else

lines[ pos1; pos2; pos3; pos1 ]

//a square that can either be hollow or filled

letsquare filled (top, right) size =

letpos1, pos2 = (top, right), (top, right + size)

letpos3, pos4 = (top + size, right + size), (top + size, right)

iffilled then

polygon [ pos1; pos2; pos3; pos4; pos1 ]

else

lines [ pos1; pos2; pos3; pos4; pos1 ]

现在我们已经有了语言的基本元素,下面需要做的是实现解析器,以显示图形。本章描述的解析器是一个 Windows 窗体,这种方法的好处是还可能用WPF、Silverlight 和GTK# 实现解析器,就是说,在图形界面库与平台之间[ 切换]是相当方便的。实现解析器非常简单,只需要实现联合中的每一种情况就可以了,在Line 和 Polygon 情况中,使用 Windows 窗体的基本对象 GDI+ 绘制图形。幸运的是,GDI + 绘制线条或多边形也很简单。第三种情况CompositeShape 也很简单,只要简单地递归调用绘制函数。在清单
12-2 中可以看到完整的源代码,要编译程序,需引用System.Drawing.dll 和 System.Windows.Forms.dll。

清单 12-2 用组合库实现渲染图形的解析器

namespace Strangelights.GraphicDSL

open System.Drawing

open System.Drawing.Drawing2D

open System.Windows.Forms

// a form that can be used to display thescene

type EvalForm(items: List<Element>)as x =

inheritForm()

//handle the paint event to draw the scene

dox.Paint.Add(fun ea ->

letrec drawShape (shape, (color: Color)) =

match shape with

| Line ((x1, y1), (x2, y2)) ->

// draw a line

let pen = new Pen(color)

ea.Graphics.DrawLine(pen, x1, y1, x2,y2)

| Polygon points ->

// draw a polygon

let points =

points

|> List.map (fun (x,y) -> new Point(x, y))

|> Array.ofList

let brush = new SolidBrush(color)

ea.Graphics.FillPolygon(brush, points)

| CompersiteShape shapes ->

// recursively draw the other contained elements

List.iter (fun shape -> drawShape(shape, color)) shapes

// draw all the items we have been passed

items |> List.iter drawShape)

现在,把两个正方形和一个三角形放到一起组成一个图形就很简单了,只要调用我们组合库中适当的函数,把它们组合起来,再加上颜色,就得到了场景的完整描述了。清单 12-3 展示了实现的过程,图 12-0 是运行得到的结果。

[

原文中没有给出这个示例的图形,图 12-1 是清单 12-4 的图示。因此,为了不改变原文的图示排序,此图示就编为图 12-0。

]

清单 12-3 利用组合库的简单示例

open System.Drawing

open System.Windows.Forms

open Strangelights.GraphicDSL

// two test squares

let square1 = Combinators.square true (100,50) 50

let square2 = Combinators.square false (50,100) 50

// a test triangle

let triangle1 =

Combinators.trianglefalse

(150,200) (150, 150) (250, 200)

// compose the basic elements into apicture

let scence = Combinators.compose [square1;square2; triangle1]

// create the display form

let form = new EvalForm([scence,Color.Red])

// show the form

Application.Run form

[

要编译成功,在程序的前面要加上 module name,name 是可以任意的,只要不与前面定义的命名空间相同即可。

]

图 12-0 由正方形和三角形组合成的图形

清单 12-3 中给出的这个简单示例并不可能代表如何利用组合库创建图形,在清单 12-4 中我们将一个更实际的情况。利用组合库的最好方法是按照原来写组合库的风格编程,就是说,应该构建一些可以在图形中重用的简单元素。接下来,我们将看一下如何创建一个场景,组合七星。很明显,开始是创建星,在清单 12-4 中可以看到,我们是如何创建星(Star)函数的定义的,这个函数创建了镜像的三解形,然后,把它们组合在一起,加上一点点偏移,构成了一个六边形的星。从这个示例,我们可以得到一些有关通过简单图形构建复杂图形的概念。有了这个星的定义以后,只需要一个简单的位置列表,告诉星应该在哪里打印出来,清单
12-4 中有点的列表。有了这两个元素之后,就可以用函数List.map和compose 把它们组合起来,创建需要的场景了。下面,就可以用前面清单中的方法来显示场景了。

清单 12-4 用组合库创建更复杂的图形

module 12-4

open System.Drawing

open System.Windows.Forms

open Strangelights.GraphicDSL

// define a function that can draw a6 sided star

let star (x, y) size=

let offset = size
/
2

// calculate the first triangle

let t1 =

Combinators.triangle false

(x, y -size- offset)

(x -size, y+ size
- offset)

(x +size, y+ size
- offset)

// calculate another inverted triangle

let t2 =

Combinators.triangle false

(x, y +size+ offset)

(x +size, y- size
+ offset)

(x -size, y- size
+ offset)

// compose the triangles

Combinators.compose [ t1; t2
]

// the points where stars should beplotted

let points =
[ (10,
20); (200,10);

(30,160);
(100,
150);(190,
150);

(20,300);
(200,
300);]

// compose the stars into a singlescene

let scence =

Combinators.compose

(List.map (funpos-> star pos
5) points)

// show the scene in red on theEvalForm

let form = newEvalForm([scence, Color.Red],

Width =260, Height
=350)

// show the form

Application.Run form

图 12-1 那显示了结果图形。

图 12-1 由组合库渲染的场景

我们已经看到了用两种方法创建组合库(通过数据结构,库创建小语言)。至此,可能已经看出如何把一个问题分解成抽象的描述,它是基于一组很小的基本操作的,这对于建于这些基本操作上的其他库可能是有帮助的。

注意

如果打算用更深入的视角研究组合库,应该看一下由Simon Peyton Jones、Jean-Marc Eber 和 JulianSeward 写的本皮书《Composing contracts: an adventure in financial engineering》,白皮书深入、易懂地研究了用组合库描述 derivatives contracts,白皮书中的示例是用的Haskell 而不是 F#,但是,可以把它转换成 F#。白皮书的地址:

http://research.microsoft.com/en-us/um/people/simonpj/papers/financial-contracts/contracts-icfp.htm

实现基于数据结构的语言,布布扣,bubuko.com

时间: 2025-01-02 14:59:09

实现基于数据结构的语言的相关文章

《数据结构-C语言版》(严蔚敏,吴伟民版)课本源码+习题集解析使用说明

先附上文档归类目录: 课本源码合辑  链接??? <数据结构>课本源码合辑 习题集全解析  链接??? <数据结构题集>习题解析合辑 博主有话说: 01.自学编程,难免思路阻塞,所以从今天起,我(StrayedKing)决定在本博客陆续更新严蔚敏,吴伟民版<数据结构-C语言版>各章节的课本源码和配套习题集答案解析,目的是为了整理数据结构中的知识点,并与网友交流意见,集思广益,共同进步.        ★注★ 左侧随笔分类下用两个栏目:<课本源码>.<习

数据结构C语言实现——线性链表

declaration.h #ifndef DECLARATION_H_INCLUDED #define DECLARATION_H_INCLUDED #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #define INFEASIBLE -1 #define OVERFLOW -2 #define ElemType int typedef ElemType* Triplet; typedef int Status; type

数据结构c语言版串的操作

#include<stdio.h> #include<malloc.h> #include<string.h> //定义字符串的结构体 typedef struct { char *str;//字符串 int maxLength;//最大可以存放字符的长度 int length;//目前的字符长度 }DString; //1.初始化操作 //初始化操作用来建立和存储串的动态数组空间以及给相关的数据域赋值 void Initiate(DString *s,int max,

数据结构C语言实现介绍

刚刚结束的大二上学期学习了数据机构C语言版这门课,在接下来的一个月中准备把课程的代码和知识点总结一下,就有了这个专题:数据结构C语言实现,在这里我将用C语言实现最基本的数据结构以及一些基本的算法,以下是我这个专题的内容: 1. 数据结构及算法简介: 主要介绍数据结构及算法的基础知识,一些基本的概念和术语,以及算法的定义.算法的特性.算法的时间复杂度和空间复杂度 2.顺序表的定义及算法实现: 主要介绍顺序表的定义及特点,实现顺序表的存储结构以及基本操作 3.单链表的定义及算法实现:  主要介绍单链

深入浅出数据结构C语言版(5)——链表的操作

上一次我们从什么是表一直讲到了链表该怎么实现的想法上:http://www.cnblogs.com/mm93/p/6574912.html 而这一次我们就要实现所说的承诺,即实现链表应有的操作(至于游标数组--我决定还是给它单独写个博文比较好~). 那么,我们的过程应该是怎么样的呢?首先当然是分析需要什么操作,然后再逐一思考该如何实现,最后再以代码的形式写出来. 不难发现,我们希望链表能支持的(基础,可以由此延伸)操作就是: 1.给出第n个元素 2.在第n个元素的后面插入一个元素(包含在最后一个

深入浅出数据结构C语言版(19)——堆排序

在介绍优先队列的博文中,我们提到了数据结构二叉堆,并且说明了二叉堆的一个特殊用途--排序,同时给出了其时间复杂度O(N*logN).这个时间界是目前我们看到最好的(使用Sedgewick序列的希尔排序时间复杂度为O(N4/3),下图为两者函数图像对比,但是注意,这并不是希尔排序与堆排序的对比,只是两个大O阶函数的对比).这篇博文,我们就是要细化用二叉堆进行排序的想法,实现堆排序. 在介绍优先队列的博文中(http://www.cnblogs.com/mm93/p/7481782.html)所提到

深入浅出数据结构C语言版(8)——后缀表达式、栈与四则运算计算器

在深入浅出数据结构(7)的末尾,我们提到了栈可以用于实现计算器,并且我们给出了存储表达式的数据结构(结构体及该结构体组成的数组),如下: //SIZE用于多个场合,如栈的大小.表达式数组的大小 #define SIZE 1000 //表达式的单个元素所使用的结构体 typedef struct elem { int num = 0; //若元素存储操作数则num为该操作数 char oper = '='; //若元素存储操作符则oper为该操作符 bool IsNum = false; //用于

《数据结构(C语言版)》学习——day1,初识数据结构

1. 什么是数据结构 一般而言,使用计算机解决一个具体的问题时,大致需要经过以下几个步骤: ① 从具体的问题中抽象出一个适当的数学模型: ② 设计一个求解该数学模型的算法: ③ 编写程序,进行测试.调整,直至得到最终的问题解答. 对实际问题建立数学模型的实质是:分析问题,并从中提取操作的对象,并找出这些对象间含有的关系,然后使用数学的语言加以描述. 数据结构(data structure)可以定义为:相互之间存在一种或多种特定关系的数据元素的集合. 在任何问题中,数据元素都不是孤立存在的,而是存

数据结构_Python语言描述(英)pdf

下载地址:网盘下载 <Python自然语言处理(影印版)>提供了非常易学的自然语言处理入门介绍,该领域涵盖从文本和电子邮件预测过滤,到自动总结和翻译等多种语言处理技术.在<Python自然语言处理(影印版)>中,你将学会编写Python程序处理大量非结构化文本.你还将通过使用综合语言数据结构访问含有丰富注释的数据集,理解用于分析书面通信内容和结构的主要算法. <Python自然语言处理>准备了充足的示例和练习,可以帮助你: 从非结构化文本中抽取信息,甚至猜测主题或识别&