2

这是我为 Rectangle 类编写的代码。

    class Rectangle (l: Double, w: Double) {
        require (l > 0, w > 0)
        val length = l
        val width = w
        def this (l: Double) = this (l, l)
        def setDimensions (l: Double, w: Double) = new Rectangle (l, w)
        def setLength (l: Double) = new Rectangle (l, width)
        def setWidth (w: Double) = new Rectangle (length, w)
    }

我的问题是如何在 Scala 中编写以下函数(独立于 Rectangle 类):

  1. 给定长度和宽度计算矩形的面积
  2. 给定长度和面积计算矩形的宽度
  3. 给定宽度和面积计算矩形的长度
  4. 给定矩形对象显示长度、宽度和面积

在阅读本文的以下段落后出现了这个问题:

函数式语言的名字来源于程序应该像数学函数一样运行的概念。换句话说,给定一组输入,一个函数应该总是返回相同的输出。这不仅意味着每个函数都必须返回一个值,而且函数在从一次调用到下一次调用的过程中本质上必须不携带任何内在状态。这种内在的无状态概念,在默认情况下延续到函数/对象世界意味着不可变对象,这也是函数式语言被誉为疯狂并发世界的伟大救世主的很大一部分原因。

请注意,作为 Scala 初学者,我正在尝试掌握其中的 FP 部分。

4

3 回答 3

1

这是一个例子:

case class Rectangle(length: Double, width: Double) {
    require (length > 0, width > 0)

    lazy val area = length * width
    override def toString = s"length: $length, width: $width, area: $area"
}

object Rectangle {
    def fromLength(length: Double) = Rectangle(length, length)
    def fromLengthArea(length: Double, area: Double) = Rectangle(length, area / length)
    def fromWidthArea(width: Double, area: Double) = Rectangle(area / width, width)
    def show(rect: Rectangle) = println(rect)
}

// Usage

Rectangle show Rectangle(2, 3)
Rectangle show Rectangle.fromLength(2)
Rectangle show Rectangle.fromLengthArea(2, 6)
Rectangle show Rectangle.fromWidthArea(3, 6)

我可以建议您始终在适当的情况下使用案例类,尤其是对于Rectangle.

show方法需要打印结果,所以在这种情况下你不能避免副作用。换句话说,这个函数不是引用透明的。Rectangle实际上,可以认为整个构造函数的引用不是透明的,因为您使用的是require. 您可以通过确保Rectangle始终在类之外的某处接收正确的值来避免这种情况,但是当Rectangle由于验证错误而无法创建类时,您还需要返回一些东西。您可以Option为此目的使用类。这是一个小例子:

case class Rectangle private (length: Double, width: Double) {
    lazy val area = length * width
    override def toString = s"length: $length, width: $width, area: $area"
}

object Rectangle {
    def fromLengthWidth(length: Double, width: Double) = 
        validating(length, width)(new Rectangle(length, width))

    def fromLength(length: Double) = validating(length)  {
        new Rectangle(length, length)
    }

    def fromLengthArea(length: Double, area: Double) = validating(length, area) {
        new Rectangle(length, area / length)
    }

    def fromWidthArea(width: Double, area: Double) = validating(width, area) {
        new Rectangle(area / width, width)
    }

    def show(rect: Option[Rectangle]) = println(rect getOrElse "Invalid Rectangle!!!")

    private def validating[R](values: Double*)(fn: => R) = 
        if (values forall (_ > 0)) Some(fn)
        else None   
}

Rectangle show Rectangle.fromLengthWidth(2, 3)
Rectangle show Rectangle.fromLength(0)

// prints:
//    length: 2.0, width: 3.0, area: 6.0
//    Invalid Rectangle!!!

如您所见,我将构造函数设为私有并在伴随对象中移动验证(它可以访问具有相同名称的类的私有成员)。所以你不能创建无效的矩形。但重要的一点是,即使您提供了损坏的长度,您仍然会收到一些东西(在这种情况下,它是None对象,它是类的实例和子Option类)。

area在类中添加了方法,但是您当然可以编写一个独立的方法或函数来计算面积:

def area(rect: Rectangle) = rect.length * rect.width

或者

