Swifters:
I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
Here’s what I’ve tried:
extension Collection where Iterator.Element == OptionSet {
Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.
public struct MyOptionSet: OptionSet {
public static let one = MyOptionSet(rawValue: 1 << 0)
public static let two = MyOptionSet(rawValue: 1 << 1)
public static let three = MyOptionSet(rawValue: 1 << 2)
public var rawValue: Int { return _rawValue }
public init(rawValue: Int) { self._rawValue = rawValue }
private let _rawValue: Int
private enum StringEnum: String { case one, two, three }
public init(strings: [String]) {
var set = MyOptionSet()
strings.flatMap({ StringEnum(rawValue: $0) })
.flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
.forEach { set.insert($0) }
_rawValue = set.rawValue
}
}
let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue
···
On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org> wrote:
Swifters:
I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
Here’s what I’ve tried:
extension Collection where Iterator.Element == OptionSet {
Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.
Thanks,
Jon
···
On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com> wrote:
Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.
public struct MyOptionSet: OptionSet {
public static let one = MyOptionSet(rawValue: 1 << 0)
public static let two = MyOptionSet(rawValue: 1 << 1)
public static let three = MyOptionSet(rawValue: 1 << 2)
public var rawValue: Int { return _rawValue }
public init(rawValue: Int) { self._rawValue = rawValue }
private let _rawValue: Int
private enum StringEnum: String { case one, two, three }
public init(strings: [String]) {
var set = MyOptionSet()
strings.flatMap({ StringEnum(rawValue: $0) })
.flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
.forEach { set.insert($0) }
_rawValue = set.rawValue
}
}
let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue
On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Swifters:
I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
Here’s what I’ve tried:
extension Collection where Iterator.Element == OptionSet {
It’s the most plausible implementation, but I’d think code that relies on case order would break silently (likely at widely-separated locations) if a case were inserted or removed. That suggests to me it’s not possible to regularize this behavior.
Folkloric API (like SEL ↔︎ char* in ObjC) makes me itch.
— F
···
On 3 Nov 2016, at 8:37 PM, Erica Sadun via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
private enum StringEnum: String { case one, two, three }
public init(strings: [String]) {
var set = MyOptionSet()
strings.flatMap({ StringEnum(rawValue: $0) })
.flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
.forEach { set.insert($0) }
_rawValue = set.rawValue
}
let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
(result, set) in return result.union(set)
}
unioned.rawValue
···
On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com> wrote:
Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.
Thanks,
Jon
On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:
Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.
public struct MyOptionSet: OptionSet {
public static let one = MyOptionSet(rawValue: 1 << 0)
public static let two = MyOptionSet(rawValue: 1 << 1)
public static let three = MyOptionSet(rawValue: 1 << 2)
public var rawValue: Int { return _rawValue }
public init(rawValue: Int) { self._rawValue = rawValue }
private let _rawValue: Int
private enum StringEnum: String { case one, two, three }
public init(strings: [String]) {
var set = MyOptionSet()
strings.flatMap({ StringEnum(rawValue: $0) })
.flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
.forEach { set.insert($0) }
_rawValue = set.rawValue
}
}
let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue
On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Swifters:
I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
Here’s what I’ve tried:
extension Collection where Iterator.Element == OptionSet {
func joinOptionSets<OS: OptionSet>(_ sets: [OS]) -> OS {
return sets.reduce( as OS) {
(result, set) in return result.union(set)
}
}
joinOptionSets(sets).rawValue
-- E
···
On Nov 3, 2016, at 7:48 PM, Erica Sadun via swift-users <swift-users@swift.org> wrote:
Like this?
let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
(result, set) in return result.union(set)
}
unioned.rawValue
On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:
Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.
Thanks,
Jon
On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:
Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.
public struct MyOptionSet: OptionSet {
public static let one = MyOptionSet(rawValue: 1 << 0)
public static let two = MyOptionSet(rawValue: 1 << 1)
public static let three = MyOptionSet(rawValue: 1 << 2)
public var rawValue: Int { return _rawValue }
public init(rawValue: Int) { self._rawValue = rawValue }
private let _rawValue: Int
private enum StringEnum: String { case one, two, three }
public init(strings: [String]) {
var set = MyOptionSet()
strings.flatMap({ StringEnum(rawValue: $0) })
.flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
.forEach { set.insert($0) }
_rawValue = set.rawValue
}
}
let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue
On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Swifters:
I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
Here’s what I’ve tried:
extension Collection where Iterator.Element == OptionSet {
Yes, just in a generic fashion. I don’t have so many OptionSets to decode that writing the typed reduce code over and over again is terrible, I just hoped there would be a way to write it generically. I just can’t seem to get the types to work out. I’m just missing the final step, generically reducing an array of OptionSet values to a single OptionSet value.
Jon
···
On Nov 3, 2016, at 9:48 PM, Erica Sadun <erica@ericasadun.com> wrote:
Like this?
let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
(result, set) in return result.union(set)
}
unioned.rawValue
On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:
Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.
Thanks,
Jon
On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:
Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.
public struct MyOptionSet: OptionSet {
public static let one = MyOptionSet(rawValue: 1 << 0)
public static let two = MyOptionSet(rawValue: 1 << 1)
public static let three = MyOptionSet(rawValue: 1 << 2)
public var rawValue: Int { return _rawValue }
public init(rawValue: Int) { self._rawValue = rawValue }
private let _rawValue: Int
private enum StringEnum: String { case one, two, three }
public init(strings: [String]) {
var set = MyOptionSet()
strings.flatMap({ StringEnum(rawValue: $0) })
.flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
.forEach { set.insert($0) }
_rawValue = set.rawValue
}
}
let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue
On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Swifters:
I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
Here’s what I’ve tried:
extension Collection where Iterator.Element == OptionSet {
On Nov 3, 2016, at 9:56 PM, Erica Sadun <erica@ericasadun.com> wrote:
How about
func joinOptionSets<OS: OptionSet>(_ sets: [OS]) -> OS {
return sets.reduce( as OS) {
(result, set) in return result.union(set)
}
}
joinOptionSets(sets).rawValue
-- E
On Nov 3, 2016, at 7:48 PM, Erica Sadun via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Like this?
let sets: [MyOptionSet] = [MyOptionSet(strings: ["one"]), MyOptionSet(strings: ["two"]), MyOptionSet(strings: ["one", "two"])]
let unioned = sets.reduce(MyOptionSet(rawValue: 0)) {
(result, set) in return result.union(set)
}
unioned.rawValue
On Nov 3, 2016, at 7:44 PM, Jon Shier <jon@jonshier.com <mailto:jon@jonshier.com>> wrote:
Thanks Erica. I’ve been able to transform arrays of strings into arrays of my OptionSets using an enum approach like you describe. I was looking more for a generic approach that I could apply to all of the various OptionSets I have to decode from JSON. I suppose whether it’s from an array of strings or array of the OptionSet is less important, but getting to the array of the OptionSet itself is something I can already do.
Thanks,
Jon
On Nov 3, 2016, at 9:37 PM, Erica Sadun <erica@ericasadun.com <mailto:erica@ericasadun.com>> wrote:
Maybe something like this? Or you could just bitwise || individual sets. Or you could use a dictionary to lookup [string: rawValue]. etc.
public struct MyOptionSet: OptionSet {
public static let one = MyOptionSet(rawValue: 1 << 0)
public static let two = MyOptionSet(rawValue: 1 << 1)
public static let three = MyOptionSet(rawValue: 1 << 2)
public var rawValue: Int { return _rawValue }
public init(rawValue: Int) { self._rawValue = rawValue }
private let _rawValue: Int
private enum StringEnum: String { case one, two, three }
public init(strings: [String]) {
var set = MyOptionSet()
strings.flatMap({ StringEnum(rawValue: $0) })
.flatMap({ MyOptionSet(rawValue: 1 << $0.hashValue) })
.forEach { set.insert($0) }
_rawValue = set.rawValue
}
}
let stringArray: [String] = ["one", "three"]
let stringOptions = MyOptionSet(strings: stringArray)
stringOptions.rawValue
On Nov 3, 2016, at 7:09 PM, Jon Shier via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
Swifters:
I’m dealing with a JSON API where sets of options are returned as arrays of strings. Representing these as OptionSets seems ideal. I can decode the arrays of strings into an array of individual OptionSet values, but I’ve run into a dead end when trying generically reduce the array of OptionSets to a single OptionSet value. I’ve tried variety of ways of definition a Collection extension, even tried defining a global function, but I can’t seem to use the OptionSet sequence initializer or reduce itself (cannot invoke insert with argument of type (OptionSet) (or T)). Any guidance here?
Here’s what I’ve tried:
extension Collection where Iterator.Element == OptionSet {
It’s the most plausible implementation, but I’d think code that relies on case order would break silently (likely at widely-separated locations) if a case were inserted or removed. That suggests to me it’s not possible to regularize this behavior.
Folkloric API (like SEL ↔︎ char* in ObjC) makes me itch.
I'm running into a very similar issue and I'm not sure if there's a cleaner way to implement encoding/decoding. I have a REST api that returns options as json array, ex: "["OptionB", "OptionC"]" and I would like to form an OptionSet from it as well as re-encode to that format.
I have the following implementation that works, but requires a lot of manual updating if the supported options changes. Is there a better way to write this, or perhaps use Mirror to get the behavior I'd like?
(note: I'm very unfamiliar with swift's reflection apis. Also, please let me know if this is off-topic; I can make a new thread if that would be better.)
struct ExampleOptions: OptionSet, Codable {
let rawValue: Int
static let optionA = ExampleOptions(rawValue: 1 << 0)
static let optionB = ExampleOptions(rawValue: 1 << 1)
static let optionC = ExampleOptions(rawValue: 1 << 2)
init(rawValue: Int) {
self.rawValue = rawValue
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var options = [ExampleOptions]()
while !container.isAtEnd {
let string = try container.decode(String.self)
let option = try ExampleOptions(string: string)
options.append(option)
}
let null = ExampleOptions(rawValue: 0)
self = options.reduce(null) { return $0.union($1) }
}
init(string: String) throws {
switch string {
case "OptionA": self = .optionA
case "OptionB": self = .optionB
case "OptionC": self = .optionC
default: throw NSError(domain: "ExampleOptions", code: 0, userInfo: nil) // make this more descriptive
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
if contains(.optionA) { try container.encode("OptionA") }
if contains(.optionB) { try container.encode("OptionB") }
if contains(.optionC) { try container.encode("OptionC") }
}
}
The implementation of init(from:) sticks out to me. Without testing the code, I think it could be written like this:
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var options = ExampleOptions()
while !container.isAtEnd {
let string = try container.decode(String.self)
let option = try ExampleOptions(string: string)
options.insert(option)
}
self = options
}
Also without trying something out, I think you may get away with less code if you go for two types instead of one. You could use one enum for talking to your API that uses String raw values and no custom code for encoding and decoding and another type for representing that as an OptionSet.
enum APIOptions: String, Codable {
case optionA = "OptionA"
case optionB = "OptionB"
case optionC = "OptionC"
}
struct LocalOptions: OptionSet {
let rawValue: Int
static let optionA = LocalOptions(rawValue: 1 << 0)
static let optionB = LocalOptions(rawValue: 1 << 1)
static let optionC = LocalOptions(rawValue: 1 << 2)
}
extension LocalOptions {
init(_ apiOptions: [APIOptions]) {
...
}
var apiOptions: [APIOptions] {
...
}
}