Класс Scala, обертывающий частично примененный конструктор - как использовать его для создания методов API?

Я пытаюсь создать простой API для работы с интервалами часов. (Я знаю о времени йода, и я не пытаюсь его изобрести заново. Это скорее упражнение).

То, что я хотел бы достичь, это:

(1)

assert(from("20:30").to("20:50") == Interval("20:30", "20:50") )
//same thing, but without implicit defs
assert(from(Time(20, 30)).to(Time(20, 50)) == Interval(Time(20, 30), Time(20, 50)))


(2)

assert(from("20:30").forMinutes(10) == from("20:30").to("20:40"))


Мне удалось реализовать (1), как это: (игнорируя toString, Упорядоченная черта, ASO)

case class Time(hour: Int, minute: Int)

case class Interval(start: Time, end: Time)

object Interval {
   case class HalfInterval(half: Time => Interval) {
      def to(time: Time): Interval = half(time)
      def forMinutes(minutes: Int): Interval = ??? 
    }
   def from(start: Time): HalfInterval = HalfInterval(Interval(start, _))
}

object Time {
  def apply(hourMinute: String): Time = {
    val tries = hourMinute.split(":").map(s => Try(s.toInt))
    tries match {
      case Array(Success(hour), Success(minute)) => Time(hour, minute)
      case _ => throw new IllegalArgumentException
    }
  }
  implicit def stringToTime(hourMinute: String) = Time(hourMinute)
}


Тем не менее, я не знаю, как реализовать (2) (то есть: Interval.forMinutes).

def forMinutes(minutes: Int): Interval = {
  val time = ?? // Based on what Time object could I construct a new Time object here?
  half(time)
}

Не могу обернуть голову вокруг этого.
Эта обертка "HalfInterval" над Time => Interval имеет смысл вообще?
Я разработал это опытным путем - просто чтобы from(..).to(..) призывает работать по плану, а не с какой-то функционально-концептуальной моделью.
Есть ли лучший способ достичь этого API?

Спасибо

2 ответа

Решение

Вот что я бы сделал:

object Interval {
   case class HalfInterval(start: Time) {
      def to(end: Time): Interval = Interval(start, end)
      def forMinutes(minutes: Int): Interval = to(getEnd(start, minutes))
      private def getEnd(start: Time, minutes: Int) = ??? 
    }
   def from(start: Time): HalfInterval = HalfInterval(start)
}

getEnd () добавляет параметр minutes к start.minute, разделенному на 60, добавляет результат к start.hours и остальной части деления в минуты, и там вы строите время окончания. (Тогда, возможно, сделайте часовой модуль 24 на случай, если вы перейдете на следующий день).

Изменить: HalfInterval должен быть класс значения, но не беспокойтесь об этом.

Я согласен с Лучано, что ваш HalfInterval немного из коробки.

Но поскольку инженерам нравятся вещи из коробки, где вы должны собрать их, прежде чем вы сможете поиграть с ними, здесь есть другой угол.

Ваш HalfInterval говорит, дай мне Interval фабрика, и я передам вам свидетельство о Interval это требуется.

Здесь я изменил тип параметра на Любой, но в этом случае это может быть Either[Int, Time]где int означает минуты в будущем, а время - время окончания. В целом, это может быть признак маркера, более безопасный, чем любой.

Чтобы ответить на ваш вопрос, откуда берется время начала, вы видите, что закрытие в from захватывает start,

import scala.language.implicitConversions
import util._

case class Time(hour: Int, minute: Int) {
  def +(delta: Int) = Time(hour, minute + delta) // TODO overflow minutes
}

case class Interval(start: Time, end: Time)

object Interval {
   case class HalfInterval(half: Any => Interval) {
      def to(time: Time): Interval = half(time)
      def forMinutes(minutes: Int): Interval = half(minutes)
    }
   def from(start: Time) = HalfInterval((arg: Any) => arg match {
     case delta: Int => Interval(start, start + delta)
     case end: Time  => Interval(start, end)
     case _          => throw new IllegalArgumentException
   })
}

object Time {
  def apply(hourMinute: String): Time = {
    val tries = hourMinute.split(":").map(s => Try(s.toInt))
    tries match {
      case Array(Success(hour), Success(minute)) => Time(hour, minute)
      case _ => throw new IllegalArgumentException
    }
  }
  implicit def stringToTime(hourMinute: String) = Time(hourMinute)
}

object Test extends App {
  import Interval._
  assert(from("20:30").to("20:50") == Interval("20:30", "20:50") )
  assert(from("20:30").forMinutes(10) == from("20:30").to("20:40"))
}

Перевернутая формулировка, где подписи более странные, но код имеет больше смысла:

object Interval {
   case class HalfInterval(f: (Time=>Time) => Interval) {
      def to(end: Time): Interval = f(_ => end)
      def forMinutes(minutes: Int): Interval = f(_ + minutes)
    }
   def from(start: Time) = HalfInterval((end: Time=>Time) => Interval(start, end(start)))
}

И то и другое to а также forMinutes знать, как сделать время окончания с самого начала.

Это облегчает добавление, например, until(end: Time), forSeconds(secs: Int),

Другие вопросы по тегам