понедельник, 6 июля 2009 г.

Простое DSL на Scala

Недавно я начал использовать Scala для одного из моих домашних хобби проектов. Ранее я слышал, что Scala позволяет писать код таким образом, что он выглядит как DSL встроенный в саму Scala'у. Решил попробовать использовать эту фичу. Мне было необходимо придумать красивое API для шедулинга сообщений актерам (actors). Вот что из этого получилось:

final class TimeSpec[T] (number : Long, action : Long => T) {
def nanoseconds = action (number)
def microseconds = action (number * 1000L)
def miliseconds = action (number * 1000L * 1000L)
def seconds = action (number * 1000L * 1000L * 1000L)
def minutes = action (number * 1000L * 1000L * 1000L * 60L)
def hours = action (number * 1000L * 1000L * 1000L * 60L * 60L)
def days = action (number * 1000L * 1000L * 1000L * 60L * 60L * 24L)
}

final class Trigger (actor : MyActor, payload : Any) {
def in (number : Long) = new TimeSpec[Unit] (number, scheduleIn)
def every (number : Long) = new TimeSpec[Unit] (number, scheduleEvery)

private def scheduleIn (nanos : Long) = Scheduler.inNano (actor, payload, nanos)
private def scheduleEvery (nanos : Long) = Scheduler.everyNano (actor, payload, nanos)
}

final class ActorSchedule (actor : MyActor) {
def payload (payload : Any) = new Trigger (actor, payload)
}

trait MyActor ..... {
protected val schedule = new ActorSchedule (this)
....
}


В результате получил возможность писать вот такой код:

object TestActor extends MyActor {
schedule payload `Hi in 10 nanoseconds

schedule payload `HowAreYou every 5 seconds

schedule payload `Bye in 5 days

def act () = {
case `Hi => println ("Hello!")
case `HowAreYou => println ("I am fine")
case `Bye => println ("Bye-bye")
}
}

Идея в том, что schedule - это объект класса ActorSchedule. Этот класс предоставляет один метод payload имеющий один аргумент (собственно то, что будет послано актеру). В действительности "schedule payload `Hi" это вызов "schedule.payload(`Hi)". Этот вызов создаст объект класса Trigger. У класса Trigger есть два публичных метода in(Long) и every(Long). Так как эти методы не могут ничего полезного сделать до тех пор пока не будет известна единица измерения времени, то эти методы создают объекты TimeSpec передавая им в качестве параметра собственный метод, который будет вызван с количеством наносекнуд одним из методов класса TimeSpec.

Как мне кажется получился достаточно простой и удобный API без особых усилий.

Комментариев нет:

Отправить комментарий