/ / Come ridurre il boilerplate con monocolo in scala-scala, lenti, monocle-scala

Come ridurre il boilerplate con monocolo in scala-scala, lenti, monocle-scala

Ho refactored il mio codice per giorno 12 di avvento del codice usando monocolo, una libreria di lenti in scala.

È possibile migliorare questo codice:

  type Register = String
type Mem = Map[String, Int]

@Lenses
case class State(mem: Mem, pointer: Int)

def processInstruction(instructions: Seq[Instruction]): State => State = { s =>
(instructions(s.pointer) match {
case Inc(r) =>
State.pointer.modify( _ + 1) andThen (State.mem composeLens at(r)).modify(_.map(_ + 1))
case Dec(r) =>
State.pointer.modify( _ + 1) andThen (State.mem composeLens at(r)).modify(_.map(_ - 1))
case CpyInt(v, to) =>
State.pointer.modify( _ + 1) andThen (State.mem composeLens at(to)).set(Some(v))
case CpyReg(from, to) =>
State.pointer.modify( _ + 1) andThen (State.mem composeLens at(to)).set(Some(s.mem(from)))
case Jnz(r, v) => if (r != "1" && s.mem(r) == 0)
State.pointer.modify( _ + 1)
else
State.pointer.modify( _ + v )
}).apply(s)
}

Ed ecco un altro tentativo, che separa la modifica di ogni campo

  def processInstruction2(instructions: Seq[Instruction]): State => State = { s =>
val ptr = instructions(s.pointer) match {
case Jnz(r, v) if !(r != "1" && s.mem(r) == 0) => State.pointer.modify(_ + v)
case _ => State.pointer.modify(_ + 1)
}

val mem = instructions(s.pointer) match {
case Inc(r) => (State.mem composeLens at(r)).modify(_.map(_ + 1))
case Dec(r) => (State.mem composeLens at(r)).modify(_.map(_ - 1))
case CpyInt(v, to) => (State.mem composeLens at(to)).set(Some(v))
case CpyReg(from, to) => (State.mem composeLens at(to)).set(Some(s.mem(from)))
case _ => identity[State]
}
(ptr andThen mem)(s)
}

Un'altra domanda: c'è un modo di usare Map.withDefaultValue con monocolo?

Il codice completo è qui: https://gist.github.com/YannMoisan/b8ba25afc041d88706545527d9ec1988

risposte:

-1 per risposta № 1

Potresti voler utilizzare il secondo approccio, perché separa la gestione di due campi. Tuttavia, le funzioni non devono essere interpretate in sequenza (andThen), ma piuttosto dovrebbero essere combinati come PartialFunctions con orElse.

def processInstruction3(instructions: Seq[Instruction]): State => State = {
val ptr: PartialFunction[Instruction, State => State] = {
case Jnz(r, v) =>
State.pointer.modify(_ + v)
}

val incPointer: State => State = State.pointer.modify( _ + 1)
def reg(r: String): Lens[State, Option[Int]] = State.mem composeLens at(r)
val mem: PartialFunction[Instruction, State => State] = {
case Inc(r) => reg(r).modify(_.orElse(Option(0)).map(_ + 1))
case Dec(r) => reg(r).modify(_.orElse(Option(0)).map(_ - 1))
case CpyInt(v, to) => reg(to).set(Some(v))
case CpyReg(from, to) => s => reg(to).set(reg(from).get(s))(s)
}
val interpreter = ptr orElse (mem andThen (_ andThen incPointer))
s => instructions.foldLeft(s)((s, i) => interpreter(i)(s))
}

AGGIORNARE (dopo il commento di Yann Moisan)

L'esecuzione potrebbe non terminare in caso di loop infinito nel programma dell'utente, quindi al posto di foldLeft abbiamo bisogno di una funzione ricorsiva che estrarrà l'istruzione successiva dal puntatore:

@tailrec
def loop(s: State): State = {
if(s.pointer>=instructions.length)
s
else {
val instruction = instructions(s.pointer)
val nextState = interpreter(instruction)(s)
loop(nextState)
}
}
loop _

(L'ultima riga di processInstruction3 dovrebbe essere sostituito con il codice sopra)