Класс 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)
,