Функциональный рисунок для двойного сгиба - PullRequest
7 голосов
/ 24 августа 2011

Пусть игрушечный класс Counter такой как:

class Counter private( val next: Int, val str2int: Map[String,Int] ) {
  def apply( str: String ): (Int,Counter)  = str2int get str match {
    case Some(i) => ( i, this )
    case None => ( next, new Counter( next+1, str2int + (str -> next) ) )
  }
}
object Counter {
  def apply() = new Counter( 0, Map() )
}

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

Затем я могу написать метод, который может преобразовывать последовательность строк в последовательность строк, обновляя отображение во время обхода. Первая реализация, которую я получил, - foldLeft:

def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
  strs.foldLeft( (Seq[Int](), counter) ) { (result, str) =>
    val (i, nextCounter) = result._2( str )
    ( result._1 :+ i, nextCounter )
  }

Это работает как задумано:

val ss = Seq( "foo", "bar", "baz", "foo", "baz" )
val is = toInt( ss, Counter() )._1
            //is == List(0, 1, 2, 0, 2)

Но я не очень доволен реализацией toInt. Проблема в том, что я складываюсь на двух разных значениях. Существует ли функциональный шаблон программирования для упрощения реализации?

Ответы [ 4 ]

5 голосов
/ 25 августа 2011

Шаблон, который вы ищете - это State монада:

import scalaz._
import Scalaz._

case class Counter(next: Int = 0, str2int: Map[String,Int] = Map()) {
  def apply( str: String ): (Counter, Int) = (str2int get str) fold (
    (this, _),
    (new Counter(next+1, str2int + (str -> next)), next)
  )}

type CounterState[A] = State[Counter, A]

def count(s: String): CounterState[Int] = state(_(s))

def toInt(strs: Seq[String]): CounterState[Seq[Int]] =
  strs.traverse[CounterState, Int](count)

Типовая аннотация там неудачная, и, возможно, ее можно как-то устранить.Во всяком случае, вот что из этого вышло:

scala> val ss = Seq( "foo", "bar", "baz", "foo", "baz" )
ss: Seq[java.lang.String] = List(foo, bar, baz, foo, baz)

scala> val is = toInt(ss) ! Counter()
is: Seq[Int] = List(0, 1, 2, 0, 2)
3 голосов
/ 25 августа 2011

Вы можете сделать получаемый фолд немного более приятным, выполнив более точное сопоставление с образцом:

strs.foldLeft((Seq[Int](), counter)) { case ((xs,counter), str) =>
  val (i, nextCounter) = counter(str)
  (xs :+ i, nextCounter)
}

И затем, если у вас определен оператор канала |>где-нибудь, и вы можете использовать псевдоним /: для foldLeft, вы можете сделать его

((Seq[Int](), counter) /: strs) { case ((xs,counter), str) =>
  counter(str) |> { case (i,nextCounter) => (xs +: i, nextCounter) }
}

, который, как только вы познакомитесь с синтаксисом, будет компактным и читабельным.

2 голосов
/ 24 августа 2011

Я думаю, что вы ищете государственную монаду.

1 голос
/ 25 августа 2011

Ничего плохого в складывании на два значения. Может быть немного улучшено:

import scalaz._
import Scalaz._

def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
  strs.foldLeft( (Seq[Int](), counter) ) { case ((xs, counter), str) =>
    counter(str).mapElements(xs :+ _, identity)
  }

Или, если хотите,

def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
  strs.foldLeft( (Seq[Int](), counter) ) { case ((xs, counter), str) =>
    (xs :+ (_: Int)) <-: counter(str)
  }

Или даже

def toInt( strs: Seq[String], counter: Counter ): ( Seq[Int], Counter ) =
  strs.foldLeft( (Seq[Int](), counter) ) { case (result, str) =>
    result.fold((xs, counter) =>
      counter(str).mapElements(xs :+ _, identity))
  }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...