7.3.1 转换表示

728 篇文章 1 订阅
19 篇文章 0 订阅

7.3.1 转换表示

 

    我们实现数的据类型之间,存在两个关键的区别:

    1、在新的表示形式中,文件是单个(递归)值,而在每种情况下,它是元素的列表。

    2、第 7.2 节的数据类型显式包含边框,指定内容的位置。

    3、第二个数据类型,只表示这部分是如何嵌套的。

    这意味着当,当我们转换这种表示形式时,需要计算每个基于嵌套部分的位置。

    这些差异影响转换函数的签名,在我们研究其实现之前先分析一下:

 

val documentToScreen : DocumentPart * Rect -> ScreenElement list

 

    这个函数取要转换的文档部分作为第一个参数值,返回 ScreenElement 值的列表,来自第 7.2 节。这意味着,输入的参数值和结果都能表示整个文档。这个函数还有第二个参数,指定整个文档的边框。在转换期间,我们将需要用它来计算每个部分的位置。清单 7.10 显示了这个实现,是(毫不奇怪)递归函数。

 

Listing 7.10 Translation between document representations (F#)

 

let rec documentToScreen(doc, bounds) =
  match doc with
  | SplitPart(Horizontal, parts) –>
    let width = bounds.Width / (float32(parts.Length))
    parts
      |> List.mapi (fun i part ->
        let left = bounds.Left + float32(i) * width
        let bounds = { bounds with Left = left; Width = width }
        documentToScreen(part, bounds))
      |> List.concat
  | SplitPart(Vertical, parts) –>
    let height = bounds.Height / float32(parts.Length)
    parts
      |> List.mapi (fun i part ->
        let top = bounds.Top + float32(i) * height
        let bounds = { bounds with Top = top; Height = height }
        documentToScreen(part, bounds))
      |> List.concat
  | TitledPart(tx, content) ->
    let titleBounds = { bounds with Height = 35.0f }
    let restBounds = { bounds with Height = bounds.Height - 35.0f;
                                Top = bounds.Top + 35.0f }
    let convertedBody = documentToScreen(content, restBounds)
    TextElement(tx, titleBounds)::convertedBody
  | TextPart(tx) -> [ TextElement(tx, bounds) ]
  | ImagePart(im) -> [ ImageElement(im, bounds) ]

 

    让我们从代码的结尾开始,处理表示内容的部分很容易,因为,我们只返回一个列表,包含单个屏幕元素。我们可以使用矩形表示位置和大小,已经作为一个参数值提供了。无需进一步的计算。

    其余部分是由其他部分组成的。在这种情况下,函数以递归方式调用自己,处理所有的子部件以形成更大的部分。这就是我们必须要执行的布局计算,因为,当我们再次调用 documentToScreen 时,给它一个子部件和这个子部件的边框。我们不能复制 bounds 参数,或在同一地方会终政府所有的子部件!不需要把我们得到的这个矩形划分为较小的矩形,每个子部件一个。

    TitledPart 包含单个子部件,因此,我们需要执行一个递归调用。在此之前,我们为标题计算一个边框(顶部的 35 个像素),一个用于正文(除了顶部 35 像素之外的一切)。接下来,我们以递归方式处理正文,追加表示标题的 TextElement 到要返回的屏幕元素的列表中。

    我们处理 SplitPart,每个方向使用一个单独的分支。我们计算每个行或列的大小,并转换所有的部件。 我们使用 List.mapi 函数,它就像 List.map,但它也给了我们目前正在处理的部件的索引。可以使用这个索引来计算小边框从左侧或顶部的对主矩形的于偏移量。这个 Lambda 函数,然后以递归方式调用 documentToScreen,为每个文档部件返回屏幕元素的列表。这意味着,我们得到一个列表的列表,使用 List.mapi 映射的结果。结果的类型是 list<list>,而不是我们需要返回的平面列表,因此,我们使用标准的 F# 库函数 List.concat,把结果变成 list 类型的值。

 

注意

 

    在文档的不同表示形式之间的转换是这一章的难点,所以,可能想要下载源代码,并体验看它是如何工作的。最有趣(和困难)的部分是每个递归调用计算边框。有必要确保你理解由这个函数返回的列表,它如何从每个深的递归调用建立的。你可能发现用笔和纸完整的走一遍是有用的,跟踪矩形边框和返回屏幕元素。

 

    表示形式之间的转换通常是简化函数式编程的关键,因为,它允许我们实现其他操作的每个部分,使用最适合这种情况的数据结构。我们看到,第一个表示形式是适合绘制的文档,但是,第二个使结构更简单。第二种形式也使操作更容易,像我们在 7.4 节中看到的。在此之前,我们将介绍另一种表示:XML。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值