paldepind / union-type

A small JavaScript library for defining and using union types.
MIT License
477 stars 28 forks source link

serialization/deserialization of actions #21

Open yelouafi opened 8 years ago

yelouafi commented 8 years ago

If we represent Actions in Elm architecture as plain objects we can record a play sequence by simple JSON stringify/parse. How should this be acheived in union types ?

paldepind commented 8 years ago

Good question. Serialization is a problem since the constructed values are represented as arrays with additional properties.

From the top of my head the easiest solution would be to create two simple conversion functions that changes the values to JSON compatible representations.

yelouafi commented 8 years ago

I think deserialization is the real issue.

yelouafi commented 8 years ago

sorry, my last comment wasn't very explicit; What I mean is that when deserializing a JSON representation; how do we determine the correct type constructor to apply.

Perhaps if we could identify each type with a serializable attribute like a global uuid

function typeRegistry = {}

function Type(desc) {
  var obj = {};
  obj.uid =  guuid() 
  typeRegistry [obj.uid] = obj
  ...
}

function serialize(inst) {
  const obj = serializeHelper(inst)
  obj.typeId = inst.of.uid
  return JSON.stringify(obj)
}

function deserialize(json) {
  const inst = JSON.parse(json)
  const type = typeRegistry[inst.typeId]
  return type[inst.name](inst)
}

EDIT: fixed code

yelouafi commented 8 years ago

For the above to work; uuid needs to be the same across multiple debug sessions (possibly on different machines). We can also set val.of to the computed type id directly to avoid reconstructing the object and eliminate the need to maintain a type registry

A possible solution is to compute the type id from the desc argument

function Type(desc) {
  var obj = {};
  obj.id = JSON.stringify( Object.keys(desc) ) // could be more reliable
  for (var key in desc) {
    // set the 'of' guard to the object id
    obj[key] = Constructor(obj.id, key, desc[key]);
  }
  obj.case = typeCase(obj);
  obj.caseOn = caseOn(obj);
  return obj;
}

function serialize(instance) {
  return JSON.stringify({
    vals: instance,
    key: instance.name,
    typeId: instance.of
  })
}

function deserialize(json) {
  const o = JSON.parse(json)
  const inst = o.vals
  inst.name = o.key
  inst.of = o.typeId
  return inst
}
jgoux commented 8 years ago

@yelouafi I'd be interested in what did you end up doing ? I'm trying to use union-type with redux, but I don't find a proper way to make the conversion FSA <-> union-type Action

yelouafi commented 8 years ago

@jgoux Actually I ended up with implementing my poor-man's solution

No currying and may need some refinements, but actions are plain objects

const Any = () => true

function validate(value, contract) {
  if(contract === String)
    return typeof value === 'string'

  if(contract === Number)
    return typeof value === 'number'

  else
    return contract(value)
}

export function unionOf(union) {
  const res = {}
  const keys = Object.keys(union)
  const unionKey = keys.join('|')

  keys.forEach(type => {
    const contracts = union[type]
    res[type] = (...args) => {
      contracts.forEach((c, idx) => {
        if(!validate(args[idx], c))
          throw new Error(`Case Error: invalid argument passed to ${type} at position ${idx}`)
      })
      return {type, args, unionKey }
    }

  })

  res.caseOf = (action, cases) => {
    let handler, args
    if(action.unionKey === unionKey) {
      handler = cases[action.type] || cases._
      args = action.args
    } else {
      handler = cases._
      args = [action]
    }
    if(!handler)
      throw `Unkwon case action ${action}`

    return handler.apply(null, args)
  }

  return res
}