码迷,mamicode.com
首页 > 其他好文 > 详细

实现基于数据结构的语言

时间:2014-06-03 00:38:21      阅读:420      评论:0      收藏:0      [点我收藏+]

标签:c   style   blog   a   tar   http   

实现基于数据结构的语言

 

创建任何 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 是可以任意的,只要不与前面定义的命名空间相同即可。

]

 

bubuko.com,布布扣

图 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 那显示了结果图形。

bubuko.com,布布扣

图 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

实现基于数据结构的语言

标签:c   style   blog   a   tar   http   

原文地址:http://blog.csdn.net/hadstj/article/details/27677801

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!