Re: [PHP-DEV] [Discussion] is_string(), string type and objectsimplementing __toString()

This is only part of a thread. view whole thread
  98459
March 9, 2017 17:47 php@fleshgrinder.com (Fleshgrinder)
On 3/9/2017 12:47 PM, Andrey Andreev wrote:
> Hi,
Hey :) On 3/9/2017 12:47 PM, Andrey Andreev wrote:
> On Thu, Mar 9, 2017 at 1:49 AM, Fleshgrinder <php@fleshgrinder.com> wrote: >> Stringable seems very explicit and strict to me, since it is opt-in. >> Currently there is no way to have the ergonomics of coercion if strict >> mode is active for a file. This could be a very explicit way to enable >> it for portions. >> > > I don't understand what you're trying to say here, or rather what you > mean by "stringable" at this point ... Is it a compound type for > string and __toString() objects only, or not? Your last comments > suggest that it isn't.
All scalars, null, and objects with a __toString are stringable. I argued at first that we should only bridge string and objects with a __toString but later came to the conclusion that it does not add value. I understand that this might be confusing, since there are right now many messages in this thread. On 3/9/2017 12:47 PM, Andrey Andreev wrote:
> How can "any other scalar value" work? Using the cookie and headers examples: > > - booleans can be used as On/Off flags for the secure and httpOnly > cookie attributes, but aren't valid literal values for any of the > attributes, or any HTTP header. > - integers are valid as a few headers' values - that is true, but > certainly in a minority of cases > - floats may be used for the q(uality) attribute in content > negotiation (and nothing else AFAIK), but that is a very, very narrow > domain > - null is obviously invalid ... who sends an empty header? And if you > have a use case where you do want to use them, we can already make > anything nullable > > Of course the string values should be validated, unless you want to > allow setting arbitrary headers, e.g.: > > abstract function setHeader(stringable $name, stringable $value); > > ... but filtering out the known to be invalid types *is* validation. > And just that much better if it happens at compile time.
Because: - bool(true) = '0' - bool(false) = '1' - int(n) = 'n' - float(n) = 'n' - null = '' - object(s) = 's' - string(s) = 's' All values are possible values that I can pass to you if you use the string type constraint. Hence, all these types are valid string values if you request a stringable. Stringable should work exactly like the string constraint in non-strict mode, but regardless of the strict mode. The difference to a scalar type constraint is that the passed values are always converted to a scalar string, hence, the source type is unknown to the receiver. As you can see, it does not matter if the stringable pseudo-type accepts more than just string and objects with a __toString method. -- Richard "Fleshgrinder" Fussenegger
  98468
March 10, 2017 10:57 narf@devilix.net (Andrey Andreev)
Hi,

On Thu, Mar 9, 2017 at 7:47 PM, Fleshgrinder <php@fleshgrinder.com> wrote:
> On 3/9/2017 12:47 PM, Andrey Andreev wrote: >> How can "any other scalar value" work? Using the cookie and headers examples: >> >> - booleans can be used as On/Off flags for the secure and httpOnly >> cookie attributes, but aren't valid literal values for any of the >> attributes, or any HTTP header. >> - integers are valid as a few headers' values - that is true, but >> certainly in a minority of cases >> - floats may be used for the q(uality) attribute in content >> negotiation (and nothing else AFAIK), but that is a very, very narrow >> domain >> - null is obviously invalid ... who sends an empty header? And if you >> have a use case where you do want to use them, we can already make >> anything nullable >> >> Of course the string values should be validated, unless you want to >> allow setting arbitrary headers, e.g.: >> >> abstract function setHeader(stringable $name, stringable $value); >> >> ... but filtering out the known to be invalid types *is* validation. >> And just that much better if it happens at compile time. > > Because: > > - bool(true) = '0' > - bool(false) = '1' > - int(n) = 'n' > - float(n) = 'n' > - null = '' > - object(s) = 's' > - string(s) = 's' > > All values are possible values that I can pass to you if you use the > string type constraint. Hence, all these types are valid string values > if you request a stringable. >
Yes, they're valid string values, but the examples I gave were meant to show that context can make them predictably invalid, and hence why strict typing is desirable.
> Stringable should work exactly like the string constraint in non-strict > mode, but regardless of the strict mode. The difference to a scalar type > constraint is that the passed values are always converted to a scalar > string, hence, the source type is unknown to the receiver. >
I'm not really interested in making "strict mode" less strict - it's already opt-in and non-enforceable. I want ways to write stonger-type code in "non-strict mode", because the fact that "strict mode" is non-enforceable means I can never rely on it. Cheers, Andrey.
  98470
March 10, 2017 14:20 rowan.collins@gmail.com (Rowan Collins)
On 10 March 2017 10:57:42 GMT+00:00, Andrey Andreev <narf@devilix.net> wrote:
>I'm not really interested in making "strict mode" less strict - it's >already opt-in and non-enforceable. >I want ways to write stonger-type code in "non-strict mode", because >the fact that "strict mode" is non-enforceable means I can never rely >on it.
This is a common misconception - you can absolutely rely on strict mode enforcing your contract. Basically all that happens in non-strict mode is that if the caller writes: foo($value); The compiler automatically changes that to: foo((string)$value); (That's not literally how it's implemented, but it's the effect you get.) As the receiver of that parameter, you can't tell, and don't care, if it was the human writing the code who added the cast, or the compiler adding it for them. You can't detect someone blindly writing "(string)" everywhere any more than you can detect whether they are running in strict mode. Nor can you know if they took it straight from unfiltered user input, or copy-pasted a literal string to 10 different files, or a hundred other things you'd really like them not to do. All you know is, you asked for a string, and you got one. Regards, -- Rowan Collins [IMSoP]
  98471
March 10, 2017 15:11 narf@devilix.net (Andrey Andreev)
On Fri, Mar 10, 2017 at 4:20 PM, Rowan Collins collins@gmail.com> wrote:
> On 10 March 2017 10:57:42 GMT+00:00, Andrey Andreev <narf@devilix.net> wrote: >>I'm not really interested in making "strict mode" less strict - it's >>already opt-in and non-enforceable. >>I want ways to write stonger-type code in "non-strict mode", because >>the fact that "strict mode" is non-enforceable means I can never rely >>on it. > > This is a common misconception - you can absolutely rely on strict mode enforcing your contract. > > Basically all that happens in non-strict mode is that if the caller writes: > > foo($value); > > The compiler automatically changes that to: > > foo((string)$value); > > (That's not literally how it's implemented, but it's the effect you get.) >
If I want to enforce strict_types=1 operation on the caller - I can't, it can always be overriden, and thus I can never rely on it. The fact that strict_types=0 will do casting for me has no relation to this. If there's any misconception here, it is that both modes are equal - if they were, we wouldn't have 2 of them.
> As the receiver of that parameter, you can't tell, and don't care, if it was the human writing the code who added the cast, or the compiler adding it for them. >
You don't, but I do care at times. I want to know that the *caller* gave me what I want, and that the compiler didn't modify it before I received it. Why I may care about that is a different question, and nototiously hard to explain to people who don't, as all you'd always say something like "you asked for an integer and got an integer" ... Let's say I asked for one of 3 class constants, that happen to hold integer values, and you gave me a string that just happens to be castable to one of those values - you obviously aren't using my API correctly, but I have no way of telling you this because the compiled hid it from both of us. These cases may be rare and very specific, but they do exist and are valid. Yet for some reason, very few people around here want to admit that - all in the name of keeping PHP a weakly-typed language all the way. As if adding one feature would all of a sudden change your ability to write the same code you always did. Sorry about the rant, you can probably tell this irritates me a lot and I can't help it. I'll shut up now, just don't go on a mission to convince me otherwise - won't ever work.
> You can't detect someone blindly writing "(string)" everywhere any more than you can detect whether they are running in strict mode. Nor can you know if they took it straight from unfiltered user input, or copy-pasted a literal string to 10 different files, or a hundred other things you'd really like them not to do. All you know is, you asked for a string, and you got one. >
.... exactly - uncertainty all over the place. :) Cheers, Andrey.
  98474
March 10, 2017 18:27 rowan.collins@gmail.com (Rowan Collins)
On 10 March 2017 15:11:39 GMT+00:00, Andrey Andreev <narf@devilix.net> wrote:
>Let's say I asked for one of 3 class constants, that happen to hold >integer values, and you gave me a string that just happens to be >castable to one of those values - you obviously aren't using my API >correctly, but I have no way of telling you this because the compiled >hid it from both of us.
I actually nearly used that same example for the opposite point: you can't *ever* know if someone used those constants, and it has nothing to do with strict types. The only way typing would help you know that is if the language had native enums, and was strict about casting to those. Regards, -- Rowan Collins [IMSoP]
  98478
March 11, 2017 11:39 php@fleshgrinder.com (Fleshgrinder)
On 3/10/2017 11:57 AM, Andrey Andreev wrote:
> Yes, they're valid string values, but the examples I gave were meant > to show that context can make them predictably invalid, and hence why > strict typing is desirable.
I am totally in favor of strict types, but having a union of some type and having the ability to constraint to it is strict. The union of bool|int|float|null|string is stricter than the super type mixed. That's the whole point of having a stringable. You can more clearly communicate what you require to do your work. Once more, it does not matter what the caller give you, you need to validate it no matter what. On 3/10/2017 4:11 PM, Andrey Andreev wrote:
> You don't, but I do care at times.
Sorry, but your example makes no sense at all. Just because you got an int does not even remotely mean that one of those constants was used. On top of that all, you still need to validate the int you got because it has 2^31-1 possible states, or more in case of 64bit. You need an enum in such a case, and that's the only thing that helps, nothing else. It is also inherently simple to create one, and be type safe forever. -- Richard "Fleshgrinder" Fussenegger
  98479
March 11, 2017 13:23 narf@devilix.net (Andrey Andreev)
On Sat, Mar 11, 2017 at 1:39 PM, Fleshgrinder <php@fleshgrinder.com> wrote:
> On 3/10/2017 11:57 AM, Andrey Andreev wrote: >> Yes, they're valid string values, but the examples I gave were meant >> to show that context can make them predictably invalid, and hence why >> strict typing is desirable. > > I am totally in favor of strict types, but having a union of some type > and having the ability to constraint to it is strict. The union of > bool|int|float|null|string is stricter than the super type mixed. That's > the whole point of having a stringable. >
We already have an unambiguous name for that: scalar
> On 3/10/2017 4:11 PM, Andrey Andreev wrote: >> You don't, but I do care at times. > > Sorry, but your example makes no sense at all. Just because you got an > int does not even remotely mean that one of those constants was used.
I'm sorry, I thought I wouldn't have to explicitly state that of course I don't know a constant was used ... "constant" is not a type. The important thing is that I know a constant was *not* used. Apparently, I am bad at examples, but take a look at sort(). Yes, even after I validate the $sort_flags values, I would never know if you passed int(1) or SORT_NUMERIC. However, knowing that no sane developer writes sort($array, 1), I can very reasonably tell you that the following is an error: $foo = '1'; sort($array, $foo); And the mere existence of SORT_FLAG_CASE makes it that much important that an error is triggered, as now my logic would be built with bitwise operations and I can't just check for one of X values. Ironically enough, the following code executes silently: $array = ['a', 'b', 'c']; sort($array, '2234234324'); If you don't see the problem with that, I guess it does make "no sense at all" from your POV. Just agree to disagree. Cheers, Andrey.
  98480
March 11, 2017 13:34 rowan.collins@gmail.com (Rowan Collins)
On 11 March 2017 13:23:16 GMT+00:00, Andrey Andreev <narf@devilix.net> wrote:
>Ironically enough, the following code executes silently: > > $array = ['a', 'b', 'c']; > sort($array, '2234234324'); > >If you don't see the problem with that, I guess it does make "no sense >at all" from your POV. Just agree to disagree.
I see a problem with that, but I see exactly the same problem with this: $array = ['a', 'b', 'c']; sort($array, 2234234324); The fact is, "int" is far too loose a type constraint to meaningfully validate that parameter. The solution to that is not to be more strict in rejecting strings, but to create richer types of constraint: enums, unions, domains, etc. Regards, -- Rowan Collins [IMSoP]
  98481
March 11, 2017 13:53 narf@devilix.net (Andrey Andreev)
On Sat, Mar 11, 2017 at 3:34 PM, Rowan Collins collins@gmail.com> wrote:
> On 11 March 2017 13:23:16 GMT+00:00, Andrey Andreev <narf@devilix.net> wrote: >>Ironically enough, the following code executes silently: >> >> $array = ['a', 'b', 'c']; >> sort($array, '2234234324'); >> >>If you don't see the problem with that, I guess it does make "no sense >>at all" from your POV. Just agree to disagree. > > > I see a problem with that, but I see exactly the same problem with this: > > $array = ['a', 'b', 'c']; > sort($array, 2234234324); > > The fact is, "int" is far too loose a type constraint to meaningfully validate that parameter. The solution to that is not to be more strict in rejecting strings, but to create richer types of constraint: enums, unions, domains, etc. >
I don't disagree with that in general, but strictly rejecting strings and other non-integer values would alleviate the problem for a majority of cases; i.e. would solve the 90% problem. What I strongly disagree on is that I should be happy with coercion, and the almost religious resistance against (non-overridable) strict scalar typing. Cheers, Andrey.
  98482
March 11, 2017 13:57 php@fleshgrinder.com (Fleshgrinder)
On 3/11/2017 2:53 PM, Andrey Andreev wrote:
> I don't disagree with that in general, but strictly rejecting strings > and other non-integer values would alleviate the problem for a > majority of cases; i.e. would solve the 90% problem. > > What I strongly disagree on is that I should be happy with coercion, > and the almost religious resistance against (non-overridable) strict > scalar typing. >
From whom? Where are you getting this from? We are all in favor of adding more types to the runtime to solve more use cases. We are all just concluding that a stringable type should not be constrained to string + object::__toString only. All your examples just help to make it more clear that there is no benefit in doing anything other than that. -- Richard "Fleshgrinder" Fussenegger
  98483
March 11, 2017 14:04 narf@devilix.net (Andrey Andreev)
On Sat, Mar 11, 2017 at 3:57 PM, Fleshgrinder <php@fleshgrinder.com> wrote:
> On 3/11/2017 2:53 PM, Andrey Andreev wrote: >> I don't disagree with that in general, but strictly rejecting strings >> and other non-integer values would alleviate the problem for a >> majority of cases; i.e. would solve the 90% problem. >> >> What I strongly disagree on is that I should be happy with coercion, >> and the almost religious resistance against (non-overridable) strict >> scalar typing. >> > > From whom? Where are you getting this from? We are all in favor of > adding more types to the runtime to solve more use cases. We are all > just concluding that a stringable type should not be constrained to > string + object::__toString only. All your examples just help to make it > more clear that there is no benefit in doing anything other than that. >
Oh, FFS! I stopped talking about __toString() 10 emails ago, and probably the 3 of us here are all thinking about different things now. I'm out.
  98484
March 11, 2017 14:06 rowan.collins@gmail.com (Rowan Collins)
On 11 March 2017 13:53:10 GMT+00:00, Andrey Andreev <narf@devilix.net> wrote:
>I don't disagree with that in general, but strictly rejecting strings >and other non-integer values would alleviate the problem for a >majority of cases; i.e. would solve the 90% problem.
I guess I just don't see that as 90% at all. The interesting values to detect are the ones that are out of range, not just that somebody wrote '4' instead of 4.
>What I strongly disagree on is that I should be happy with coercion, >and the almost religious resistance against (non-overridable) strict >scalar typing.
For me, it's about division of responsibility: a library defines a contract, and it's up to me how I meet that contract. If I go through writing (string) everywhere, or use a pre-compiler that does that for me, I'm meeting the contract. It's no more the library's business than whether I use an IDE with dozens of templates, or hand craft my code in notepad. All coercive typing does is turn that pre-compiler on by default. What's more interesting to me is how the library can express the contract it actually wants, and scalar types by their nature make for weak constraints. All of the examples you've given are good illustrations of that, and that's why I've been trying to tease out some things that the language could do to actually help with those cases. Regards, -- Rowan Collins [IMSoP]