val area = (rect: Rectangle) => rect.length * rect.width

我希望这能帮助你理解这个话题。如果仍然不清楚(或者我的回答没有涵盖您真正想知道的内容),请不要犹豫并发表评论。

于 2012-06-08T18:34:09.630 回答
1

这是您描述的功能的实现:

def area(length: Double, width: Double) = length * width
def length(width: Double, area: Double) = if (width > 0) area / width else 0
def width(length: Double, area: Double) = if (length > 0) area / length else 0
def show(rect: Rectangle) = 
    println(rect.length + ", " + rect.width + ", " + area(rect.length, rect.width))
于 2012-06-08T19:13:30.313 回答
1

我很好奇如何用类型类来做到这一点,所以我想出了这个版本,它甚至不允许你编译带有无效矩形的代码。我敢打赌这可以做得更干净一些,但这是我快速整理的:

trait LengthCalc[-A] {
  def length(x: A): Double
}

trait WidthCalc[-A] {
  def width(x: A): Double
}

trait AreaCalc[-A] {
  def area(x: A): Double
}

case class Rectangle[A <: Option[Double], B <: Option[Double], C <: Option[Double]](lengthOpt: A = None, widthOpt: B = None, areaOpt: C = None)
(implicit lengthCalc: LengthCalc[Rectangle[A,B,C]], widthCalc: WidthCalc[Rectangle[A,B,C]], areaCalc: AreaCalc[Rectangle[A,B,C]]) {
  lazy val length = lengthCalc.length(this)
  lazy val width = widthCalc.width(this)
  lazy val area = areaCalc.area(this)
}

implicit object RectLengthCalcFromLength extends LengthCalc[Rectangle[Some[Double], _ <: Option[Double], _ <: Option[Double]]] {
  def length(x: Rectangle[Some[Double], _ <: Option[Double], _ <: Option[Double]]) = x.lengthOpt.get
}

implicit object RectLengthCalcFromWidthAndArea extends LengthCalc[Rectangle[None.type, Some[Double], Some[Double]]] {
  def length(x: Rectangle[None.type, Some[Double], Some[Double]]) = (for {
    area <- x.areaOpt
    width <- x.widthOpt
  } yield (area / width)).get
}

implicit object RectWidthFromWidth extends WidthCalc[Rectangle[_ <: Option[Double], Some[Double], _ <: Option[Double]]] {
  def width(x: Rectangle[_ <: Option[Double], Some[Double], _ <: Option[Double]]) = x.widthOpt.get
}

implicit object RectWidthFromLengthAndArea extends WidthCalc[Rectangle[Some[Double], None.type, Some[Double]]] {
  def width(x: Rectangle[Some[Double], None.type, Some[Double]]) = (for {
    area <- x.areaOpt
    length <- x.lengthOpt
  } yield (area / length)).get
}

implicit object RectAreaFromArea extends AreaCalc[Rectangle[_ <: Option[Double], _ <: Option[Double], Some[Double]]] {
  def area(x: Rectangle[_ <: Option[Double], _ <: Option[Double], Some[Double]]) = {
    x.areaOpt.get
  }
}

implicit object RectAreaFromLengthAndWidth extends AreaCalc[Rectangle[Some[Double], Some[Double], None.type]] {
  def area(x: Rectangle[Some[Double], Some[Double], None.type]) = (for {
    width <- x.widthOpt
    length <- x.lengthOpt
  } yield (width * length)).get
}

以下是一些示例调用:

scala> Rectangle(Some(3.),None,Some(4.))
res8: Rectangle[Some[Double],None.type,Some[Double]] = Rectangle(Some(3.0),None,Some(4.0))

scala> res8.width
res9: Double = 1.3333333333333333


scala> Rectangle(Some(3.),None,None)
<console>:25: error: could not find implicit value for parameter widthCalc: WidthCalc[Rectangle[Some[Double],None.type,None.type]]
          Rectangle(Some(3.),None,None)


scala> Rectangle(None, Some(8.), Some(64.))
res10: Rectangle[None.type,Some[Double],Some[Double]] = Rectangle(None,Some(8.0),Some(64.0))

scala> res10.length
res11: Double = 8.0
于 2012-06-08T20:11:15.733 回答