mattpolzin/JSONAPI

why naming for includes are like this ?

ZahraAsgharzadeh opened this issue · 1 comments

include 0, include 1 and ...
or if I want to access include value type , I should get object with name 'a' when I include one type and 'b' for include 2 types and ...
could you please explain it to me ?!

This question could potentially result in a pretty long answer so I will do my best to keep it short and still provide some insight into my thinking.

To lay the groundwork, JSON:API Includes are an array of any number of types of things -- including a Person's friends and dogs might result in Includes of types Dog and also Person.

In Swift when I need a type that can represent one of a number of things, I reach for one of (a) existential support via Protocols, (b) type erasure, or (c) generics. Option (a) could not be used outright because protocols cannot provide conformance to Codable; This option would have required a similar degree of work at the use-site as option (b) would go through behind the scenes in a new Type.

Option (b) would have worked fine for encoding/decoding, but getting the value back out would mean passing through Any and that introduces a usability problem as described below.

Option (c) retains Type information via the specialization of the generic package Type (i.e. Include2<Dog, Person> is a Type that can be passed anywhere and will always know the thing within it is either a Dog or a Person). This option makes type naming a bit awkward (Include1, Include2, Include3, etc.) and direct access a bit awkward (i.e. include.a, include.b, etc.) but it offers additional guarantees at the call site over option (a)/(b).

All of that said, here is a quick comparison of call site use that gets at the "additional guarantees" I mentioned. I will use a fictitious type AnyInclude as a stand-in for option (b).

// Assume the following are all valid resource types that could be included (depending on the request)
// `Dog`, `Person`, `Cat`

let optionBInclude: AnyInclude = ...
let optionCInclude: Include2<Dog, Person> = ...

// getting a `Dog` back out
let optionBDog: Dog? = optionBInclude.value as? Dog
let optionCDog1: Dog? = optionCInclude.a
let optionCDog2: Dog? = optionCInclude[Dog.self] // just an alternative to using `.a` property.

// getting a `Cat` back out
let optionBCat: Cat? = optionBInclude.value as? Cat // allowed, because `AnyInclude` only knows it contains some type of include
let optionCCat1: Cat? = optionCInclude.a // compile-time error, `.a` property is of type `Dog?`
let optionCCat2: Cat? = optionCInclude.b // compile-time error, `.b` property is of type `Person?`
let optionCCat3: Cat? = optionCInclude[Cat.self] // compile-time error, no subscript operator that accepts type `Cat`

As you can see, Include2<Dog, Person> won't even let you accidentally try to pull a Cat back out again, whereas AnyInclude has no way of knowing it does not contain a Cat. This means you can explicitly state all of the types of includes a response is expected to get. If you wanted a type that could also represent Cat, you could just use Include3<Dog, Person, Cat> (now optionCInclude[Cat.self] will compile).