Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transforming data from a runtime storage to a case class

I'm trying to abstract over the android.os.Bundle API, aiming to generate Bundles in this fashion:

case class MyClass( a: Int, b: String )
val mc = MyClass( 3, "5" )
implicit val bundleable = Bundle.from[MyClass]()
val bundle = bundleable.write( mc )
assert( mc == bundleable.read( bundle ) )

Converting the case class to a LabelledGeneric and writing the key value pairs to the Bundle is straightforward. But I can't find a way to extract the values from a Bundle back into their original type. I guess the numerous JSON-libraries out there solved this problem already, but I still fail at finding a clue on how to proceed.

object Bundle {
    def from[T] = new {
        def apply[LG <: HList, K <: HList, N <: Nat]()(
            implicit
            lg:  LabelledGeneric.Aux[T, LG],
            l:   Length.Aux[LG, N],
            k:   Keys.Aux[LG, K],
            lfw: LeftFolder.Aux[LG, Bundle, fold.write.type, Bundle],
            //lfr: LeftFolder.Aux[K, Bundle, fold.read.type, LG],
            ti: ToInt[N]
        ) = new Bundleable[T] {
            override def write( value: T ): Bundle = {
                lg.to( value ).foldLeft( new Bundle( toInt[N] ) )( fold.write )
            }

            override def read( bundle: Bundle ): T = ???
        }
    }

    object fold {
        object write extends Poly2 {
            implicit def default[K <: Symbol, V: Bundleize]( implicit key: Witness.Aux[K] ): Case.Aux[Bundle, FieldType[K, V], Bundle] = {
                at { ( bundle, value ) ⇒
                    implicitly[Bundleize[V]].write( key.value.name, value, bundle )
                    bundle
                }
            }
        }

        object read extends Poly2 {
            ???
        }
    }
}

/**
 * Read or write a single value from/into a Bundle
 */
trait Bundleize[T] {
    def read( key: String, bundle: Bundle ): T

    def write( key: String, value: T, bundle: Bundle ): Unit
}

/**
 * Transformation T <> Bundle
 */
trait Bundleable[T] {
    def read( bundle: Bundle ): T

    def write( value: T ): Bundle
}

Also, is there a way to restructure the code in such a way, that I can write Bundle.from[MyClass], rather than Bundle.from[MyClass]() (omitting the parentheses)?

like image 365
Taig Avatar asked Nov 05 '15 00:11

Taig


1 Answers

Thanks to the pointer to the shapeless S-Expression example, I was able to put together a working solution.

import android.os.Bundle
import shapeless.labelled._
import shapeless.ops.hlist.{ Length, LeftFolder }
import shapeless._
import shapeless.Nat.toInt
import shapeless.ops.nat.ToInt
import shapeless.syntax.std.tuple._

/**
 * Type class that instructs how to deserialize/serialize a value from/to a Bundle
 */
trait Bundleable[T] {
    def read( bundle: Bundle ): T

    def write( value: T ): Bundle
}

object Bundleable {
    def apply[T]( r: Bundle ⇒ T, w: T ⇒ Bundle ) = new Bundleable[T] {
        override def read( bundle: Bundle ) = r( bundle )

        override def write( value: T ) = w( value )
    }

    def from[T: Bundleable]: Bundleable[T] = the[Bundleable[T]]

    private object fold {
        object write extends Poly2 {
            implicit def default[K <: Symbol, V: Bundleize]( implicit key: Witness.Aux[K] ) = {
                at[Bundle, FieldType[K, V]] { ( bundle, value ) ⇒
                    implicitly[Bundleize[V]].write( key.value.name, value, bundle )
                    bundle
                }
            }
        }
    }

    implicit val `Bundleable[HNil]` = Bundleable[HNil]( _ ⇒ HNil, _ ⇒ Bundle.EMPTY )

    implicit def `Bundleable[HList]`[K <: Symbol, V, T <: HList, N <: Nat](
        implicit
        key: Witness.Aux[K],
        bv:  Bundleize[V],
        bt:  Bundleable[T],
        l:   Length.Aux[FieldType[K, V] :: T, N],
        ti:  ToInt[N],
        lf:  LeftFolder.Aux[FieldType[K, V] :: T, Bundle, fold.write.type, Bundle]
    ) = Bundleable[FieldType[K, V] :: T](
        bundle ⇒ field[K]( bv.read( key.value.name, bundle ) ) :: bt.read( bundle ),
        _.foldLeft( new Bundle( toInt[N] ) )( fold.write )
    )

    implicit def `Bundleable[LabelledGeneric]`[T, LG](
        implicit
        lg: LabelledGeneric.Aux[T, LG],
        b:  Bundleable[LG]
    ) = Bundleable[T]( bundle ⇒ lg.from( b.read( bundle ) ), value ⇒ b.write( lg.to( value ) ) )
}

Sample usage:

case class MyCaseClass( a: String, b: Int, c: Double )
val instance = MyCaseClass( "3", 3, 3 )
val bundleable = Bundleable.from[MyCaseClass]
val bundle: Bundle = bundleable.write( instance )
val deserialized = bundleable.read( bundle )
assert( instance == deserialized )
like image 166
Taig Avatar answered Nov 15 '22 00:11

Taig