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. 与えられた Rectangle オブジェクトは、長さ、幅、および面積を示します

この質問は、この記事の次の段落を読んだ後に発生しました。

関数型言語の名前は、プログラムが数学関数のように動作する必要があるという概念に由来しています。つまり、一連の入力が与えられた場合、関数は常に同じ出力を返す必要があります。これは、すべての関数が値を返さなければならないことを意味するだけでなく、関数は本質的に、ある呼び出しから次の呼び出しまで固有の状態を保持してはならないことを意味します。このステートレスの本質的な概念は、デフォルトで不変オブジェクトを意味する関数/オブジェクトの世界に引き継がれ、関数型言語が狂ったように並行した世界の偉大な救世主として歓迎されている理由の大部分を占めています。

Scala の初心者として、私はその FP 部分を把握しようとしていることに注意してください。

4

3 に答える 3

1

型クラスでこれを行う方法に興味があったので、無効なR​​ectangleを使用してコードをコンパイルすることさえできないこのバージョンを思いつきました。私はこれが少しきれいにできると思います、しかしこれは私がすぐにまとめたものです:

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 に答える
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

以下に例を示します。

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 に答える