or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

conversions.mdgeneric.mdhlist.mdhmap.mdindex.mdlift.mdnat.mdpoly.mdrecords.mdsized.mdsybclass.mdtypeable.mdtypeoperators.md

hmap.mddocs/

0

# Heterogeneous Maps (HMap)

1

2

Heterogeneous maps (HMap) in shapeless provide type-safe maps where keys and values can have different types, with the relationships between key types and value types enforced at compile time through a type-level relation.

3

4

## Core Type

5

6

### HMap Definition

7

8

```scala { .api }

9

/**

10

* Heterogeneous map with type-level key/value associations fixed by relation R

11

* Also extends Poly, making HMaps polymorphic function values

12

*/

13

class HMap[R[_, _]](underlying: Map[Any, Any] = Map.empty) extends Poly {

14

def get[K, V](k: K)(implicit ev: R[K, V]): Option[V]

15

def +[K, V](kv: (K, V))(implicit ev: R[K, V]): HMap[R]

16

def -[K](k: K): HMap[R]

17

}

18

```

19

20

### HMap Factory

21

22

```scala { .api }

23

object HMap {

24

def apply[R[_, _]] = new HMapBuilder[R]

25

def empty[R[_, _]] = new HMap[R]

26

def empty[R[_, _]](underlying: Map[Any, Any]) = new HMap[R]

27

}

28

```

29

30

## Type-Level Relations

31

32

HMaps are parameterized by a type-level relation `R[_, _]` that constrains which key types can be associated with which value types.

33

34

**Usage Examples:**

35

36

```scala

37

import shapeless._

38

39

// Define a relation: Strings map to Ints, Ints map to Strings

40

class BiMapIS[K, V]

41

implicit val stringToInt = new BiMapIS[String, Int]

42

implicit val intToString = new BiMapIS[Int, String]

43

44

// Create HMap with this relation

45

val hm = HMap[BiMapIS](23 -> "foo", "bar" -> 13)

46

47

// Type-safe access - return types are inferred

48

val s1: Option[String] = hm.get(23) // Some("foo")

49

val i1: Option[Int] = hm.get("bar") // Some(13)

50

51

// This would not compile - violates the relation:

52

// val hm2 = HMap[BiMapIS](23 -> "foo", 23 -> 13) // Error: 23 can't map to Int

53

```

54

55

## Basic Operations

56

57

### Get Operation

58

59

```scala { .api }

60

/**

61

* Type-safe retrieval - return type determined by relation R

62

*/

63

def get[K, V](k: K)(implicit ev: R[K, V]): Option[V]

64

```

65

66

**Usage Examples:**

67

68

```scala

69

import shapeless._

70

71

// Relation for demo: Symbols map to their string names

72

class SymbolNames[K, V]

73

implicit val symbolToString = new SymbolNames[Symbol, String]

74

75

val syms = HMap[SymbolNames]('name -> "Alice", 'age -> "30", 'active -> "true")

76

77

val name: Option[String] = syms.get('name) // Some("Alice")

78

val age: Option[String] = syms.get('age) // Some("30")

79

val missing: Option[String] = syms.get('missing) // None

80

81

// This would not compile - wrong type association:

82

// val invalid: Option[Int] = syms.get('name) // Error: Symbol doesn't map to Int

83

```

84

85

### Add Operation

86

87

```scala { .api }

88

/**

89

* Add key-value pair (type-safe according to relation R)

90

*/

91

def +[K, V](kv: (K, V))(implicit ev: R[K, V]): HMap[R]

92

```

93

94

**Usage Examples:**

95

96

```scala

97

import shapeless._

98

99

// Relation: different numeric types map to their string representations

100

class NumericToString[K, V]

101

implicit val intToString = new NumericToString[Int, String]

102

implicit val doubleToString = new NumericToString[Double, String]

103

implicit val longToString = new NumericToString[Long, String]

104

105

val empty = HMap.empty[NumericToString]

106

val hm1 = empty + (42 -> "forty-two")

107

val hm2 = hm1 + (3.14 -> "pi")

108

val hm3 = hm2 + (999L -> "nine-nine-nine")

109

110

val intVal: Option[String] = hm3.get(42) // Some("forty-two")

111

val doubleVal: Option[String] = hm3.get(3.14) // Some("pi")

112

val longVal: Option[String] = hm3.get(999L) // Some("nine-nine-nine")

113

```

114

115

### Remove Operation

116

117

```scala { .api }

118

/**

119

* Remove key from map

120

*/

121

def -[K](k: K): HMap[R]

122

```

123

124

**Usage Examples:**

125

126

```scala

127

import shapeless._

128

129

class StringToAny[K, V]

130

implicit def stringToAny[V] = new StringToAny[String, V]

131

132

val hm = HMap[StringToAny]("name" -> "Bob", "age" -> 30, "active" -> true)

133

val withoutAge = hm - "age"

134

135

val name: Option[String] = withoutAge.get("name") // Some("Bob")

136

val age: Option[Int] = withoutAge.get("age") // None

137

val active: Option[Boolean] = withoutAge.get("active") // Some(true)

138

```

