Hi! I was using 'dynamic' in a very fast and loose sense based on the issues I read and vyper's surface similarity to python. But after reading more of the vyper source and testing it out, I found that vyper is much more static than I originally thought.
For instance, based on the following issue ethereum/vyper#582, it looked like
[ 1, 2.0, 3 ] was valid vyper. (allowing mixed type lists would result in a dynamically typed language because you can't determine the list element types at compile time). When i went to test it out though, and reading through this channel's history, it looks like mixed typed lists don't compile.
Another thing I noticed was about the semantics of
None. It seems that
None inhabits every type - but at least you can't have a
None with underspecified type (e.g.
foo = None is an invalid declaration of
foo). Reading through some IR output though it seems
None is just an alias for
0, which seems typesafe but obfuscating.
My comment about dynamic execution was based on ethereum/vyper#590, as well as vyper's surface similarity to python (which allows code rewriting at runtime). But I see after playing with vyper some more that block-local functions (or really any other mechanism which could result in contract-local looping/recursion) are disallowed. This is good because it improves the ability to statically trace execution! Now correct me if I'm wrong here but does this mean vyper is not Turing complete? Or is there still a back door to recursion through remote calls via
So with the caveat that control flow is a very tricky design area, I think static analysis would be improved further though by requiring all if statements to have both branches and disallowing short-circuiting / remote calls except in very explicit contexts. For instance in Haskell, 'short-circuiting' basically requires a monadic context in order to happen. I think having a limited effects system (which decorators already provide some mileage towards) could help solve a lot of these problems.
For instance, I was thinking about ethereum/vyper#590 and it seems that the issue is stemming from having two separate 'effect systems' for functions-which-don't-return-anything and functions-which-return-something. If they had been reified into a single type, making
pass an alias for
return void (and disallowing signatures like
def foo(), instead requiring
def foo() -> void), then of course
def foo() -> num couldn't end in
return void) because that wouldn't type check.
It's just an idea - in practice it would probably be too draconian to require users to add
return void everywhere, although perhaps functions could be annotated with default return values.
voidreturn type could be solved by implicit assumption in an intuitive and safe way
else passimplicitly is okay from a static analysis perspective
return [void](you can call return without any type to output) when no return exists in the function is also pretty okay
@fubuloubu how do you feel about forcing each branch of an if statement to have the same effect type? Take for instance the following example
def foo (x: num) : if x < 2 : x = 2 # modifies locally scoped variable, call it the 'locally scoped mutation effect' elif x < 10 : self.balances[self.owner] = x # modifies contract state, call it the 'contract mutation effect' elif x < 15 : return 1 # exits the function, call it the 'control flow effect' else : raw_call(stuff) # calls to external address, call it the 'external call effect' return 0
Each branch has fundamentally different properties which you might want to segregate. If you forced each branch to have the same kind of effect, it might be easier to perform certain kinds of analysis on these blocks.
decimalin its constructor, but I can't work out how the heck to pass a literal using
web3.py. I've unsuccessfully tried literals of the form:
1/2... Is this not yet possible? (I found this: ethereum/EIPs#598)
Decimaltype, with no luck)