2016-01-19 12 views
7

Scalaz State monada na modify ma następujący podpis:Przemiany państwa z bezkształtnej monady State

def modify[S](f: S => S): State[S, Unit] 

Pozwala to państwo zastępuje go stanu tego samego typu, co nie działa dobrze, gdy stan obejmuje wartość bezkształtne, takie jak Record którego zmiany typu jako nowe pola są dodawane. W tym przypadku jest to, czego potrzebujemy:

def modify[S, T](f: S => T): State[T, Unit] 

Co to dobry sposób, aby dostosować State monady Scalaz do korzystania stan bezkształtną tak, że można używać Records w przeciwieństwie do, powiedzmy, bał Map[String, Any]?

Przykład:

case class S[L <: HList](total: Int, scratch: L) 

def contrivedAdd[L <: HList](n: Int): State[S[L], Int] = 
    for { 
    a <- init 
    _ <- modify(s => S(s.total + n, ('latestAddend ->> n) :: s.scratch)) 
    r <- get 
    } yield r.total 

Aktualizacja:

Kod pełna za odpowiedź Travisa jest here.

+0

Jak o 'def modyfikować [S, T], (f: S => T): Stan [T, jednostka] = stan ((S S) => (F (a),()))'? – knutwalker

+0

@knutwalker sugerujesz rozszerzenie 'State' i przeciążenia' Zmień '? – Sim

+0

Nie, wystarczy napisać tę funkcję w dowolnym miejscu w kodzie.Nie ma potrzeby rozszerzania ani przeciążania czegokolwiek. – knutwalker

Odpowiedz

8

State jest alias typ bardziej uniwersalnym typem IndexedStateT, który jest specjalnie zaprojektowany, aby reprezentować funkcje, które zmieniają rodzaj państwowej jako obliczeń państwowych:

type StateT[F[_], S, A] = IndexedStateT[F, S, S, A] 
type State[S, A] = StateT[Id, S, A] 

Chociaż to nie jest możliwe napisanie modify[S, T] użyciu State, to możliwe IndexedState (który jest innym typem alias IndexedStateT że ustala rodzaj efektu do Id):

import scalaz._, Scalaz._ 

def transform[S, T](f: S => T): IndexedState[S, T, Unit] = 
    IndexedState(s => (f(s),())) 

Można nawet użyć tego w for -comprehensions (które zawsze wydawały się trochę dziwne dla mnie, ponieważ monadycznych zmian typu między operacjami, ale działa):

val s = for { 
    a <- init[Int]; 
    _ <- transform[Int, Double](_.toDouble) 
    _ <- transform[Double, String](_.toString) 
    r <- get 
} yield r * a 

, a następnie:

scala> s(5) 
res5: scalaz.Id.Id[(String, String)] = (5.0,5.05.05.05.05.0) 

W twoim przypadku można napisać coś takiego:

import shapeless._, shapeless.labelled.{ FieldType, field } 

case class S[L <: HList](total: Int, scratch: L) 

def addField[K <: Symbol, A, L <: HList](k: Witness.Aux[K], a: A)(
    f: Int => Int 
): IndexedState[S[L], S[FieldType[K, A] :: L], Unit] = 
    IndexedState(s => (S(f(s.total), field[K](a) :: s.scratch),())) 

, a następnie:

def contrivedAdd[L <: HList](n: Int) = for { 
    a <- init[S[L]] 
    _ <- addField('latestAdded, n)(_ + n) 
    r <- get 
} yield r.total 

(To nie może być najlepszym sposobem faktoringowych na kawałki operacji aktualizacji, ale to pokazuje, jak działa podstawowy pomysł.)

Warto również zauważyć, że jeśli nie dbają o reprezentujący transformacja stan jako stan obliczeń, można po prostu użyć imap na byle State:

init[S[HNil]].imap(s => 
    S(1, field[Witness.`'latestAdded`.T](1) :: s.scratch) 
) 

nie pozwalają na skorzystanie z tych operacji kompozycyjnie w taki sam sposób, ale może to być wszystko, co potrzebne w niektórych sytuacjach ,

+2

Twoja odpowiedź, oprócz tego, że jest elegancka, zawiera w sobie schludny mały klejnot, który jest fragmentem 'addField', który rozszerza rekord za pomocą klucza i wartości. Kombinacja świadka i 'pola [K] (a)' nie jest oczywista bez czytania bezkształtnego źródła. – Sim