139

140

## Polymorphic Function Integration

141

142

Since HMap extends Poly, it can be used as a polymorphic function:

143

144

```scala { .api }

145

/**

146

* Implicit conversion makes HMap act as polymorphic function

147

*/

148

implicit def caseRel[K, V](implicit ev: R[K, V]) = Case1Aux[HMap[R], K, V](k => get(k).get)

149

```

150

151

**Usage Examples:**

152

153

```scala

154

import shapeless._

155

156

class ConfigMap[K, V]

157

implicit val stringToInt = new ConfigMap[String, Int]

158

implicit val stringToString = new ConfigMap[String, String]

159

implicit val stringToBool = new ConfigMap[String, Boolean]

160

161

val config = HMap[ConfigMap](

162

"port" -> 8080,

163

"host" -> "localhost",

164

"debug" -> true

165

)

166

167

// Use as polymorphic function with HList

168

val keys = "port" :: "host" :: "debug" :: HNil

169

val values = keys.map(config) // 8080 :: "localhost" :: true :: HNil

170

171

// Individual function calls

172

val port: Int = config("port") // 8080 (unsafe - throws if missing)

173

val host: String = config("host") // "localhost"

174

val debug: Boolean = config("debug") // true

175

```

176

177

## Advanced Usage Patterns

178

179

### Type-safe Configuration

180

181

```scala

182

import shapeless._

183

184

// Define configuration relation

185

trait ConfigKey[T] { type Out = T }

186

case object Port extends ConfigKey[Int]

187

case object Host extends ConfigKey[String]

188

case object Debug extends ConfigKey[Boolean]

189

case object Timeout extends ConfigKey[Long]

190

191

class Config[K <: ConfigKey[_], V]

192

implicit def configRelation[K <: ConfigKey[V], V] = new Config[K, V]

193

194

val config = HMap[Config](

195

Port -> 9000,

196

Host -> "api.example.com",

197

Debug -> false,

198

Timeout -> 30000L

199

)

200

201

val port: Option[Int] = config.get(Port) // Some(9000)

202

val host: Option[String] = config.get(Host) // Some("api.example.com")

203

val debug: Option[Boolean] = config.get(Debug) // Some(false)

204

205

// Update configuration

206

val devConfig = config + (Debug -> true) + (Port -> 3000)

207

```

208

209

### Multi-type Registry

210

211

```scala

212

import shapeless._

213

214

// Registry relation - classes map to their instances

215

class Registry[K, V]

216

implicit def classToInstance[T] = new Registry[Class[T], T]

217

218

trait Service

219

class DatabaseService extends Service

220

class EmailService extends Service

221

class LoggingService extends Service

222

223

val services = HMap[Registry](

224

classOf[DatabaseService] -> new DatabaseService,

225

classOf[EmailService] -> new EmailService,

226

classOf[LoggingService] -> new LoggingService

227

)

228

229

def getService[T](implicit tag: Class[T]): Option[T] = services.get(tag)

230

231

val dbService: Option[DatabaseService] = getService[DatabaseService]

232

val emailService: Option[EmailService] = getService[EmailService]

233

```

234

235

### Witness Relations

236

237

```scala

238

import shapeless._

239

240

// Use singleton types as witnesses for relations

241

trait Witness[K, V]

242

243

val dbKey = "database"

244

val cacheKey = "cache"

245

val queueKey = "queue"

246

247

implicit val dbWitness = new Witness[dbKey.type, DatabaseConfig]

248

implicit val cacheWitness = new Witness[cacheKey.type, CacheConfig]

249

implicit val queueWitness = new Witness[queueKey.type, QueueConfig]

250

251

case class DatabaseConfig(host: String, port: Int)

252

case class CacheConfig(maxSize: Int, ttl: Long)

253

case class QueueConfig(workers: Int, timeout: Long)

254

255

val systemConfig = HMap[Witness](

256

dbKey -> DatabaseConfig("localhost", 5432),

257

cacheKey -> CacheConfig(1000, 3600),

258

queueKey -> QueueConfig(4, 30000)

259

)

260

261

val dbConfig: Option[DatabaseConfig] = systemConfig.get(dbKey)

262

val cacheConfig: Option[CacheConfig] = systemConfig.get(cacheKey)

263

```

264

265

### Building HMaps

266

267

```scala

268

import shapeless._

269

270

// HMapBuilder provides convenient construction

271

class TypeMapping[K, V]

272

implicit val stringToInt = new TypeMapping[String, Int]

273

implicit val stringToString = new TypeMapping[String, String]

274

implicit val stringToBool = new TypeMapping[String, Boolean]

275

276

// Use apply method for building

277

val builder = HMap[TypeMapping]

278

val hmap = builder("count" -> 100, "name" -> "test", "enabled" -> true)

279

280

// Chain operations

281

val extended = hmap + ("timeout" -> 5000) - "count"

282

```

283

284

HMaps provide a powerful abstraction for type-safe heterogeneous data storage while maintaining the benefits of compile-time type checking and enabling sophisticated type-level programming patterns.