Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Here is a fun challenge for anyone who thinks "it can't be that bad": Try to implement a TwoWayDict, an AbstractDict where if d[a] = b, then d[b] = a

I've done the exercise, let me walk you through it.

First, there is no inheritance of concrete types, only composition, so I wrote a wrapper:

    struct TwoWayDict{K, V, D <: AbstractDict{K,V}} <: AbstractDict{K,V}
        d::D
    end
How do I know this signature? By looking at

    julia> supertypes(typeof(Dict("a"=>1)))
    (Dict{String, Int64}, AbstractDict{String, Int64}, Any)
and the {K, V, D <: AbstractDict{K,V}} pattern is a well-known trick in julia.

Then I tried to instantiate a TwoWayDict in the REPL, and it complains about iteration support, so I implement that:

    julia> d = TwoWayDict(Dict{String,String}())
    ERROR: MethodError: no method matching iterate(::TwoWayDict{String, String, Dict{String, String}})

    julia> import Base: iterate
    
    julia> iterate(x::TwoWayDict) = iterate(x.d)
    
    julia> iterate(x::TwoWayDict, s) = iterate(x.d, s)
    
    julia> d = TwoWayDict(Dict{String,String}())
    TwoWayDict{String, String, Dict{String, String}}()

How about setting a value?

    julia> d["a"] = "b"
    ERROR: MethodError: no method matching setindex!(::TwoWayDict{String, String, Dict{String, String}}, ::String, ::String)
Okay, so let's implement that

    import Base: setindex!

    function setindex!(d, key, value)
        d.d[value] = key
        d.d[key] = value
    end

    julia> d["a"] = "b"
    "b"

    julia> d
    Error showing value of type TwoWayDict{String, String, Dict{String, String}}:
    ERROR: MethodError: no method matching length(::TwoWayDict{String, String, Dict{String, String}})
Hm, so length is missing, let's add that

    julia> import Base: length
    
    julia> length(x::TwoWayDict) = length(x.d)

    julia> d
    TwoWayDict{String, String, Dict{String, String}} with 2 entries:
      "b" => "a"
      "a" => "b"
Ok great. How about retrieving a value:

    julia> d["a"]
    ERROR: MethodError: no method matching get(::TwoWayDict{String, String, Dict{String, String}}, ::String, ::Symbol)
Hm, okay, 3 arguments?! Let's see how it works for Dict:

    julia> get(d::Dict, [tab][tab]

    get(h::Dict{K, V}, key, default) where {K, V} in Base at dict.jl:504
Ok so

    julia> import Base: get

    julia> get(d::TwoWayDict, x, default) = get(d.d, x, default)

    julia> d["a"]
    "b"
---

It should be documented better, but without leaving the repl it's not that difficult to implement the basics, just some function forwarding really.

Now if you ask: did you implement the full interface? I don't know [1], but I do like that julia doesn't force me to implement things I don't need in my code. By not being strict about interfaces, julia solves the expression problem [2], and in some cases this is very useful. E.g. if I want to create a number type and I only want to implement +, -, *, /, and potentially conversion and promotion, then I can do that, but I don't have to worry about exponentiation, trig functions, or whatever people think is encapsulated by the notion of number.

[1] Well, I forgot to write a constructor to enforce the d[a] = b and d[b] = a invariant on instantiation, and there's no support for deletion by key.

[2] https://en.wikipedia.org/wiki/Expression_problem



This is one of those comments that’s so instructive I’m going to save it, because I’ll probably want to refer to it in the future. Thanks for taking the trouble.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: