Skip Menu |

This queue is for tickets about the Type-Tiny CPAN distribution.

Report information
The Basics
Id: 97516
Status: resolved
Priority: 0/
Queue: Type-Tiny

People
Owner: Nobody in particular
Requestors: mschwern [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: (no value)
Broken in:
  • 0.044
  • 0.046
Fixed in: 1.000000



Subject: InstanceOf[Class::Name] is not cached, makes declaring coercion inconsistent
InstanceOf[Class::Name] is cached, but only for about a second.  Then a new instance of the type is returned.  This one liner demonstrates.

$ perl -wle 'use Scalar::Util qw(refaddr); use Types::Standard qw(:types);  while(1) { print join ", ", (refaddr(InstanceOf["Class::Name"]), refaddr(InstanceOf["Class::Name"])); sleep 1 }'
140722877312264, 140722877312264
140722876835704, 140722876835704
140722876589416, 140722876589416
140722875485312, 140722875485312

Contrast with Str which always returns the same type object.

$ perl -wle 'use Scalar::Util qw(refaddr); use Types::Standard qw(:types);  while(1) { print join ", ", (refaddr(Str), refaddr(Str)); sleep 1 }'
140414620306936, 140414620306936
140414620306936, 140414620306936
140414620306936, 140414620306936
140414620306936, 140414620306936

This seems wasteful.  On a practical level, it makes adding a coercion to InstanceOf["Class::Name"] impossible.

coerce InstanceOf["URI"],
  from Str,
  via {
      require URI;
      return URI->new($_);
  };

The coercion is applied to a particular instance of InstanceOf["URI"] passed to coerce.  Later InstanceOf["URI"] will return a different instance with no coercion.  This is confusing.

The problem appears to stem from a cache which will only hold onto externally referenced types.  If I store InstanceOf["URI"] in a lexical then InstanceOf["URI"] will return the same object.

I'm having deja vu about this issue.
Moose/Mouse will certainly always return the same object reference, but that's a consequence of them having global type libraries. If you have a named type constraint, that means the same everywhere. This leads to action at a distance. A coercion to URI defined by the routing part of your application can have unintended consequences on the web-scraping part. While Type::Tiny type constraints play nicely with Moose and Mouse, I did aim to change some aspects of the system I thought were flawed. Type::Tiny's type constraints are essentially lexical. And then Type::Library basically gives you a way to give them (namespaced) global names. So the solution, such as it is, is to create a type library, and declare a new type in it: declare MyURI, as InstanceOf["URI"]; And then attach the coercion to "MyURI". Once you've created a child type, the child "belongs to you", and you can coerce to it freely. Certainly it would be nicer if the example you provided at least emitted a warning though. I'll keep this ticket open while I think about things.
Subject: Re: [rt.cpan.org #97516] InstanceOf[Class::Name] is not cached, makes declaring coercion inconsistent
Date: Mon, 28 Jul 2014 11:25:49 -0700
To: bug-Type-Tiny [...] rt.cpan.org
From: Michael G Schwern <schwern [...] pobox.com>
Regardless of the lexical vs global type philosophy, the current situation where SOME of Types::Standard's types act globally and some do not, is the worst case. It's unclear and undocumented to the user which types will persist and which will just toss changes out. The changes made to things like InstanceOf["URI"] don't even persist lexically, they immediately disappear because of the hidden lexical nature of the type. UNLESS that type happens to have been coincidentally cached by something else. For example... subtype "AbsDir", as InstanceOf["Path::Tiny"], where { $_->is_absolute }; coerce "AbsDir", from InstanceOf["Path::Tiny"], via { return $_->absolute; }; coerce InstanceOf["Path::Tiny"], from "Str", via { return path($_); }; Now that final coercion will persist but only because AbsDir happens to contain an instance of InstanceOf["Path::Tiny"] which it keeps in existence. Something has to be done about that. (Note: that something is NOT to document the broken behavior). Now I get a bit long winded as I have Opinions about type coercion. The MyURI work around, as a long term solution, is problematic from my point of view about type coercion. Unlike the Moose POV about type coercion, that it should be under the control of the consumer of the type, I prefer the ease of transparent type coercion. If my method wants InstanceOf[URI] all I care is my method receives InstanceOf[URI]; I don't care what the original input was so long as I have a faithful representation of it as a URI instance. Similarly, when I add a coercion to a type I expect it to just work with all the things that take that type. I do not want to have to declare a new type and then hunt down all the places which used InstanceOf[URI] and change it to MyURI. It's funny, each POV views the other as causing an encapsulation violation. I guess it depends on whether you look at type coercion as altering the input (I gave you a string and suddenly it's a URI object) or as guaranteeing the input (I said I want a URI object, I don't care what it was before). Or as proscriptive (my method takes only URI objects, it's wrong to give it a string) vs descriptive (my method takes URI objects, or anything which can be reasonably coerced into one). Anyhow, the work around I'm using for Gitpan is: our $URI_Type = InstanceOf["URI"]; To the action at a distance issue... global types are global and inherently have action at a distance issues. This is like saying the method modifiers after, before and around have action at a distance issues. They do. It's part of their nature. It's accepted. Adding coercion is a far less severe action at a distance issue. Moose style conversion has to be turned on by the type user. If I add coercion to InstanceOf[URI] it only effects the places which have already decided they want coercion. The nature of that coercion may change, but that's what you accept by using global types and coercion. The action at a distance issue already exists with the standard types cached by Types::Standard. I can add a coercion to Str and it will appear elsewhere. Action at a distance is only a problem when a global entity is changed in two different places. If one library declares and modifies a global type, and at the same time, it's not really action at a distance. For example, the Gitpan project has Gitpan::Types to collect all its custom types and custom type modifications together in one place. https://github.com/evalEmpire/gitpan/blob/rewrite/lib/Gitpan/Types.pm It was in converting this to Moo/Type::Tiny that I encountered the problem. This only works for Gitpan because it is an application. If a CPAN module starts messing with standard types, that's the bad sort of action at a distance... unless it's a CPAN module about types. As to Type::Tiny's intent to make types lexical, that's fine. If you want lexical protections for your types, you can make lexical types. If you want global types, you can make global types. Type::Tiny presents a lexical type system with types declared lexically. Types::Standard presents a global type system with types declared in a separate file and exported. Changes to lexical types should be lexical. And changes to global types should be global. If you start adding coercions to global types declared elsewhere, you incur the same problems as messing with any global. This is a known problem with global entities and one which is accepted. All that said, mutable global entities are a problem because they can be changed anywhere and cause action at a distance. IMMUTABLE global entities are less of a problem. Perhaps types can be locked to prevent further changes, and unlocked as an "I thought about this and know what I'm doing" check? Another possibility is to have Types::Standard export *copies* of their types so they can be safely messed with without danger of causing action at a distance. Then if one wants to modify standard types for their own project they have to write their own local type library like Gitpan::Types. Another possibility is to accept that Types::Standard produces global types, to make all standard types persist, and document that standard programming practices about modifying globals apply.
Show quoted text
> All that said, mutable global entities are a problem > because they can be changed anywhere and cause action > at a distance. IMMUTABLE global entities are less of > a problem.
Agreed. Type::Tiny objects are already immutable by design, but the Type::Coercion objects referenced by them are not. However, Type::Coercion has long provided a $coercion->freeze method to lock down a coercion object, causing it to throw an exception if there are any attempts to add coercions to it. The problem is that other parts of Type::Tiny have been conservative at calling $coercion->freeze. Well, not any more... https://github.com/tobyink/p5-type-tiny/compare/lockdown As of the next release, all the bundled type constraints will have their coercions frozen, as will any parameterized type constraint at all. Third-party type libraries can call: __PACKAGE__->meta->make_immutable; # familiar? ... to do the same to their types. If you want to add coercions to a type constraint with a frozen coercion object, the officially supported technique is to create a child type and add the coercions to that. Type::Tiny has long had a $type->plus_coercions method to simplify that process. Show quoted text
> unlocked as an "I thought about this and know what > I'm doing" check
The following should unlock a type's coercion object: $type->coercion->{frozen} = 0; It is unlikely to become an officially supported part of Type::Tiny's API, but it is equally unlikely to suddenly stop working.
Subject: Re: [rt.cpan.org #97516] InstanceOf[Class::Name] is not cached, makes declaring coercion inconsistent
Date: Wed, 30 Jul 2014 08:03:09 -0700
To: bug-Type-Tiny [...] rt.cpan.org
From: Michael G Schwern <schwern [...] pobox.com>
On 7/30/14, 2:56 AM, Toby Inkster via RT wrote: Show quoted text
> As of the next release, all the bundled type constraints will > have their coercions frozen, as will any parameterized type > constraint at all.
Okie doke. I'll switch my code over. Show quoted text
> The following should unlock a type's coercion object: > > $type->coercion->{frozen} = 0; > > It is unlikely to become an officially supported part > of Type::Tiny's API, but it is equally unlikely to suddenly stop working.
If you're going to tell people to do that, I would make it part of the API. I know, I know, you don't want people actually doing it so you don't want it in the API. The limbo state is worse. Providing an unofficial, encapsulation-breaking work around sets you and everyone who uses it up for a maintenance headache down the road when you either want to remove or change the behavior and find out it's used in some Really Important CPAN module's dependency chain. Put in the accessor, give it an annoyingly long name, document it, document why it's a bad idea to alter standard types (global effects).
Show quoted text
> If you're going to tell people to do that, > I would make it part of the API.
Oh, OK. But I'm marking it as unstable.
Type-Tiny-1.000000 has been released, mostly resolving this.