首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >重构/布局functional Scala

重构/布局functional Scala
EN

Stack Overflow用户
提问于 2010-11-22 00:30:56
回答 4查看 578关注 0票数 12

这一条..。

代码语言:javascript
复制
 Console.println(io.Source.fromFile("names.txt").getLines.mkString.split(",").map{x:String => x.slice(1, x.length -1)}.sortBy { x => x}.zipWithIndex.map{t =>{ (t._2 +1)*(t._1.map{_.toChar - "A"(0).toChar + 1}.sum)}}.sum);

..。是我对Project Euler problem 22的解决方案。它似乎起作用了,而且是用(我尝试的)函数式风格编写的。

这个例子有点极端,但我的问题更一般--你喜欢如何编写/format/comment函数风格的代码?函数式方法似乎鼓励一系列方法调用,我发现这可能会变得不可读,而且没有留下明显的注释。

此外,当我编写过程代码时,我发现我编写的方法很小,每个方法都有一个目的和有意义的名称。当我编写函数式代码时,我似乎养成了一种习惯,它生成的代码行有点像上面的代码,其中(对我来说)含义很难破解-而且单独的计算也很难在其他地方重用。我在网上看到的相当多的函数代码示例(对我来说)同样简洁而晦涩难懂。

我应该做些什么?为计算的每个部分编写小函数,并在当前上下文中使用有意义的名称?(即使它们只是map的包装器,比如说?)

对于我给出的例子,有什么更好的方法来编写和展示它呢?

(像所有的风格问题一样,这个问题是主观的。然而,没有理由让它变得争论不休!)

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2010-11-22 02:44:59

整理它的一个简单的第一次尝试是删除前导Console.、尾随的;和显式的:String类型-所有这些都是不必要的-添加一些缩进并导入io.Source:

代码语言:javascript
复制
import io.Source
println(
  Source.fromFile("names.txt").getLines.mkString.split(",").map{
    x => x.slice(1, x.length -1)
  }.sortBy {x => x}.zipWithIndex.map{
    t =>{ (t._2 +1)*(t._1.map{_.toChar - "A"(0).toChar + 1}.sum)}
  }.sum
)

下一步是稍微清理一下,在元组列表和identity而不是x=>x上映射时使用模式匹配。字符也不需要toChar,单引号可以用来表示字符字面量。

代码语言:javascript
复制
import io.Source
println(
  Source.fromFile("names.txt").getLines.mkString.split(",").map {
    x => x.slice(1, x.length -1)
  }.sortBy(identity).zipWithIndex.map {
    case (v, idx) =>{ (idx+1)*(v.map{_ - 'A' + 1}.sum)}
  }.sum
)

更多的更改也有助于使代码的意图变得更加清晰:

代码语言:javascript
复制
import io.Source
println(
  Source.fromFile("names.txt").getLines.mkString.split(",")
  .map { _.stripPrefix("\"").stripSuffix("\"") }
  .sortBy(identity)
  .map { _.map{_ - 'A' + 1}.sum }
  .zipWithIndex
  .map { case (v, idx) => (idx+1) * v }
  .sum
)

下一步,让它变得更“功能性”,就是把它分解成“函数”(偷偷摸摸,哈?)。理想情况下,每个函数都应该有一个明确表达其用途的名称,而且应该很短(目标是单个表达式,所以不需要大括号):

代码语言:javascript
复制
import io.Source

def unquote(s:String) = s.stripPrefix("\"").stripSuffix("\"")

def wordsFrom(fname:String) =
  Source.fromFile(fname).getLines.mkString.split(",").map(unquote)

def letterPos(c:Char) = c - 'A' + 1

println(
  wordsFrom("names.txt")
  .sortBy(identity)
  .map { _.map(letterPos).sum }
  .zipWithIndex
  .map { case (v, idx) => (idx+1) * v }
  .sum
)

wordsFrom是一个明显的一行程序,但为了在stackOverflow上更容易地格式化,我将其拆分

票数 19
EN

Stack Overflow用户

发布于 2010-11-22 02:14:14

以下是我认为可能是更好的布局方式:

代码语言:javascript
复制
Console.println(
    io.Source.fromFile("names.txt")
    .getLines.mkString.split(",")
    .map{x:String => x.slice(1, x.length -1)}
    .sortBy { x => x}
    .zipWithIndex
    .map{t =>{ (t._2 +1)*(t._1.map{_.toChar - "A"(0).toChar + 1}.sum)}}
    .sum); 

我觉得在我的大脑深处有一些算法可以在水平和垂直空间之间做出代码布局权衡的决定,但我似乎没有直接的访问权来使我能够清楚地表达出来。:)

