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).