Tôi là một Scala n00b (nhưng có kinh nghiệm với các ngôn ngữ khác) và đang học ngôn ngữ như tôi tìm thấy thời gian - rất thích thú với nó cho đến nay!Phê bình mã Scala của tôi
Thông thường khi học một ngôn ngữ mới, điều đầu tiên tôi làm là thực hiện Conway's Game of Life, vì nó chỉ đủ phức tạp để mang lại ý thức tốt về ngôn ngữ, nhưng đủ nhỏ để có thể bắt kịp trong vài giờ (hầu hết trong số đó là chi tiêu đấu vật với cú pháp).
Anyhoo, đã trải qua bài tập này với Scala Tôi đã hy vọng các bậc thầy Scala ra có thể xem xét mã tôi đã kết thúc và cung cấp phản hồi về nó. Tôi đang theo đuổi bất cứ điều gì - cải tiến về thuật toán (đặc biệt là các giải pháp đồng thời!), Cải tiến phong cách, các API thay thế hoặc cấu trúc ngôn ngữ, ghê tởm ở độ dài tên chức năng của tôi - bất kỳ phản hồi nào bạn có, tôi rất muốn nghe nó!
Bạn sẽ có thể chạy tập lệnh sau thông qua scala GameOfLife.scala
- theo mặc định nó sẽ chạy một bảng 20x20 với một tàu lượn đơn trên đó - vui lòng thử nghiệm.
// CONWAY'S GAME OF LIFE (SCALA)
abstract class GameOfLifeBoard(val aliveCells : Set[Tuple2[Int, Int]])
{
// Executes a "time tick" - returns a new board containing the next generation
def tick : GameOfLifeBoard
// Is the board empty?
def empty : Boolean = aliveCells.size == 0
// Is the given cell alive?
protected def alive(cell : Tuple2[Int, Int]) : Boolean = aliveCells contains cell
// Is the given cell dead?
protected def dead(cell : Tuple2[Int, Int]) : Boolean = !alive(cell)
}
class InfiniteGameOfLifeBoard(aliveCells : Set[Tuple2[Int, Int]])
extends GameOfLifeBoard(aliveCells)
{
// Executes a "time tick" - returns a new board containing the next generation
override def tick : GameOfLifeBoard = new InfiniteGameOfLifeBoard(nextGeneration)
// The next generation of this board
protected def nextGeneration : Set[Tuple2[Int, Int]] = aliveCells flatMap neighbours filter shouldCellLiveInNextGeneration
// Should the given cell should live in the next generation?
protected def shouldCellLiveInNextGeneration(cell : Tuple2[Int, Int]) : Boolean = (alive(cell) && (numberOfAliveNeighbours(cell) == 2 || numberOfAliveNeighbours(cell) == 3)) ||
(dead(cell) && numberOfAliveNeighbours(cell) == 3)
// The number of alive neighbours for the given cell
protected def numberOfAliveNeighbours(cell : Tuple2[Int, Int]) : Int = aliveNeighbours(cell) size
// Returns the alive neighbours for the given cell
protected def aliveNeighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = aliveCells intersect neighbours(cell)
// Returns the coordinates of all of the neighbouring cells of the given cell
protected def neighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = Set((cell._1-1, cell._2-1), (cell._1, cell._2-1), (cell._1+1, cell._2-1),
(cell._1-1, cell._2), (cell._1+1, cell._2),
(cell._1-1, cell._2+1), (cell._1, cell._2+1), (cell._1+1, cell._2+1))
// Information on where the currently live cells are
protected def xVals = aliveCells map { cell => cell._1 }
protected def xMin = (xVals reduceLeft (_ min _)) - 1
protected def xMax = (xVals reduceLeft (_ max _)) + 1
protected def xRange = xMin until xMax + 1
protected def yVals = aliveCells map { cell => cell._2 }
protected def yMin = (yVals reduceLeft (_ min _)) - 1
protected def yMax = (yVals reduceLeft (_ max _)) + 1
protected def yRange = yMin until yMax + 1
// Returns a simple graphical representation of this board
override def toString : String =
{
var result = ""
for (y <- yRange)
{
for (x <- xRange)
{
if (alive (x,y)) result += "# "
else result += ". "
}
result += "\n"
}
result
}
// Equality stuff
override def equals(other : Any) : Boolean =
{
other match
{
case that : InfiniteGameOfLifeBoard => (that canEqual this) &&
that.aliveCells == this.aliveCells
case _ => false
}
}
def canEqual(other : Any) : Boolean = other.isInstanceOf[InfiniteGameOfLifeBoard]
override def hashCode = aliveCells.hashCode
}
class FiniteGameOfLifeBoard(val boardWidth : Int, val boardHeight : Int, aliveCells : Set[Tuple2[Int, Int]])
extends InfiniteGameOfLifeBoard(aliveCells)
{
override def tick : GameOfLifeBoard = new FiniteGameOfLifeBoard(boardWidth, boardHeight, nextGeneration)
// Returns the coordinates of all of the neighbouring cells of the given cell
override protected def neighbours(cell : Tuple2[Int, Int]) : Set[Tuple2[Int, Int]] = super.neighbours(cell) filter { cell => cell._1 >= 0 && cell._1 < boardWidth &&
cell._2 >= 0 && cell._2 < boardHeight }
// Information on where the currently live cells are
override protected def xRange = 0 until boardWidth
override protected def yRange = 0 until boardHeight
// Equality stuff
override def equals(other : Any) : Boolean =
{
other match
{
case that : FiniteGameOfLifeBoard => (that canEqual this) &&
that.boardWidth == this.boardWidth &&
that.boardHeight == this.boardHeight &&
that.aliveCells == this.aliveCells
case _ => false
}
}
override def canEqual(other : Any) : Boolean = other.isInstanceOf[FiniteGameOfLifeBoard]
override def hashCode : Int =
{
41 * (
41 * (
41 + super.hashCode
) + boardHeight.hashCode
) + boardWidth.hashCode
}
}
class GameOfLife(initialBoard: GameOfLifeBoard)
{
// Run the game of life until the board is empty or the exact same board is seen twice
// Important note: this method does NOT necessarily terminate!!
def go : Unit =
{
var currentBoard = initialBoard
var previousBoards = List[GameOfLifeBoard]()
while (!currentBoard.empty && !(previousBoards contains currentBoard))
{
print(27.toChar + "[2J") // ANSI: clear screen
print(27.toChar + "[;H") // ANSI: move cursor to top left corner of screen
println(currentBoard.toString)
Thread.sleep(75)
// Warning: unbounded list concatenation can result in OutOfMemoryExceptions ####TODO: replace with LRU bounded list
previousBoards = List(currentBoard) ::: previousBoards
currentBoard = currentBoard tick
}
// Print the final board
print(27.toChar + "[2J") // ANSI: clear screen
print(27.toChar + "[;H") // ANSI: move cursor to top left corner of screen
println(currentBoard.toString)
}
}
// Script starts here
val simple = Set((1,1))
val square = Set((4,4), (4,5), (5,4), (5,5))
val glider = Set((2,1), (3,2), (1,3), (2,3), (3,3))
val initialBoard = glider
(new GameOfLife(new FiniteGameOfLifeBoard(20, 20, initialBoard))).go
//(new GameOfLife(new InfiniteGameOfLifeBoard(initialBoard))).go
// COPYRIGHT PETER MONKS 2010
Một câu hỏi cụ thể: Tôi đã xóa các loại trả về từ tất cả các chức năng tại một điểm (Scala type inference ftw!), Nhưng mã thực sự khó đọc hơn. Có bất kỳ quy ước nào về thời điểm để lại các loại trả lại trong khi cho phép Scala tìm ra chúng (ngoài những trường hợp cần thiết)?
Đặt mở cú đúp trong cùng một dòng. Nếu bạn viết mã trong Java, hãy làm như vậy ở đó. – OscarRyz
Cảm ơn, nhưng tôi là một anh chàng kiểu ANSI-C kiểu cũ-skool, vì vậy tôi không thể thay đổi phong cách cú đúp/thụt đầu của mình sớm. ;-) – Peter
Đó là những gì tôi nghĩ :) Nhưng, bạn nói: * phê bình mã của tôi * :) Tôi nghĩ rằng đó giống như học một ngôn ngữ tự nhiên, bạn sẽ luôn có một giọng nước ngoài. – OscarRyz