1defmodule Dict do 2 @moduledoc ~S""" 3 Generic API for dictionaries. 4 5 If you need a general dictionary, use the `Map` module. 6 If you need to manipulate keyword lists, use `Keyword`. 7 8 To convert maps into keywords and vice-versa, use the 9 `new` function in the respective modules. 10 """ 11 12 @moduledoc deprecated: "Use Map or Keyword modules instead" 13 14 @type key :: any 15 @type value :: any 16 @type t :: list | map 17 18 message = 19 "Use the Map module for working with maps or the Keyword module for working with keyword lists" 20 21 defmacro __using__(_) do 22 # Use this import to guarantee proper code expansion 23 import Kernel, except: [size: 1] 24 25 if __CALLER__.module != HashDict do 26 IO.warn("use Dict is deprecated. " <> unquote(message), Macro.Env.stacktrace(__CALLER__)) 27 end 28 29 quote do 30 message = "Use maps and the Map module instead" 31 32 @deprecated message 33 def get(dict, key, default \\ nil) do 34 case fetch(dict, key) do 35 {:ok, value} -> value 36 :error -> default 37 end 38 end 39 40 @deprecated message 41 def get_lazy(dict, key, fun) when is_function(fun, 0) do 42 case fetch(dict, key) do 43 {:ok, value} -> value 44 :error -> fun.() 45 end 46 end 47 48 @deprecated message 49 def get_and_update(dict, key, fun) do 50 current_value = get(dict, key) 51 {get, new_value} = fun.(current_value) 52 {get, put(dict, key, new_value)} 53 end 54 55 @deprecated message 56 def fetch!(dict, key) do 57 case fetch(dict, key) do 58 {:ok, value} -> value 59 :error -> raise KeyError, key: key, term: dict 60 end 61 end 62 63 @deprecated message 64 def has_key?(dict, key) do 65 match?({:ok, _}, fetch(dict, key)) 66 end 67 68 @deprecated message 69 def put_new(dict, key, value) do 70 case has_key?(dict, key) do 71 true -> dict 72 false -> put(dict, key, value) 73 end 74 end 75 76 @deprecated message 77 def put_new_lazy(dict, key, fun) when is_function(fun, 0) do 78 case has_key?(dict, key) do 79 true -> dict 80 false -> put(dict, key, fun.()) 81 end 82 end 83 84 @deprecated message 85 def drop(dict, keys) do 86 Enum.reduce(keys, dict, &delete(&2, &1)) 87 end 88 89 @deprecated message 90 def take(dict, keys) do 91 Enum.reduce(keys, new(), fn key, acc -> 92 case fetch(dict, key) do 93 {:ok, value} -> put(acc, key, value) 94 :error -> acc 95 end 96 end) 97 end 98 99 @deprecated message 100 def to_list(dict) do 101 reduce(dict, {:cont, []}, fn kv, acc -> {:cont, [kv | acc]} end) 102 |> elem(1) 103 |> :lists.reverse() 104 end 105 106 @deprecated message 107 def keys(dict) do 108 reduce(dict, {:cont, []}, fn {k, _}, acc -> {:cont, [k | acc]} end) 109 |> elem(1) 110 |> :lists.reverse() 111 end 112 113 @deprecated message 114 def values(dict) do 115 reduce(dict, {:cont, []}, fn {_, v}, acc -> {:cont, [v | acc]} end) 116 |> elem(1) 117 |> :lists.reverse() 118 end 119 120 @deprecated message 121 def equal?(dict1, dict2) do 122 # Use this import to avoid conflicts in the user code 123 import Kernel, except: [size: 1] 124 125 case size(dict1) == size(dict2) do 126 false -> 127 false 128 129 true -> 130 reduce(dict1, {:cont, true}, fn {k, v}, _acc -> 131 case fetch(dict2, k) do 132 {:ok, ^v} -> {:cont, true} 133 _ -> {:halt, false} 134 end 135 end) 136 |> elem(1) 137 end 138 end 139 140 @deprecated message 141 def merge(dict1, dict2, fun \\ fn _k, _v1, v2 -> v2 end) do 142 # Use this import to avoid conflicts in the user code 143 import Kernel, except: [size: 1] 144 145 if size(dict1) < size(dict2) do 146 reduce(dict1, {:cont, dict2}, fn {k, v1}, acc -> 147 {:cont, update(acc, k, v1, &fun.(k, v1, &1))} 148 end) 149 else 150 reduce(dict2, {:cont, dict1}, fn {k, v2}, acc -> 151 {:cont, update(acc, k, v2, &fun.(k, &1, v2))} 152 end) 153 end 154 |> elem(1) 155 end 156 157 @deprecated message 158 def update(dict, key, default, fun) do 159 case fetch(dict, key) do 160 {:ok, value} -> 161 put(dict, key, fun.(value)) 162 163 :error -> 164 put(dict, key, default) 165 end 166 end 167 168 @deprecated message 169 def update!(dict, key, fun) do 170 case fetch(dict, key) do 171 {:ok, value} -> 172 put(dict, key, fun.(value)) 173 174 :error -> 175 raise KeyError, key: key, term: dict 176 end 177 end 178 179 @deprecated message 180 def pop(dict, key, default \\ nil) do 181 case fetch(dict, key) do 182 {:ok, value} -> 183 {value, delete(dict, key)} 184 185 :error -> 186 {default, dict} 187 end 188 end 189 190 @deprecated message 191 def pop_lazy(dict, key, fun) when is_function(fun, 0) do 192 case fetch(dict, key) do 193 {:ok, value} -> 194 {value, delete(dict, key)} 195 196 :error -> 197 {fun.(), dict} 198 end 199 end 200 201 @deprecated message 202 def split(dict, keys) do 203 Enum.reduce(keys, {new(), dict}, fn key, {inc, exc} = acc -> 204 case fetch(exc, key) do 205 {:ok, value} -> 206 {put(inc, key, value), delete(exc, key)} 207 208 :error -> 209 acc 210 end 211 end) 212 end 213 214 defoverridable merge: 2, 215 merge: 3, 216 equal?: 2, 217 to_list: 1, 218 keys: 1, 219 values: 1, 220 take: 2, 221 drop: 2, 222 get: 2, 223 get: 3, 224 fetch!: 2, 225 has_key?: 2, 226 put_new: 3, 227 pop: 2, 228 pop: 3, 229 split: 2, 230 update: 4, 231 update!: 3, 232 get_and_update: 3, 233 get_lazy: 3, 234 pop_lazy: 3, 235 put_new_lazy: 3 236 end 237 end 238 239 defmacrop target(dict) do 240 quote do 241 case unquote(dict) do 242 %module{} -> module 243 %{} -> Map 244 dict when is_list(dict) -> Keyword 245 dict -> unsupported_dict(dict) 246 end 247 end 248 end 249 250 @deprecated message 251 @spec keys(t) :: [key] 252 def keys(dict) do 253 target(dict).keys(dict) 254 end 255 256 @deprecated message 257 @spec values(t) :: [value] 258 def values(dict) do 259 target(dict).values(dict) 260 end 261 262 @deprecated message 263 @spec size(t) :: non_neg_integer 264 def size(dict) do 265 target(dict).size(dict) 266 end 267 268 @deprecated message 269 @spec has_key?(t, key) :: boolean 270 def has_key?(dict, key) do 271 target(dict).has_key?(dict, key) 272 end 273 274 @deprecated message 275 @spec get(t, key, value) :: value 276 def get(dict, key, default \\ nil) do 277 target(dict).get(dict, key, default) 278 end 279 280 @deprecated message 281 @spec get_lazy(t, key, (() -> value)) :: value 282 def get_lazy(dict, key, fun) do 283 target(dict).get_lazy(dict, key, fun) 284 end 285 286 @deprecated message 287 @spec get_and_update(t, key, (value -> {value, value})) :: {value, t} 288 def get_and_update(dict, key, fun) do 289 target(dict).get_and_update(dict, key, fun) 290 end 291 292 @deprecated message 293 @spec fetch(t, key) :: value 294 def fetch(dict, key) do 295 target(dict).fetch(dict, key) 296 end 297 298 @deprecated message 299 @spec fetch!(t, key) :: value 300 def fetch!(dict, key) do 301 target(dict).fetch!(dict, key) 302 end 303 304 @deprecated message 305 @spec put(t, key, value) :: t 306 def put(dict, key, val) do 307 target(dict).put(dict, key, val) 308 end 309 310 @deprecated message 311 @spec put_new(t, key, value) :: t 312 def put_new(dict, key, val) do 313 target(dict).put_new(dict, key, val) 314 end 315 316 @deprecated message 317 @spec put_new_lazy(t, key, (() -> value)) :: t 318 def put_new_lazy(dict, key, fun) do 319 target(dict).put_new_lazy(dict, key, fun) 320 end 321 322 @deprecated message 323 @spec delete(t, key) :: t 324 def delete(dict, key) do 325 target(dict).delete(dict, key) 326 end 327 328 @deprecated message 329 @spec merge(t, t) :: t 330 def merge(dict1, dict2) do 331 target1 = target(dict1) 332 target2 = target(dict2) 333 334 if target1 == target2 do 335 target1.merge(dict1, dict2) 336 else 337 do_merge(target1, dict1, dict2, fn _k, _v1, v2 -> v2 end) 338 end 339 end 340 341 @deprecated message 342 @spec merge(t, t, (key, value, value -> value)) :: t 343 def merge(dict1, dict2, fun) do 344 target1 = target(dict1) 345 target2 = target(dict2) 346 347 if target1 == target2 do 348 target1.merge(dict1, dict2, fun) 349 else 350 do_merge(target1, dict1, dict2, fun) 351 end 352 end 353 354 defp do_merge(target1, dict1, dict2, fun) do 355 Enumerable.reduce(dict2, {:cont, dict1}, fn {k, v}, acc -> 356 {:cont, target1.update(acc, k, v, fn other -> fun.(k, other, v) end)} 357 end) 358 |> elem(1) 359 end 360 361 @deprecated message 362 @spec pop(t, key, value) :: {value, t} 363 def pop(dict, key, default \\ nil) do 364 target(dict).pop(dict, key, default) 365 end 366 367 @deprecated message 368 @spec pop_lazy(t, key, (() -> value)) :: {value, t} 369 def pop_lazy(dict, key, fun) do 370 target(dict).pop_lazy(dict, key, fun) 371 end 372 373 @deprecated message 374 @spec update!(t, key, (value -> value)) :: t 375 def update!(dict, key, fun) do 376 target(dict).update!(dict, key, fun) 377 end 378 379 @deprecated message 380 @spec update(t, key, value, (value -> value)) :: t 381 def update(dict, key, default, fun) do 382 target(dict).update(dict, key, default, fun) 383 end 384 385 @deprecated message 386 @spec split(t, [key]) :: {t, t} 387 def split(dict, keys) do 388 target(dict).split(dict, keys) 389 end 390 391 @deprecated message 392 @spec drop(t, [key]) :: t 393 def drop(dict, keys) do 394 target(dict).drop(dict, keys) 395 end 396 397 @deprecated message 398 @spec take(t, [key]) :: t 399 def take(dict, keys) do 400 target(dict).take(dict, keys) 401 end 402 403 @deprecated message 404 @spec empty(t) :: t 405 def empty(dict) do 406 target(dict).empty(dict) 407 end 408 409 @deprecated message 410 @spec equal?(t, t) :: boolean 411 def equal?(dict1, dict2) do 412 target1 = target(dict1) 413 target2 = target(dict2) 414 415 cond do 416 target1 == target2 -> 417 target1.equal?(dict1, dict2) 418 419 target1.size(dict1) == target2.size(dict2) -> 420 Enumerable.reduce(dict2, {:cont, true}, fn {k, v}, _acc -> 421 case target1.fetch(dict1, k) do 422 {:ok, ^v} -> {:cont, true} 423 _ -> {:halt, false} 424 end 425 end) 426 |> elem(1) 427 428 true -> 429 false 430 end 431 end 432 433 @deprecated message 434 @spec to_list(t) :: list 435 def to_list(dict) do 436 target(dict).to_list(dict) 437 end 438 439 @spec unsupported_dict(t) :: no_return 440 defp unsupported_dict(dict) do 441 raise ArgumentError, "unsupported dict: #{inspect(dict)}" 442 end 443end 444