> On 3 Dec 2017, at 18:42, Matthew Johnson <matt...@anandabits.com> wrote: > > > > Sent from my iPad > > On Dec 3, 2017, at 10:40 AM, David Hart <da...@hartbit.com > <mailto:da...@hartbit.com>> wrote: > >> >> >>> On 3 Dec 2017, at 04:11, Matthew Johnson via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote: >>> >>> >>> >>> Sent from my iPad >>> >>> On Dec 2, 2017, at 7:40 PM, Chris Lattner <clatt...@nondot.org >>> <mailto:clatt...@nondot.org>> wrote: >>> >>>> On Dec 2, 2017, at 2:13 PM, Matthew Johnson <matt...@anandabits.com >>>> <mailto:matt...@anandabits.com>> wrote: >>>>>> For all those reasons, we really do need something like AnyObject >>>>>> dispatch if we care about working with dynamically typed languages. The >>>>>> design I’m suggesting carefully cordons this off into its own struct >>>>>> type, so it doesn’t infect the rest of the type system, and is >>>>>> non-invasive in the compiler. >>>>> >>>>> I am quite familiar with dynamic languages and agree that this is >>>>> necessary if we are going to fully open up access to these languages from >>>>> Swift. >>>> >>>> Ok, then it appears you agree that something like anyobject dispatch is >>>> necessary for effective dynamic language interop. >>>> >>>>>>> I strongly urge you to reconsider the decision of that dynamic members >>>>>>> must be made available with no indication at usage sites. An >>>>>>> indication of dynamic lookup at usage sites aligns very well (IMO) with >>>>>>> the rest of Swift (AnyObject lookup aside) by calling attention to code >>>>>>> that requires extra care to get right. >>>>>> >>>>>> I don’t understand this. The proposal is fully type safe, and this >>>>>> approach is completely precedented by AnyObject. Swift’s type system >>>>>> supports many ways to express fallibility, and keeping those decisions >>>>>> orthogonal to this proposal is the right thing to do, because it allows >>>>>> the author of the type to decide what model makes sense for them. >>>>> >>>>> Allowing the author of the type to choose whether the mechanism is hidden >>>>> or visible is exactly what I don’t want to allow. I think you have the >>>>> right design regarding types and semantics - the author chooses. But I >>>>> don’t want these calls to look like ordinary member lookup when I’m >>>>> reading code. >>>>> >>>>> They inherently have a much greater chance of failure than ordinary >>>>> member lookup. Further, authors are likely to choose immediate traps or >>>>> nil IUO as failure modes as forcing users to deal with Optional on every >>>>> call is likely to be untenable. I believe this behavior should be >>>>> represented by some kind of syntax at the usage site. I don’t believe it >>>>> is an undue burden. It would make the dynamic lookup semantic clear to >>>>> all readers and would help to discourage abuse. >>>> >>>> I believe that adding explicit syntax would be counterproductive to your >>>> goals, and would not make dynamic lookup syntax more clear. I assume that >>>> you would also want the same thing for DynamicCallable too, and operator >>>> overloads, subscripts, and every other operation you perform on these >>>> values, since they all have the exact same behavior. >>>> >>>> If we required some syntax even as minimal as “foo.^bar” and "baz^(42)”, >>>> that change would turn this (which uses runtime failing or IUO return >>>> values like AnyObject): >>>> >>>> let np = Python.import("numpy") >>>> let x = np.array([6, 7, 8]) >>>> let y = np.arange(24).reshape(2, 3, 4) >>>> >>>> let a = np.ones(3, dtype: np.int32) >>>> let b = np.linspace(0, pi, 3) >>>> let c = a+b >>>> let d = np.exp(c) >>>> print(d) >>>> >>>> into: >>>> >>>> let np = Python.import("numpy") >>>> let b = np^.array^([6, 7, 8]) >>>> let y = np^.arange^(24)^.reshape^(2, 3, 4) >>>> >>>> let a = np^.ones^(3, dtype: np^.int32) >>>> let b = np^.linspace^(0, pi, 3) >>>> let c = a+^b >>>> let d = np^.exp^(c) >>>> >>>> This does not improve clarity of code, it merely serves to obfuscate >>>> logic. It is immediately apparent from the APIs being used, the API >>>> style, and the static types (in Xcode or through static declarations) that >>>> this is all Python stuff. >>> >>> It may be immediately apparent when the types involved are obviously >>> dynamic, such as in this example where Python.import is explicitly used. >>> However, my concern is less about the intended use case of dynamic language >>> interop than I am that this feature will be generally available to all >>> types in Swift. >>> >>> This is big change from AnyObject dispatch. It opens up the dynamism to >>> types and contexts that are not necessarily obviously using dynamic lookup, >>> callable, etc. Maybe this won’t turn out to be a problem in practice but I >>> still think it’s a legitimate concern. >> >> If dynamism if restricted to subclasses of a DynamicObject type, like Xiaodi >> suggested earlier, then we can protect ourselves from this dynamic dispatch >> being generally available to all types in Swift. > > Chris has good reasons for not wanting to use a class. More generally, > designs that require subclassing a specific superclass are usually a bad > idea. They should only be used with very specific and very good reasons. > I’m not sure what this would buy us here. How specifically do you think this > helps in ways that requiring conformance to be stated in the original type > declaration will not?
It achieves the same objectives as requiring conformance be stated in the original type declaration. But I’m less enthusiastic about this approach as it introduces a new language rule for one specific protocol. >> >>>> When you start mixing in use of native Swift types like dictionaries >>>> (something we want to encourage because they are typed!) you end up with >>>> an inconsistent mismash where people would just try adding syntax or >>>> applying fixits continuously until the code builds. >>>> >>>> Beyond that, it is counterproductive to your goals, because it means that >>>> people are far less likely to use to use optional returns. Doing so >>>> (which produces a safer result) would cause a double tax in syntax, and >>>> would be a confusing jumble. I can’t bring myself to do the whole example >>>> above, one line - just converting member lookup syntax but not callable >>>> syntax - would end up: >>>> >>>> let y = np^.arange?^(24)^.reshape^?(2, 3, 4) >>>> >>>> If you made DynamicCallable also return optional it would be: >>>> >>>> let y = np^.arange?^(24)?^.reshape^?(2, 3, 4)! >>>> >>>> or something. This is such madness that no one would do that. >>> >>> Yes, I agree. The interaction with optional chaining makes it unworkable. >>> I hadn’t thought that all the way through. Thank you for indulging in the >>> discussion about this idea. >>> >>> I’m uncertain what the right answer is. I’m still not really comfortable >>> with opening up dynamic lookup to any user-defined type without some way to >>> indicate to readers that dynamic lookup is happening in a piece of code. >>> Maybe there is a less localized annotation that would indicate dynamic >>> lookup is in effect for a larger chunk of code. >> >> I think that making dynamic calls syntactically different to readers is >> going too far in the direction of safety. Plus, it makes the language >> inconsistent as we already have AnyObject dispatch with exactly the same >> syntax. But I understand why you would want it if *any* type could end up >> being conformed to a dynamic lookupable/callable protocol. Like said above, >> I think that a DynamicObject type is enough protection to not bother making >> the syntax heavier at the point of use. >> >>> One idea that hasn’t been explored yet is introducing a dynamic lookup >>> effect. That would provide clean syntax at the expression level while >>> still making it clear that dynamic lookup is happening. This probably >>> isn’t the right approach as it would be viral and wouldn’t even indicate >>> that the code in a specific function body even contains a dynamic lookup. >>> I mention it mostly to broaden the conversation and thought space about >>> what kind of approach might address my concerns without cluttering up >>> expressions. >>> >>> Another, potentially more viable approach would be to use an approach >>> similar to try and the proposed async of a statement modifier (possibly >>> also allowed at the expression level as with try and async). This could be >>> used in conjunction with an effect as mentioned above but could also be >>> used independently even though there isn’t a current precedent for that. >>> Your example written with this approach might be: >>> >>> let np = Python.import("numpy") >>> let b = dynamic np.array([6, 7, 8]) >>> let y = dynamic np.arange(24).reshape(2, 3, 4) >>> >>> let a = dynamic np.ones(3, dtype: np.int32) >>> let b = dynamic np.linspace(0, pi, 3) >>> let c = dynamic a + b >>> let d = dynamic np.exp(c) >>> >>> Note: `dynamic` is just a straw man here. This is obviously a lot of >>> boilerplate repetition of the same modifier. I would also be perfectly >>> happy allowing this annotation to apply to a block of code or even a whole >>> function body. Then it might be: >>> >>> dynamic { >>> let np = Python.import("numpy") >>> let b = np.array([6, 7, 8]) >>> let y = np.arange(24).reshape(2, 3, 4) >>> >>> let a = np.ones(3, dtype: np.int32) >>> let b = np.linspace(0, pi, 3) >>> let c = a + b >>> let d = np.exp(c) >>> } >>> >>> The most obvious objection to this is that it introduces nesting and does >>> so in a way that only very indirectly influences control flow (through the >>> potential for dynamic failure). >>> >>> My objective certainly isn’t to make code ugly and obfuscate logic. It is >>> simply to make the usage of dynamic features that are prone to failure at >>> runtime immediately clear to a reader. This should be as lightweight as >>> possible while still providing valuable information to the reader. >>> >>>> >>>> >>>>>> Swift already has a dynamic member lookup feature, "AnyObject dispatch" >>>>>> which does not use additional punctuation, so this would break precedent. >>>>> I would prefer if dynamic lookup were visible with AnyObject as well. >>>>> For that reason I don’t believe it makes a good precedent to follow. In >>>>> fact, I would prefer to see us go the other direction and perhaps even >>>>> consider revising dynamic lookup syntax for AnyObject in the future. >>>> >>>> This is definitely not going to happen. The change Doug mentioned is to >>>> have AnyObject lookup return optional instead of IUO, which forces ? on >>>> the clients. Adding other syntax (like you’re suggesting) is certainly >>>> not going to happen. >>>> >>>> The entire point of AnyObject dispatch is to improve syntactic elegance >>>> and clarity of code using it. There is no other reason to exist. Making >>>> code that uses it syntactically onerous completely defeats the point of >>>> having it in the first place, as I’ve mentioned before. >>> >>> I agree. The interaction with optional chaining is significantly more >>> onerous than I had considered. I should have worked through an example on >>> my own. I do hope you will consider a less localized approach to >>> usage-site annotation though. >>> >>>> >>>> >>>> Furthermore, your premise that Swift does not have invisibly failable >>>> operations is plainly wrong. Array subscript and even integer addition >>>> can fail. >>> >>> I am well aware of these behaviors. The difference IMO is that programmers >>> tend to be well aware of these preconditions even if they also often choose >>> to ignore them. Dynamic lookup will not be so clear. This is especially >>> true if people use it with types that also have an API available through >>> static lookup. >>> >>>> Even the behavior of AnyObject was carefully designed and considered, and >>>> were really really good reasons for it returning IUO. >>> >>> I am not trying to call into question the choices made in the past. Swift >>> wouldn’t be the great language with a bright future that it is today >>> without an incredibly successful migration of a large user base from >>> Objective-C to Swift. This is a huge accomplishment and couldn’t have >>> happened without making really good decisions about some really hard >>> tradeoffs. >>> >>>> >>>> -Chris >>>> >>> _______________________________________________ >>> swift-evolution mailing list >>> swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>> https://lists.swift.org/mailman/listinfo/swift-evolution >>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution