Mapping decoded arrays with array functions (or T with T's functions)
jshier opened this issue · 9 comments
General Question:
I'm decoding an array of a given type, but I'd like to drop the first element of the array when returned the decoded types. Naturally, dropFirst
can be used here. Is there any way to map such a function into the decoding?
In Particular:
I'm parsing my CocoaPods acknowledgements files, and it's first array item is something I want to ignore. The decoding is otherwise trivial:
struct Acknowledgements {
let acknowledgements: [Acknowledgement]
}
extension Acknowledgements: Decodable {
static func decode(_ json: JSON) -> Decoded<Acknowledgements> {
return self.init <^> ((json <|| "PreferenceSpecifiers") <^> Array.dropFirst)
}
}
struct Acknowledgement {
let title: String
let licenseText: String
}
extension Acknowledgement: Decodable {
static func decode(_ json: JSON) -> Decoded<Acknowledgement> {
let cinit = curry(self.init)
return cinit
<^> json <| "Title"
<*> json <| "FooterText"
}
}
However, I see no elegant way to return the array of items with the first element dropped. Either I'll have to do the parsing more manually or I'll have to create a global function which takes an array and returns the array with the first element dropped.
So the type of dropFirst
is [A] -> [A]
and the type you have after decoding the list is Decoded<[Acknowledgement]>
. If you ignore the Array it comes down to A -> A
and Decoded<A>
... now this looks perfect for map
ie <^>
.
I can see you got that far. I think you just mixed up the types. <^>
takes the function as it's first argument (left side). (Array.dropFirst <^> (json <|| "PreferenceSpecifiers"))
should do the trick.
Actually, the signature is dropFirst() -> ArraySlice<Element>
which is a pain. So I need to map the Array
initializer in there somewhere. return self.init <^> ((Array.dropFirst <^> (json <|| "PreferenceSpecifiers")) >>- Array.init(_:))
doesn't work. Nor does (Array.init(_:) <^> (Array.dropFirst <^> (json <|| "PreferenceSpecifiers")))
. After dropFirst
, I think I have Decoded<ArraySlice<Acknowledgement>>
, so I would've thought <^>
would be appropriate again.
At that point I would probably just make an extension on ArraySlice
or Array
that takes care of that weirdness for you.
You're right that <^>
is appropriate again but I think it's going to look pretty gross with it all in there together. Not sure why <^>
didn't work for you ... in those situations I always break the statement up into single expression with a typed variable so I can make sure the type at each step is what I think it is.
I'm getting the error: Cannot convert value of type '(_) -> Array<_>' to expected argument type '(_) -> _'
, probably because Array.init(_:)
is generic for a Sequence
.
Breaking it out step by step, it looks like dropFirst
in this context is returning a Decoded<(Int) -> ArraySlice<Acknowledgement>>
, which I'm not sure how to handle at all.
I just broke it out into a global function so I could flatMap
it.
extension Acknowledgements: Decodable {
static func decode(_ json: JSON) -> Decoded<Acknowledgements> {
return self.init <^> (json <|| "PreferenceSpecifiers" >>- dropFirst)
}
}
fileprivate func dropFirst<T>(_ array: [T]) -> Decoded<[T]> {
return pure(Array(array.dropFirst()))
}
Only now that code is causing a compiler crash. 💥
Making the dropFirst
global function I created non-generic fixed the crash. 🤷♂️
haha welp glad you got it working! I think there is just some weirdness with ArraySlice and that was causing you some issues.