как реализовать эту концепцию будущего / состояния как монаду в scala - PullRequest
7 голосов
/ 13 ноября 2011

Я пытаюсь реализовать контейнер для результата матча (как в спорте), чтобы я мог создавать матчи между победителями других матчей.Эта концепция близка к будущей монаде, так как содержит определенное значение, а также близка к монаде состояния, поскольку скрывает изменение состояния.Будучи в основном новичком в этой теме, я реализовал начальную версию в scala, которая, несомненно, является улучшенной.Я добавил метод get, но я не уверен, что это хорошая идея, и пока единственным способом создания значения будет Unknown(null), что не так элегантно, как я надеялся.Как вы думаете, что я мог бы сделать, чтобы улучшить этот дизайн?

case class Unknown[T](t : T) {
  private var value : Option[T] = Option(t)
  private var applicatives: List[T => Unit] = Nil

  def set(t: T) {
    if (known) {
      value = Option(t)
      applicatives.foreach(f => f(t))
      applicatives = Nil
    } else {
      throw new IllegalStateException
    }
  }

  def get : T = value.get

  def apply(f: T => Unit) = value match {
    case Some(x) => f(x);
    case None => applicatives ::= f
  }

  def known = value == None
}

ОБНОВЛЕНИЕ : пример использования текущей реализации следует

case class Match(val home: Unknown[Team], val visit: Unknown[Team], val result: Unknown[(Int, Int)]) {
  val winner: Unknown[Team] = Unknown(null)
  val loser: Unknown[Team] = Unknown(null)

  result.apply(result => {
    if (result._1 > result._2) {
      home.apply(t => winner.set(t))
      visit.apply(t => loser.set(t))
    } else {
      home.apply(t => loser.set(t))
      visit.apply(t => winner.set(t))
    }
  })
}

и тестовый фрагмент:

val definedUnplayedMatch = Match(Unknown(Team("A")), Unknown(Team("B")), Unknown(null));
val definedPlayedMatch = Match(Unknown(Team("D")), Unknown(Team("E")), Unknown((1,0)));
val undefinedUnplayedMatch = Match(Unknown(null), Unknown(null), Unknown(null));

definedUnplayedMatch.winner.apply(undefinedUnplayedMatch.home.set(_))
definedPlayedMatch.winner.apply(undefinedUnplayedMatch.visit.set(_))
undefinedUnplayedMatch.result.set((3,1))
definedUnplayedMatch.result.set((2,4))
undefinedUnplayedMatch.winner.get must be equalTo(Team("B")); 
undefinedUnplayedMatch.loser.get must be equalTo(Team("D"));

ОБНОВЛЕНИЕ - ТЕКУЩАЯ ИДЕЯ: У меня не было много времени поработать над этим, потому что у меня сломался ноутбук, но я подумал, что было бы полезно написать монадупока что для тех, кто интересуется:

sealed abstract class Determine[+A] {
  def map[B](f: A => B): Determine[B]
  def flatMap[B](f: A => Determine[B]): Determine[B]
  def filter(p: A => Boolean): Determine[A]
  def foreach(b: A => Unit): Unit
}
final case class Known[+A](value: A) extends Determine[A] {
  def map[B](f: A => B): Determine[B] = Known(f(value))
  def flatMap[B](f: A => Determine[B]): Determine[B] = f(value)
  def filter(p: A => Boolean): Determine[A] = if (p(value)) this else Unknown
  def foreach(b: A => Unit): Unit = b(value)
}
final case class TBD[A](definer: () => A) extends Determine[A] {
  private var value: A = _

  def map[B](f: A => B): Determine[B] = {
    def newDefiner(): B = {
      f(cachedDefiner())
    }
    TBD[B](newDefiner)
  }

  def flatMap[B](f: A => Determine[B]): Determine[B] = {
    f(cachedDefiner())
  }

  def filter(p: A => Boolean): Determine[A] = {
    if (p(cachedDefiner()))
      this
    else
      Unknown
  }

  def foreach(b: A => Unit): Unit = {
    b(cachedDefiner())
  }

  private def cachedDefiner(): A = {
    if (value == null)
      value = definer()
    value
  }
}
case object Unknown extends Determine[Nothing] {
  def map[B](f: Nothing => B): Determine[B] = this
  def flatMap[B](f: Nothing => Determine[B]): Determine[B] = this
  def filter(p: Nothing => Boolean): Determine[Nothing] = this
  def foreach(b: Nothing => Unit): Unit = {}
}

Я избавился от set & get, и теперь класс TBD получает вместо него функцию, которая определит, предоставит значение или значение null, если оно еще не определено.Эта идея прекрасно работает для метода map, но остальные методы имеют незначительные ошибки.

1 Ответ

2 голосов
/ 18 ноября 2011

Для простого подхода вам не нужны монады, достаточно частичного применения:

//some utilities
type Score=(Int,Int)
case class MatchResult[Team](winner:Team,loser:Team)

//assume no ties
def playMatch[Team](home:Team,away:Team)(score:Score)= 
  if (score._1>score._2) MatchResult(home,away) 
  else MatchResult(away,home)

//defined played match
val dpm= playMatch("D","E")(1,0)
//defined unplayed match, we'll apply the score later
val dum= playMatch("A","B")_

// a function that takes the dum score and applies it 
// to get a defined played match from  an undefined one
// still is a partial application of match because we don't have the final result yet
val uumWinner= { score:Score => playMatch (dpm.winner,dum(score).winner) _ }
val uumLoser= { score:Score => playMatch (dpm.loser,dum(score).loser)  _}

//apply the scores 
uumWinner (2,4)(3,1)
uumLoser (2,4)(0,1)


//scala> uumWinner (2,4)(3,1)
//res6: MatchResult[java.lang.String] = MatchResult(D,B)
//scala> uumLoser (2,4)(0,1)
//res7: MatchResult[java.lang.String] = MatchResult(A,E)

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

...