-
Notifications
You must be signed in to change notification settings - Fork 4
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
[RFC] Typing of ite #122
Comments
I think allowing 8 seems reasonable from a purely user facing perspective, it looks like natural code to write. From what I understand, the reason 6 and 7 work are an artifact of how magma handles muxing return values (on the individual components, rather than muxing the tuple itself). Supporting 8 would simply require magma to support muxing Python tuples, which itself doesn't seem to be the issue (this should be straightforward to add). I think the issue you're raising is the the typing rules are pretty unwieldy (specifically this: https://github.com/leonardt/hwtypes/blob/master/hwtypes/bit_vector_util.py#L12-L58) I think having the user annotate the tuple type (i.e. I'll ponder this some more, but I can't think of anything useful off the top of my head. Perhaps we can abstract the type coercion logic into the types themselves? Given two sides of ite, would determining the resulting logic be the same as the result of a binary operator? If so, could we just abstract that into a reusable function that resolves the type given to two compatible types? Not sure if that really helps, because that's essentially just the same code that's been written. |
You are correct.
No, even ignoring the tuple case we still have the problem that ite is different from all other operators because of an issue I didn't even raise. No only does it mux |
I vote for allowing muxing any magma type including tuples. I also vote for requiring the two types in the then and else clauses to be the same. That is, no coercion. Particular, no coercion of fields in a tuple. I also think all the if-then-else statement blocks should be consistent with ite. My queasiness for coercion is due to value preservation. If the types were a lattice, then parent types can hold all the values possible in the value types. And the semantics of the types would be the same. When you have a class hierarchy, you may not preserve values. Although uint is-a bitvector, uint has different semantics than bitvector. This leads to unpredicable behavior, for example bv & uint is a bv,. But what is bv + uint. If it is a bv, that seems weird because + is only technically defined on integers. And if it returned an uint because + is an operator on uint, then we need to convert bv to uint which seems dangerous. I am ok with coercing an integer constant to a type in our language. That seems more like type inference to me. But I was always happy to do it explicitly; we just got lots of complaints about that. I hope I understood the nuances here. Maybe we should talk in person. |
Do you mean including python tuples? Just need to disambiguate that term.
I would prefer to have no coercion as well but have been told that people complain if they are not allowed to write: What I don't want to do is special case
Technically no I would be happy to accept a PR which defined |
@phanrahan So any PR removing arithmetic operators from |
Comments.
|
Just to be clear @phanrahan excluding python tuple means excluding: # 8
@ssa
def foobar(cond: Bit) -> (BV, BV):
if cond:
r_val = bv1, bv2
else:
r_val = bv2, bv1
return r_val |
I'd be ok with coercing python tuples to magma tuples. My only worry is this issue is that it will start getting out of control. For example, are we going to continually be adding conversions that are less and less well-defined. |
Okay here is my issue with support through coercion into T = Tuple[BV[4], BV[4]]
S = Tuple[SBV[4], BV[4]]
# This could be False, which would fix my issue partially
# However the implementation would be pretty tricky
assert not issubclass(S, T)
def foo(cond: Bit):
if cond:
r_val = bv1, bv2
else:
r_val = sbv, bv1
return r_val gets transformed approximately to: def foo(cond: Bit):
r_val = cond.ite((bv1, bv2), (sbv, bv1))
# ite infers type T form (bv1, bv2)
# ite infers type S from (sbv, bv1)
# there is no way to unify the types so a TypeError will be raised
# but if we did resolve S a sublclass of T it would return a T
return r_val However: def bar(cond: Bit):
if cond:
r_val_0 = bv1
r_val_1 = bv2
else:
r_val_0 = sbv
r_val_1 = bv1
return r_val_0, r_val_1 get transformed approximately to: def bar(cond: Bit):
r_val_0 = cond.ite(bv1, sbv) # type is BV[4]
r_val_1 = cond.ite(bv2, bv1) # type is BV[4]
return r_val_0, r_val_1 # returns python tuple |
I want consistent and clear rules for user code. Either we allow python tuples or we don't. If we do you shouldn't have to use stupid code structuring tricks (like I think the rules become the most sane if we remove the coercion from def unify_types(true_branch, false_branch):
if (isinstance(true_branch, tuple)
and isinstance(false_branch, tuple)
and len(true_branch) == len(false_branch):
return tuple(unify_types(t, f) for t, f in zip(true_branch, false_branch))
elif isinstance(false_branch, type(true_branch)):
return type(true_branch)
elif isinstance(true_branch, type(false_branch)):
return type(false_branch)
else:
raise TypeError() Otherwise I think we should forbid the use of python tuples completely. |
I wish we could spend 15 minutes talking about this in person! Didn't I propose forcing the two side of ite be required to have the same type, i.e. no coercion? |
sure we can.
I am confused on what you mean by the same type. My proposal above does insure both sides have the same type. Do you mean you want to prevent subtype polymorphism? It's going to be very hard to convince me that instances of a child type should not be considered instances of their parent type. This is seperate from coercion. Coercion is implicitly calling constructors. Maybe it would be helpful if you could answer yes / no to my 8 original code snippets. |
I meant that ite(t,f) requires that the type(t) == type(f). |
I agree subtypoing does not imply coercion, but when you do an operation you are returning a new value of a different type. For example, if we call bits(uint) we are doing a coercion (IMO) because we make a new value of a different type. |
Would you say the following C++ program coerces anything: class BV {
protected:
int val;
public:
BV(int val) : val(val) {}
};
class SBV : public BV {
public:
SBV(int val) : BV(val) {}
};
class Bit {
protected:
bool val;
public:
Bit(bool val) : val(val) {}
BV* ite(BV* a, BV* b) { return (val ? a : b ); }
};
int main(int argc, char** argv) {
BV a = BV(1);
SBV b = SBV(2);
Bit c = Bit(false);
BV* d = c.ite(&a, &b);
} Because conceptually my proposal operates in the same way. It just infers all the type instead having them manually written. No new value is created. The type of of the objects never changes. A new reference with a potentially less specific type is created but that it isn't coercion. That's subtype polymorphism. Similarly, if |
Isn't that called object slicing? You need to "slice" off the parts of the child class. Object slicing is one of the trickiest parts of C++ from what I recall. Note there is a difference between casting a pointer and assigning a value. That is, these assignments to b and c are very different.
It is also dangerous to compare python to C++ ... Magma is a language on values. We don't have pointers or references. |
Well I had both object slicing and polymorphism. The ite example was polymorphism. The add example is object slicing. I should have passed references in the add example. SBV a = SBV(1);
BV b = a; // Object slicing, invokes the copy assignment operator, b holds a BV
BV *v = &a; // Polymorphism, v holds a pointer to a SBV
I am not comparing python to C++ I am comparing a strictly type language imbedded in python to C++. I chose C++ as its the strictly typed, supports polymorphism, and I know the syntax without needing reference. However my examples could be moved to a Java where we would then not need to worry about the object slicing vs polymorphism and pointers etc... There all assignments would be polymorphic. /* Java */
SBV a = new SBV(1)
BV b = a /* b references a */
I would argue it's a language of references. Its imbedded in python, there is only references in python. Now our types are immutable (unless you do naughty things, there is no such thing as immutability in python) so the distinction between value and reference is almost meaningless. However, strictly speaking variables hold references not values. def do_naughty(x: BV):
x._value_ = x._value_ + 1
return x
x = BV(0)
xid = id(x)
assert int(x) = 0
y = do_naughty(x)
assert x is y
assert id(y) == xid == id(x)
assert int(x) == 1 == int(y) # Clearly x holds a reference not a value |
I am just saying that when you assign a to b, you are doing a copy constructor. Which means you are converting types. It is not a reference to a child type which is an instance in a parent type. Object slicing is almost as evil as coercion. Let's continue this conversation outside of this issue. |
Sure but my not lazily written
We can but I think it's pretty relevant. I want to support polymorphism in PEak. You apparently do not want to have polymorphism in magma. |
As a user, I would very much prefer if python tuples worked naturally in the code. My impression from chatting with caleb is that the only reasonable way for 6/7/8 to work is to embed the python tuple logic within the ite, so I do not see any issue with that. In terms of whether the types of the elements of the tuple between both branches are exactly the same or if you allow subtyping relationship between them, I am indifferent. The unify_types rule as written above seems reasonable, but I would also be fine with forcing the element types do be identical. |
I think we should definitely allow tuples. The remaining issue is whether to allow different types in an ite. I am going to prepare a summary of the design issues. |
Currently the typing of
ite
is a mess and inconsistent with magma.Now before you all go looking at the mess I want you all to consider the following code snippets and tell me which of the following should be allowable syntax (otherwise you will never understand why
ite
is such a mess).Assume the following for all snippets:
Also keep in mind the following property:
Okay on to the code snippets
Currently, hwtypes with ast_tools rewrites* accepts all of these (if it doesn't there is a bug).
It would not be hard to convince me to drop 1 and 2, they are coercions. While it is consistent with other methods to coerce I am fine not coercing in
ite
and continuing to coerce in other methods (I would rather have no coercion at all)I feel very strongly that 3 is correct
sbv
is aBV[8]
so there is no reason it shouldn't work4 and 5 are (partly) the cause of current mess but I feel strongly that if 4 is valid and 3 is valid then logically so must be 5.
Finally, the last 3 cases are the reason why hwtypes accepts tuples. Currently magma combinational will work with 6 and 7 but not 8. This is because magma is quite clever about unpacking return values before it muxes (by using the returns annotation to determine if a return value is a tuple), it however cannot support the arbitrary muxing of tuples which is why 8 does not work. Hence to support 8 we must support 4**.
I find allowing 6 and 7 but not 8 to be pretty unintuitive and hence undesirable.
Now these requirements leads to the really messy typing rules for ITE. See:
https://github.com/leonardt/hwtypes/blob/master/hwtypes/bit_vector.py#L82
https://github.com/leonardt/hwtypes/blob/master/hwtypes/smt_bit_vector.py#L137
https://github.com/leonardt/hwtypes/blob/master/hwtypes/bit_vector_util.py
*
**
I tried to come up with an ast transformation that could robustly perform the necessary unpacking of tuples. However, all the solutions I came up with either required type annotations on ALL assignments (
r_val: (BV, BV) = ...
) or a static type inference engine (which would give us those annotations). Note as we a have dependent type system we cannot easily hack mypy (or any other static type checking tool that I am aware of) to generate annotations for us.The text was updated successfully, but these errors were encountered: