mzaks / compact-dict

A fast and compact Dict implementation in Mojo 🔥
MIT License
32 stars 2 forks source link

Metaprogramming #2

Open StijnWoestenborghs opened 7 months ago

StijnWoestenborghs commented 7 months ago

Do you think you could support compile time metaprogramming? An example of how I would try to use it:

fn generate_dict() -> StringDict[Int]:
    var d = StringDict[Int]()
    d.put("a", 0)
    return d^

fn main():
    alias mydict = generate_dict()
    print(mydict.get("a", -1))

Note that the std Dict also doesn't support this yet. You are able to initialise one there but that is it. Not sure if this is aligned with the goals of your project. So feel free to close the issue if you want.

mzaks commented 7 months ago

Seems like compile time evaluation is quite volatile right now.

I tried something very simple and it still didn't work:

from collections import Optional

struct Dict[T: CollectionElement](Sized):
    var keys: DynamicVector[StringLiteral]
    var values: DynamicVector[T]

    fn __init__(inout self, *values: Tuple[StringLiteral, Int]):
        self.keys = DynamicVector[StringLiteral](len(values))
        self.values = DynamicVector[T](len(values))
        for i in range(len(values)):
            self.keys.push_back(values[i].get[0, StringLiteral]())
            self.values.push_back(values[i].get[1, Int]())

    fn __init__(inout self, *values: Tuple[StringLiteral, StringLiteral]):
        self.keys = DynamicVector[StringLiteral](len(values))
        self.values = DynamicVector[T](len(values))
        for i in range(len(values)):
            self.keys.push_back(values[i].get[0, StringLiteral]())
            self.values.push_back(values[i].get[1, StringLiteral]())

    fn __getitem__(self, key: String) raises -> T:
        for i in range(len(self.keys)):
            if String(self.keys[i]) == key:
                return self.values[i]
        raise String("No value for key ") + key

    fn __len__(self) -> Int:
        return len(self.keys)

fn main() raises:
    let ds = Dict[StringLiteral](("a", "this is it"))
    # alias ds = Dict[StringLiteral](("a", "this is it"))
    print("len", len(ds))
    print("a:", ds["a"])
    let di = Dict[Int](("a", 1), ("b", 2))
    # alias di = Dict[Int](("a", 1), ("b", 2))
    print("len", len(di))
    print("a:", di["a"])

So I would postpone this issue. Lets keep it around though.

StijnWoestenborghs commented 7 months ago

Yeah I have been doing this more and more lately and you have to be very careful to get this to work. Basically you need to make sure that all (or most?) the datatypes you are using are register passable. Having that as best practice is generally working for me. In this case your Tuple (StaticTuple might work) and/or StringLiteral is not. This example is working for me:

struct Dict[T: CollectionElement](Sized):
    var keys: DynamicVector[String]
    var values: DynamicVector[T]

    fn __init__(inout self):
        self.keys = DynamicVector[String]()
        self.values = DynamicVector[T]()

    fn put(inout self, key: String, value: T):
        self.keys.push_back(key)
        self.values.push_back(value)

    fn get(self, key: String, default: T) -> T:
        for i in range(len(self.keys)):
            if self.keys[i] == key:
                return self.values[i]
        return default

    fn __len__(self) -> Int:
        return len(self.keys)

    fn __moveinit__(inout self, owned other: Self):
        self.keys = other.keys^
        self.values = other.values^

fn create_dict() -> Dict[String]:
    var d = Dict[String]()
    d.put("a", "this is it")
    return d^

fn create_dict2() -> Dict[Int]:
    var d = Dict[Int]()
    d.put("b", 0)
    return d^

fn main():
    alias ds = create_dict()
    print("len", len(ds))
    print("a:", ds.get("a", "default"))

    alias di = create_dict2()
    print("len", len(di))
    print("b:", di.get("b", -1))

UPDATE: Only the Int version seems to be working consistently. You have to be very careful with Strings. Perhaps storing them as bytes internally is a better idea.