Dictionary with types as keys and different type values?

I have some code where I want to create a Dictionary of document "styles", similar to CSS. I'll have keys like "font size", "font weight", "background color", "font family", etc. But the values for those keys are different types (Float, String, Color, etc). I'm wondering about the best way to do this, and if there is an Swifty idiom for this situation.

One option is that I could make things dynamic.

var styles: [StyleKey: Any]
enum StyleKey {
    case fontSize, fontWeight, ...
}

But taking some inspiration from the PreferenceKey type in SwiftUI, I could try something more type-safe:

protocol StyleKey {
    associatedtype Value
}
struct FontSize: StyleKey {
    typealias Value = Float
}
// structs for other keys

The keys would be things like FontSize.self, and I could have lookup and setter functions like:

func getStyle<K: StyleKey>(key: K.Type) -> K.Value { ... }
func setStyle<K: StyleKey>(key: K.Type, value: K.Value) { ... }

But I'm not sure what the underlying dictionary type would be or how to implement that. Things like this do not compile.

var dict1: [StyleKey.Type: Any] = [:]
var dict2: [Any.Type: Any] = [:]

What could I use as a hashable dictionary key in the implementations of getStyle and setStyle?

You could use ObjectIdentifier, which you can construct with objects as well as Any.Type values.

1 Like

enum with associated values?

2 Likes

If your set of keys is known ahead of time, what you’re looking for is just a struct

struct Style {
    var fontSize: CGColor?
    var color: Color?
    …
}
4 Likes

Thanks, I'll think about this. A possible downside is that it could take too much memory space. I don't have many keys, yet. (I'm not re-implementing all of CSS.) But I have styles attached to every node of a document tree, so if the key count gets higher, there will be a lot of nils in these structs.

You should follow @tera's suggestion: the style value can be an enum with associated values, and the dictionary contains enums with the appropriate values. Having a struct with every possible value is wasteful.

Note there's significant memory and CPU consumption overhead in any dynamic structure like Dictionary. In case of dictionary beside anything else there'll be a minimal hash table size. A simpler "struct Style" outlined by @Nickolas_Pohilets can easily win both by memory and by CPU consumption.

1 Like

In this case, what is the type of Dictionary? Do you have one enum for keys, and another enum for the values? If I do something like this below, it's possible to pass mismatched keys and values. That's not the end of the world; but I'm trying to understand what you mean exactly.

enum StyleKey { case fontSize, fontColor, ... }
enum StyleValue { case fontSize(Float), fontColor(RGBColor), ... }
func setStyle(key: StyleKey, value: StyleValue) { ... }
setStyle(key: .fontSize, value: .fontColor(red))

enum with associated value is anti-OO pattern. It will need switch/case any where the enum value is operated on. There must be better option. Look into AttributedString to get some inspiration. It used DynamicMemberLookup. So you may not even need your dictionary. Let’s you set foregroundColor, backgroundColor, font etc. It has many different styling options thats extensible, it’s even user configurable(scriptable) with json5 syntax.

Can you elaborate on this? What's wrong with switch and case? Enumerations with associated values are very useful and the feature is loved by many programmers[1][2]. Types like Optional and Result are enumerations with associated values.

If you look at the actual source code for AttributedString (at least, the swift-corelibs-foundation version), you'll see that AttributedString internally contains an array of Int and [String: Any] to store attributes.


  1. Enumerating The Ways I Love Swift Enumerations — Liss is More ↩︎

  2. https://twitter.com/hollyborla/status/1491470708240633856 ↩︎

1 Like

In this case, what is the type of Dictionary? Do you have one enum for keys, and another enum for the values?

Yes: a simple enum without any associated values for the keys, and an enum with associated values for the values. You can use dictionary subscripts to access the values.

The type of Dictionary would be something like [StyleKey : StyleValue]

if you use OO, you don't need to use switch/case, that's all. I'm not saying something is wrong with switch/case.

A recent blog post by @ole may provide some guidance here: A heterogeneous dictionary with strong types in Swift – Ole Begemann

4 Likes

Thanks for the link @Jon_Shier. I saw this thread yesterday while I was already working on the article. Nice timing.

1 Like

not necessarily. there are hundreds of possible CSS property keys and if only a few are set at any given time, a Dictionary-based representation may easily win in terms of memory efficiency.

4 Likes