-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A little review #1
Comments
Oh, hi there. I never saw the notification about your reply on reddit. Thanks for checking my code out! I've written several versions of this system in other languages (python, elixir, J). It's sort of become one of the first things I take on after "hello world" when I'm learning a new language. The other implementations have always been so slow that they're basically just toys, though. So, this started out as another throwaway implementation. But I really like rust, and it's sort of become a little game for me to see how fast I can make it go... And at some point, I decided it might actually be worth sharing. Since then, I've mostly been working on bdd.rs, which is why it's the one with all the nice comments and everything else is still shrouded in mystery. :) This also started as part of another project in a private repo, and the most illuminating example code was still private. I just copied it over as examples/bdd-solve.rs. As to your notes: top level:
trait TBase In any case, the interface exposed in base.rs is very similar to the one in bdd.rs ... I really think these should be refactored into just one "base" trait, so that int.rs or the interactive shell (... which you haven't seen, but I just copied over to examples/bex-shell.rs) can work with bases of ASTs or BDDs or any of various other implementations. There are a bunch of interesting representations with different properties that work well for different tasks... ZDDs, algebraic normal form, maybe CNF (a normal form used in sat solvers)... As for nid()... Is it really a getter? A nid isn't exactly a property of a Base. I don't have a strong opinion about the name, though. struct Base base::Op The two-argument ones are hopefully self-explanatory. They're the bitwise operators you most often find in programming languages... I kind of want to have one type that uses this small subset of the 16 two-argument boolean operators, and another type that offers all of them as an optimization... So that Not(Xor(x,y)) becomes Eq(x,y), etc. Likewise, there are 256 three-argument boolean functions, but only a couple I care about:
Anyway, sorry for the wall of text. Thanks for the prod to add those examples. I'd love to hear a little more about your project and what you're using all this for. :) |
Thanks for this detailed answer! I wrote my own version of a boolean ast for my project. I only needed to construct the ast and then convert it to cnf. I looked for a library that could do that, but couldn't find one. Because you asked: I tried to find sha256 collisions with a SAT solver ;) It's not clear to me what an idiomatic API for boolean expressions would look like. A small API surface would be good. That is: A trait for the Base + at least on implementor of that trait and a trait for a boolean expression + at least one implementor. Base would have methods to create fresh Literals etc. and boolean expressions should be composable to more boolean expressions. It remains an open question if the composition part would need a reference to the Base or not. The latter would be more user friendly, while the former allows more representations and optimizations... Also, as you said, there are infinite operators, like maj etc. Enums are inherently inextensible. Maybe it makes more sense to use a trait and trait objects for operators. That would be quite similar to a class hierarchy in cpp, with virtual fn calls. |
I think at the very least, the interface would have all boolean operators that rust the language provides, and perhaps even all boolean operators of two arguments, as well as a handful of the common 3-argument ones. But these could all have a default implementation in terms of and/xor/not or if-then-else. The way I pictured it, each sort of base would have an associated NID type... These could be just integers in the simplest case (like base.rs) or more complicated structures. For bdd.rs, it's conceptually a struct with various meta-information about the node so that for many operations, the node itself never has to be looked up from memory. (I actually used a struct at one point, but it turned out to be faster to use a bit-packed u64). Regarding the composition part needing the base: if you look in int.rs, you'll see there's a compound struct called a BaseBit -- a (BaseRef, NID) pair. This allows you to implement all the standard rust operators on the values. So you can use that fancy overloaded syntax if you want, or you can just call base methods on individual NIDs. The next higher up abstraction is an array of these bits that act like integers. It's funny you mentioned SHA256, because that was also my original use case: I implemented sha256 for u32, and then once I had that working and tested, passed in my x32 type instead.
I'm not sure I understand this. Can you elaborate? |
+1 NID as associated type on base makes sense. I also did it that way. Having Bit and BaseBit is elegant, sounds great! For the first prototype BaseBit can be left out.
Hah, I did the same. However, I'd envision a boolean only lib at first. Bitvectors could live in their own crate. Regarding trait objects: If you define an enum with a variant for each operator, users can't add new operators, because they can't add enum variants. The solution is to use a trait instead. Users can create their own types for custom operators and implement the trait for them. There is a drawback though: The nodes would need to store custom operators as trait objects. It can be made quite efficient by having an enum variant for custom operators (as trait object). A trait object is a pointer to a struct or enum and a pointer to a virtual function table. It hides the memory layout of an implementor of a trait, allowing users to call methods of the trait anyway. Virtual dispatch is used, which makes performance worse compared to direct method calls. |
Hey! We were chatting about bex on reddit the other day. I glimpsed over the docs and code of the base module and took some notes I wanted to share. I really appreciate you writing this crate. It is already quite a large code base and a useful one. I want to help you polish it a little by sending PRs. This feedback is intended to be the start of a little discussion about what to refactor.
In general: The rust api guidelines are a good resource to design an idiomatic and reusable rust crate.
Bex notes
top level
trait TBase
struct Base
base::Op
The text was updated successfully, but these errors were encountered: