Использование бесформенных записей для объединения произвольного количества функций, изменяющих состояние - PullRequest
0 голосов
/ 09 октября 2018

Я пытаюсь перенести combineReducers из Redux в Scala.Идея состоит в том, что каждая функция контролирует свою небольшую часть состояния, а combineReducers создает функцию, которая контролирует все состояние.Я не могу определить необходимую подпись для функции, которая должна работать примерно так:

sealed trait Event

case class Create() extends Event
case class Save() extends Event

object Reducers {
  def combineReducers[E, S1, S2](fTag: String, f: (E, S1) => S1, gTag: String, g: (E, S2) => S2) = ???

  type IntState = Int
  type StringState = String

  def intReducer(e: Event, state: IntState): IntState = state + 1

  def stringReducer(e: Event, state: StringState): StringState = state + e.toString


  val reducer = combineReducers("count", intReducer, "names", stringReducer)

  val initialState = ("count" ->> 0 ) :: ("names" ->> "") :: HNil

  val newState = reducer(Create(), initialState) // returns ("count" ->> 1) :: ("names" ->> "Create()") :: HNil
  reducer(Save(), newState) // returns ("count" ->> 2) :: ("names" ->> "Create()Save()") :: HNil

}

Ответы [ 2 ]

0 голосов
/ 10 октября 2018

IMO, вам все еще не нужен Shapeless: D

Ваша функция сокращения может включать метку, которая только что передана без изменений.Немного неудобно устанавливать метку в начальном состоянии, но у вашего текущего решения та же проблема

object CombineStatesQ extends App {

  import cats._
  import cats.implicits._

  sealed trait Event
  case class Create() extends Event
  case class Save() extends Event

  type Reducer[A] = (Event, A) => A

  implicit object ReducerAp extends InvariantSemigroupal[Reducer] {
    override def product[A, B](fa: Reducer[A], fb: Reducer[B]): Reducer[(A, B)] =
      (e: Event, ab:(A,B)) => (fa(e,ab._1), fb(e, ab._2))
    override def imap[A, B](fa: Reducer[A])(f: A => B)(g: B => A): Reducer[B] = (e, b) => f(fa(e, g(b)))
  }

  type Label = String
  val countReducer: Reducer[(Label, Int)] = (e, s) => (s._1, s._2 + 1)
  val stringReducer: Reducer[(Label, String)] = (e, s) => (s._1, s._2 + e.toString)
  val listReducer: Reducer[(Label, List[Event])] = (e, s) => (s._1, e:: s._2)

  val combinedReducer: Reducer[((Label, Int), (Label, String), (Label, List[Event]))] =
    (countReducer, stringReducer, listReducer).tupled

  val initialState: ((Label, Int), (Label, String), (Label, Nil.type)) =
    (("count", 0 ),("names",""), ("list", Nil))


  val state1 = combinedReducer(Create(),initialState)
  println(state1) // ((count,1),(names,Create()),(list,List(Create())))
  val state2 = combinedReducer(Save(), state1)
  println(state2) //((count,2),(names,Create()Save()),(list,List(Save(), Create())))
}
0 голосов
/ 09 октября 2018

Попробуйте

import shapeless.labelled.{FieldType, field}
import shapeless.{::, HNil}
import shapeless.syntax.singleton._

sealed trait Event

case class Create() extends Event
case class Save() extends Event

object Reducers {
  type Reducer[E, S] = (E, S) => S

  def combineReducers[K1 <: String, E, S1, K2 <: String, S2](
                                                              fTag: K1,
                                                              f: Reducer[E, S1],
                                                              gTag: K2,
                                                              g: Reducer[E, S2]
                                                            ): Reducer[E, FieldType[K1, S1] :: FieldType[K2, S2] :: HNil] =
    { case (e, v1 :: v2 :: HNil) => field[K1](f(e, v1)) :: field[K2](g(e, v2)) :: HNil }

  type IntState = Int
  type StringState = String

  def intReducer(e: Event, state: IntState): IntState = state + 1

  def stringReducer(e: Event, state: StringState): StringState = state + e.toString

  val reducer: Reducer[Event, FieldType[Witness.`"count"`.T, IntState] :: FieldType[Witness.`"names"`.T, StringState] :: HNil] =
    combineReducers("count".narrow, intReducer, "names".narrow, stringReducer)

  val initialState = ("count" ->> 0 ) :: ("names" ->> "") :: HNil

  val newState = reducer(Create(), initialState) // returns ("count" ->> 1) :: ("names" ->> "Create()") :: HNil
  val newState1 = reducer(Save(), newState) // returns ("count" ->> 2) :: ("names" ->> "Create()Save()") :: HNil
}

Мы должны аннотировать reducer явным типом, потому что компилятор не может вывести E в combineReducers.Если вы измените сигнатуру на

def combineReducers[K1 <: String, S1, K2 <: String, S2](
                                                         fTag: K1,
                                                         f: Reducer[Event, S1],
                                                         gTag: K2,
                                                         g: Reducer[Event, S2]
                                                       ): Reducer[Event, FieldType[K1, S1] :: FieldType[K2, S2] :: HNil] = ...

, тогда вы можете написать просто

val reducer = combineReducers("count".narrow, intReducer, "names".narrow, stringReducer)

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

trait CombineReducers[Keys <: HList, Reducers <: HList] {
  type Record <: HList
  def apply(keys: Keys, reducers: Reducers): Reducer[Event, Record]
}
object CombineReducers {
  type Aux[Keys <: HList, Reducers <: HList, Record0 <: HList] = CombineReducers[Keys, Reducers] { type Record = Record0 }
  def instance[Keys <: HList, Reducers <: HList, Record0 <: HList](f: (Keys, Reducers) => Reducer[Event, Record0]): Aux[Keys, Reducers, Record0] = new CombineReducers[Keys, Reducers] {
    type Record = Record0
    override def apply(keys: Keys, reducers: Reducers): Reducer[Event, Record0] = f(keys, reducers)
  }

  implicit val hnilCombineReducers: Aux[HNil, HNil, HNil] = instance { case (HNil, HNil) => { case (_, HNil) => HNil }}
  implicit def hconsCombineReducers[K <: String, Ks <: HList, S, Rs <: HList](implicit
    cr: CombineReducers[Ks, Rs]): Aux[K :: Ks, Reducer[Event, S] :: Rs, FieldType[K, S] :: cr.Record] = instance {
    case (k :: ks, r :: rs) => {
      case (e, s :: ss) => field[K](r(e, s)) :: cr(ks, rs)(e, ss)
    }
  }
}

и использоватьэто

def combineReducers[Keys <: HList, Reducers <: HList](keys: Keys, reducers: Reducers)(implicit
  cr: CombineReducers[Keys, Reducers]): Reducer[Event, cr.Record] = cr(keys, reducers)

val reducer =
  combineReducers("count".narrow :: "names".narrow :: HNil, (intReducer: (Event, IntState) => IntState) :: (stringReducer : (Event, StringState) => StringState) :: HNil)
...