关于引入名称而不是使用lambda,我认为我通常会做的是,如果我想要添加一个简短的注释来描述代码的意图,但一个好的标识符名称也可以做到这一点,那么我可能会将一次性的lambda放入命名函数中,以便获得标识符名称的可读性优势。在我看来,上面唯一一行包含toChar调用的行看起来像是候选人。(为了清楚起见,我考虑了map中的lambda (一部分),但map调用本身。)或者,垂直空白的引入为您提供了挂起//comment的位置,这是引入标识符名的替代方案。

(免责声明:我不写Scala,所以如果我说的任何话与样式约定冲突,请忽略me :),但我想这些建议大多是语言不可知的。)

票数 4
EN

Stack Overflow用户

发布于 2010-11-23 04:10:45

严格地说,在不做任何结构更改的情况下,如何格式化代码,我会这样做:

代码语言:javascript
复制
Console println (
  (
    io.Source
    fromFile "names.txt"
    getLines ()
    mkString ""
    split ","
    map (x => x.slice(1, x.length - 1))
    sortBy (x => x)
    zipWithIndex
  )
  map (t => (t._2 + 1) * (t._1 map (_.toChar - "A"(0).toChar + 1) sum) )
  sum
)

或者,为了绕过无参数方法,我会这样做:

代码语言:javascript
复制
Console println (
  io.Source
  .fromFile("names.txt")
  .getLines
  .mkString
  .split(",")
  .map(x => x.slice(1, x.length - 1))
  .sortBy(x => x)
  .zipWithIndex
  .map(t => (t._2 + 1) * (t._1 map (_.toChar - "A"(0).toChar + 1) sum) )
  .sum
)

请注意,有足够的空间来发表评论,但一般来说,正在做的事情通常是清晰的。不习惯它的人有时可能会在中途迷失方向,没有变量来跟踪转换后的值的含义/类型。

现在,我会做一些不同的事情:

代码语言:javascript
复制
println ( // instead of Console println
  Source // put import elsewhere
  .fromFile("names.txt")
  .mkString // Unless you want to get rid of /n, which is unnecessary here
  .split(",")
  .map(x => x.slice(1, x.length - 1))
  .sorted // instead of sortBy
  .zipWithIndex
  .map { // use { only for multiple statements and, as in this case, pattern matching
    case (c, index) => (index + 1) * (c map (_ - 'A' + 1) sum) // chars are chars
  }
  .sum
)

我也不会在同一步骤中做加法和乘法,所以:

代码语言:javascript
复制
  .sorted
  .map(_ map (_ - 'A' + 1) sum)
  .zipWithIndex
  .map { case (av, index) => av * (index + 1) }
  .sum

最后,我不太喜欢调整字符串的大小,所以我可能会求助于正则表达式。添加一点重构,这是我可能会写的东西:

代码语言:javascript
复制
  import scala.io.Source
  def names = Source fromFile "names.txt" mkString

  def wordExtractor = """"(.*?)"""".r
  def words = for {
    m <- wordExtractor findAllIn names matchData
  } yield m group 1

  def alphabeticValue(s: String) = s map (_ - 'A' + 1) sum
  def wordsAV = words.toList.sorted map alphabeticValue

  def multByIndex(t: (Int, Int)) = t match {
    case (av, index) => av * (index + 1)
  }
  def wordsAVByIndex = wordsAV.zipWithIndex map multByIndex

  println(wordsAVByIndex.sum)

编辑

下一步将是重命名重构--选择更好地表达代码每个部分所做的事情的名称。Ken在评论中建议了更好的名称,而我将它们适用于更多的变体(它也很好地展示了更好的命名如何提高可读性)。

代码语言:javascript
复制
import scala.io.Source
def rawData = Source fromFile "names.txt" mkString

// I'd rather write "match" than "m" in the next snippet, but
// the former is a keyword in Scala, so "m" has become more
// common in my code than "i". Also, make the return type of
// getWordsOf clear, because iterators can be tricky.
// Returning a list, however, makes a much less cleaner
// definition.

def wordExtractor = """"(.*?)"""".r
def getWordsOf(input: String): Iterator[String] = for {
  m <- wordExtractor findAllIn input matchData
} yield m group 1
def wordList = getWordsOf(rawData).toList

// I stole letterPosition from Kevin's solution. There, I said it. :-)

def letterPosition(c: Char) = c.toUpper - 'A' + 1 // .toUpper isn't necessary
def alphabeticValueOfWord(word: String) = word map letterPosition sum
def alphabeticValues = wordList.sorted map alphabeticValueOfWord

// I don't like multAVByIndex, but I haven't decided on a better
// option yet. I'm not very fond of declaring methods that return
// functions either, but I find that better than explicitly handling
// tuples (oh, for the day tuples/arguments are unified!).

def multAVByIndex = (alphabeticValue: Int, index: Int) => 
  alphabeticValue * (index + 1)
def scores = alphabeticValues.zipWithIndex map multAVByIndex.tupled

println(scores sum)
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4238902

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档