Play! Framework 2.0 - 在Scala模板中如何遍历Map?

14

我有一个表示目录的映射,它包含 Chapter 键和 List[Section] 值。现在我正在尝试在我的模板中循环遍历它:

<dl>
@table_of_contents.foreach((e) => {
    <dt>
        @e._1.title
    </dt>
        for(section <- e._2){
        <dd>
            @section.title
        </dd>
        }
})
</dl>

目前在<dl>标签中没有任何输出。

我在模板的顶部添加了一个println(table_of_contents)语句,以确保映射确实有数据,并打印出:

{models.Chapter@1=BeanList size[4] hasMoreRows[false] list[models.Section@1, models.Section@2, models.Section@3, models.Section@4], models.Chapter@2=BeanList size[0] hasMoreRows[false] list[]}

也许我需要使用命令式风格?

更新:

仍在处理这个问题……编译出现了变化,但没有输出。

<dl>
@table_of_contents.foreach{case(a, b) => {
    <dt>
        @a.title
    </dt>
        @displaySections(b)
}}
</dl>

...

@displaySections(sections: List[Section]) = {
  @for(a_section <- sections) {
        <dd>@a_section.title</li>
  }
}

3
жҲ‘жІЎжңүеӨӘеӨҡPlayзҡ„з»ҸйӘҢпјҢдҪҶжҳҜиҝҷжҳҜеӣ дёәforeachзҡ„иҝ”еӣһзұ»еһӢжҳҜUnitеҗ—пјҹдҪ иҜ•иҝҮдҪҝз”Ёmapеҗ—пјҹ - Gareth
@Gareth 我认为你是对的,我将循环改为使用 for() 语法而不是调用 foreach() 方法,然后它就起作用了。 - wfbarksdale
3个回答

19

简而言之

到目前为止,@wbarksdale@PlexQ@Daniel C. Sobral在评论中给出的回答已足够解决本文所述的问题。

但是这些回答缺少了对最初使用foreach代码不起作用的真正解释。

这是因为foreach返回的是Unit,导致它不能正常工作。

Play框架概念

让我快速介绍一下模板的工作原理。

Play Framework 2默认提供的Scala模板系统确实建立在FP概念之上,因此它使用了很多不可变结构等内容。

此外,这样的 Scala 模板(例如 myTemplate.scala.html)将编译成一个常规的 Scala object,其中有一个称为 apply 的方法。后者使我们能够使用一些参数(在模板的第一行声明的参数)将对象作为函数调用

这个 object 也依赖于像 BaseScalaTemplate 这样的结构,该结构是使用输出格式化程序(Html)构建的。这个格式化程序将能够接受一些东西(比如 StringUnitSeq[Int]Map[A,B] 等),并将其呈现为 HTML 代码。

在使用 BaseScalaTemplate_display_ 方法时进行格式化,该方法返回一个格式化输出的实例。这个 display 方法将在编译后的 .scala.html 文件中,在对象的 apply 方法体中调用。

所以,正文可能以这样结束:
def apply/*1.2*/(testMap:scala.collection.immutable.Map[String, Int]):play.api.templates.Html = 
  _display_ {
    Seq[Any](
      _display_(
        Seq[Any](
          /*3.2*/testMap/*3.9*/.map/*3.13*/ { e =>
            _display_(Seq[Any](_display_(Seq[Any](/*5.3*/e))))
          }
        )
      )
    )
  }

看到了吗?_display_调用没有改变任何东西,但是它们被组合在一起,使得应用本身将返回格式化代码(Html)的实例!

这给了我们线索...

是啊,啦啦啦...但是为什么?

在关于Play内部的那些闪电般的介绍之后,我们现在可以解决真正的问题:为什么在问题帖子中提供的惯用Scala代码不起作用...也就是说根本没有输出任何内容。

很简单,当在Map上使用foreach时,您确实正在循环处理项目并将其适配为Html。但是这些计算无法被模板系统使用,因为它们被包含在foreach的循环中。也就是说,当需要对序列中的每个项目进行副作用时,必须使用foreach...并在完成后返回Unit

由于模板系统将尝试在给定的 Map 上“_display_”foreach 的结果,因此它将仅呈现/格式化 Unit ,从而导致空的 String !总之,只需使用返回包含适应的项目(即 Html 实例)的新序列的 map 即可。嗯,那 for 呢?是的,你说得对...根据已经说过的,为什么那些提出使用 for 循环的答案可行,因为如果没有产生值, for 等效于< code> foreach !(引入 yield 会导致类似于 map 的行为)
答案在代码中...模板编译器将在for的主体前面添加yield关键字--请查看这里。:-D Et voilà,它也可以工作,因为在for的主体中生成的内容将附加到返回的序列后。

这个回答中的链接是404,而我仍然在疑惑在Play框架中如何使用For、Foreach或Map。在模板中包含循环以输出所需的结构非常麻烦。 - Aj Otto

19

我想到的解决方案看起来像这样。基本上它避免使用函数式编程,目前我还可以接受,但我仍然非常希望看到一个使用Scala函数式风格的可行解决方案。

<dl>
@for((key, value) <- table_of_contents) {
    <dt>
        @key.getTitle
    </dt>
        @displaySections(value)
}
</dl>

@displaySections(sections: List[Section]) = {
  @for(a_section <- sections) {
        <dd>@a_section.getTitle</li>
  }
}

4
尝试使用@table_of_contents.map { case (key, value) =>。 说明:这是一段代码,使用Scala语言中的高阶函数map对一个名为table_of_contents的数据结构进行操作。其中,大括号内部的内容是一个匿名函数,它接收一个由键值对组成的元组作为输入,并返回一个新的元组。 - Daniel C. Sobral
1
我不记得看到过这样的拆解方式,使用部分函数,真是巧妙。Scala 能做到的事情仍然让我感到惊讶。 - PlexQ

8

Scala的Play框架非常好地利用了Scala的函数式特性。将其更改为返回元素的映射,并且它应该可以工作。

<dl>
@table_of_contents.map( case(k,v) => {
    <dt>
        @k.title
    </dt>
    @v.map { section =>
        <dd>
            @section.title
        </dd>
    }
})
</dl>

根据上面的建议,通过这个案例,它将其转换为一个部分函数,非常好地实现了我们想要的功能!


谢谢@PlexQ,我在@e._2.map { section =>这一行上遇到了一个错误expected start of definition。我之前并没有太多接触Scala,但自从发布这个帖子以来,我已经开始学习它,并对其表达能力感到惊讶。真的很酷! - wfbarksdale
1
我在 case(k, v) 部分遇到了非法的表达式开头,抱歉 :( 哈哈 - wfbarksdale
尝试将大括号移动到case关键字之前,我实际测试过这个方法,它是有效的,所以我知道这里的代码非常接近正确。显然,它已经被适应到了你特定的情况。 - PlexQ
我已经编辑了解决方案,使其能够正常工作。 - Dhruv Kapoor
@wbarksdale:我也遇到了这个问题,通过将括号替换为花括号来解决。 - Kiara Grouwstra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接