- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Pattern matching reified types in 2.8
Tue, 2010-03-02, 01:50
Folks,
If there's a reified generic, does pattern matching take that into account in 2.8?
For example:
case class OhPlease[T: Manifest](x: T)
def matchIt(in: OhPlease[_]): String = in match {
case n: OhPlease[String] = n.x
case v: OhPlease[Int] = (v.x * 5).toString
case _ => "None"
}
Will this work around the type erasure thingy?
Thanks,
David
If there's a reified generic, does pattern matching take that into account in 2.8?
For example:
case class OhPlease[T: Manifest](x: T)
def matchIt(in: OhPlease[_]): String = in match {
case n: OhPlease[String] = n.x
case v: OhPlease[Int] = (v.x * 5).toString
case _ => "None"
}
Will this work around the type erasure thingy?
Thanks,
David
Tue, 2010-03-02, 02:17
#2
Re: Pattern matching reified types in 2.8
On Mon, Mar 1, 2010 at 4:57 PM, Jorge Ortiz <jorge.ortiz@gmail.com> wrote:
Not automatically, no.
You can, of course, hack your own:
case class OhPlease[T](x: T)(implicit val m: Manifest[T])
def matchIt(in: OhPlease[_]): String = in.m.toString match { case str if str == manifest[String].toString => in.x.asInstanceOf[String]
Actually, this won't work because the equals operator on manifests is broken. Plus, you'd want to do the is instance of thing (is that transitive with manifests)...
Okay... I guess that part of Beginning Scala 2nd edition doesn't get written. :-(
case int if int == manifest[Int].toString => (in.x.asInstanceOf[Int] * 5).toString case _ => "None" }
--j
On Mon, Mar 1, 2010 at 4:50 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
Folks,
If there's a reified generic, does pattern matching take that into account in 2.8?
For example:
case class OhPlease[T: Manifest](x: T)
def matchIt(in: OhPlease[_]): String = in match {
case n: OhPlease[String] = n.x
case v: OhPlease[Int] = (v.x * 5).toString
case _ => "None"
}
Will this work around the type erasure thingy?
Thanks,
David
Tue, 2010-03-02, 03:17
#3
Re: Pattern matching reified types in 2.8
While equality is broken, others methods aren't. Consider the example at http://stackoverflow.com/questions/1094173/how-do-i-get-around-type-erasure-on-scala-or-why-cant-i-get-the-type-parameter/1094214#1094214, for instance.
On Mon, Mar 1, 2010 at 10:07 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
On Mon, Mar 1, 2010 at 10:07 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
On Mon, Mar 1, 2010 at 4:57 PM, Jorge Ortiz <jorge.ortiz@gmail.com> wrote:Not automatically, no.
You can, of course, hack your own:
case class OhPlease[T](x: T)(implicit val m: Manifest[T])
def matchIt(in: OhPlease[_]): String = in.m.toString match { case str if str == manifest[String].toString => in.x.asInstanceOf[String]
Actually, this won't work because the equals operator on manifests is broken. Plus, you'd want to do the is instance of thing (is that transitive with manifests)...
Okay... I guess that part of Beginning Scala 2nd edition doesn't get written. :-(
case int if int == manifest[Int].toString => (in.x.asInstanceOf[Int] * 5).toString case _ => "None" }
--j
On Mon, Mar 1, 2010 at 4:50 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
Folks,
If there's a reified generic, does pattern matching take that into account in 2.8?
For example:
case class OhPlease[T: Manifest](x: T)
def matchIt(in: OhPlease[_]): String = in match {
case n: OhPlease[String] = n.x
case v: OhPlease[Int] = (v.x * 5).toString
case _ => "None"
}
Will this work around the type erasure thingy?
Thanks,
David
Tue, 2010-03-02, 03:27
#4
Re: Pattern matching reified types in 2.8
Further besides, that equals method in that example belongs to String, not Manifest. :-)
On Mon, Mar 1, 2010 at 10:07 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
On Mon, Mar 1, 2010 at 10:07 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
On Mon, Mar 1, 2010 at 4:57 PM, Jorge Ortiz <jorge.ortiz@gmail.com> wrote:
Not automatically, no.
You can, of course, hack your own:
case class OhPlease[T](x: T)(implicit val m: Manifest[T])
def matchIt(in: OhPlease[_]): String = in.m.toString match { case str if str == manifest[String].toString => in.x.asInstanceOf[String]
Actually, this won't work because the equals operator on manifests is broken. Plus, you'd want to do the is instance of thing (is that transitive with manifests)...
Okay... I guess that part of Beginning Scala 2nd edition doesn't get written. :-(
case int if int == manifest[Int].toString => (in.x.asInstanceOf[Int] * 5).toString case _ => "None" }
--j
On Mon, Mar 1, 2010 at 4:50 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote:
Folks,
If there's a reified generic, does pattern matching take that into account in 2.8?
For example:
case class OhPlease[T: Manifest](x: T)
def matchIt(in: OhPlease[_]): String = in match {
case n: OhPlease[String] = n.x
case v: OhPlease[Int] = (v.x * 5).toString
case _ => "None"
}
Will this work around the type erasure thingy?
Thanks,
David
Tue, 2010-03-02, 05:50
#5
Re: Pattern matching reified types in 2.8
On Mon, Mar 01, 2010 at 04:50:35PM -0800, David Pollak wrote:
> If there's a reified generic, does pattern matching take that into
> account in 2.8?
>
> For example:
>
> case class OhPlease[T: Manifest](x: T)
I obtained agreement-in-principle to do this when martin was here,
matter of details and priorities.
On Mon, Mar 01, 2010 at 05:07:27PM -0800, David Pollak wrote:
> Actually, this won't work because the equals operator on manifests is
> broken.
Are you sure? What do you mean by equals exactly, what do you expect to
happen? You better open a ticket if you know something. When I write a
test case this long it's kind of in somebody else's court to tell me if
there are issues. I checked this in Dec 9 along with all the code which
made it pass.
object Test
{
object Variances extends Enumeration {
val CO, IN, CONTRA = Value
}
import Variances.{ CO, IN, CONTRA }
object SubtypeRelationship extends Enumeration {
val NONE, SAME, SUB, SUPER = Value
}
import SubtypeRelationship.{ NONE, SAME, SUB, SUPER }
class VarianceTester[T, U, CC[_]](expected: Variances.Value)(
implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) {
def elements = List(ev1 <:< ev2, ev2 <:< ev1)
def containers = List(ev3 <:< ev4, ev4 <:< ev3)
def isUnrelated = typeCompare[T, U] == NONE
def isSame = typeCompare[T, U] == SAME
def isSub = typeCompare[T, U] == SUB
def isSuper = typeCompare[T, U] == SUPER
def showsCovariance = (elements == containers)
def showsContravariance = (elements == containers.reverse)
def showsInvariance = containers forall (_ == isSame)
def allContainerVariances = List(showsCovariance, showsInvariance, showsContravariance)
def showsExpectedVariance =
if (isUnrelated) allContainerVariances forall (_ == false)
else if (isSame) allContainerVariances forall (_ == true)
else expected match {
case CO => showsCovariance && !showsContravariance && !showsInvariance
case IN => showsInvariance && !showsCovariance && !showsContravariance
case CONTRA => showsContravariance && !showsCovariance && !showsInvariance
}
}
def showsCovariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
new VarianceTester[T, U, CC](CO) showsExpectedVariance
def showsInvariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
new VarianceTester[T, U, CC](IN) showsExpectedVariance
def showsContravariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
new VarianceTester[T, U, CC](CONTRA) showsExpectedVariance
def typeCompare[T, U](implicit ev1: Manifest[T], ev2: Manifest[U]) = (ev1 <:< ev2, ev2 <:< ev1) match {
case (true, true) => SAME
case (true, false) => SUB
case (false, true) => SUPER
case (false, false) => NONE
}
def assertAnyRef[T: Manifest] = List(
manifest[T] <:< manifest[Any],
manifest[T] <:< manifest[AnyRef],
!(manifest[T] <:< manifest[AnyVal])
) foreach (assert(_, "assertAnyRef"))
def assertAnyVal[T: Manifest] = List(
manifest[T] <:< manifest[Any],
!(manifest[T] <:< manifest[AnyRef]),
manifest[T] <:< manifest[AnyVal]
) foreach (assert(_, "assertAnyVal"))
def assertSameType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SAME, "assertSameType")
def assertSuperType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SUPER, "assertSuperType")
def assertSubType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SUB, "assertSubType")
def assertNoRelationship[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == NONE, "assertNoRelationship")
def testVariancesVia[T: Manifest, U: Manifest] = assert(
typeCompare[T, U] == SUB &&
showsCovariance[T, U, List] &&
showsInvariance[T, U, Set],
"testVariancesVia"
)
def runAllTests = {
assertAnyVal[AnyVal]
assertAnyVal[Unit]
assertAnyVal[Int]
assertAnyVal[Double]
assertAnyVal[Boolean]
assertAnyVal[Char]
assertAnyRef[AnyRef]
assertAnyRef[java.lang.Object]
assertAnyRef[java.lang.Integer]
assertAnyRef[java.lang.Double]
assertAnyRef[java.lang.Boolean]
assertAnyRef[java.lang.Character]
assertAnyRef[String]
assertAnyRef[scala.List[String]]
assertAnyRef[scala.List[_]]
// variance doesn't work yet
// testVariancesVia[String, Any]
// testVariancesVia[String, AnyRef]
assertSubType[List[String], List[Any]]
assertSubType[List[String], List[AnyRef]]
assertNoRelationship[List[String], List[AnyVal]]
assertSubType[List[Int], List[Any]]
assertSubType[List[Int], List[AnyVal]]
assertNoRelationship[List[Int], List[AnyRef]]
// Nothing
assertSubType[Nothing, Any]
assertSubType[Nothing, AnyVal]
assertSubType[Nothing, AnyRef]
assertSubType[Nothing, String]
assertSubType[Nothing, List[String]]
assertSubType[Nothing, Null]
assertSameType[Nothing, Nothing]
// Null
assertSubType[Null, Any]
assertNoRelationship[Null, AnyVal]
assertSubType[Null, AnyRef]
assertSubType[Null, String]
assertSubType[Null, List[String]]
assertSameType[Null, Null]
assertSuperType[Null, Nothing]
// Any
assertSameType[Any, Any]
assertSuperType[Any, AnyVal]
assertSuperType[Any, AnyRef]
assertSuperType[Any, String]
assertSuperType[Any, List[String]]
assertSuperType[Any, Null]
assertSuperType[Any, Nothing]
// Misc unrelated types
assertNoRelationship[Unit, AnyRef]
assertNoRelationship[Unit, Int]
assertNoRelationship[Int, Long]
assertNoRelationship[Boolean, String]
assertNoRelationship[List[Boolean], List[String]]
assertNoRelationship[Set[Boolean], Set[String]]
}
def main(args: Array[String]): Unit = runAllTests
}
Tue, 2010-03-02, 19:47
#6
Re: Pattern matching reified types in 2.8
On Mon, Mar 1, 2010 at 8:46 PM, Paul Phillips <paulp@improving.org> wrote:
On Mon, Mar 01, 2010 at 04:50:35PM -0800, David Pollak wrote:
> If there's a reified generic, does pattern matching take that into
> account in 2.8?
>
> For example:
>
> case class OhPlease[T: Manifest](x: T)
I obtained agreement-in-principle to do this when martin was here,
matter of details and priorities.
Very cool.
On Mon, Mar 01, 2010 at 05:07:27PM -0800, David Pollak wrote:
> Actually, this won't work because the equals operator on manifests is
> broken.
Are you sure? What do you mean by equals exactly, what do you expect to
happen? You better open a ticket if you know something. When I write a
test case this long it's kind of in somebody else's court to tell me if
there are issues. I checked this in Dec 9 along with all the code which
made it pass.
First, the hashCodes are never the same between Manifests of the same class:
dpp@yak:~$ scala
Welcome to Scala version 2.8.0.Beta1-prerelease (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_15).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val m1 = manifest[String]
m1: Manifest[String] = java.lang.String
scala> val m2 = manifest[String]
m2: Manifest[String] = java.lang.String
scala> m1.hashCode == m2.hashCode
res0: Boolean = false
Second, equality testing seems to be on the class itself, not the whole Manifest (although equality testing on manifest.toString) does do the right thing:
scala> val m3 = manifest[List[Int]]
m3: Manifest[List[Int]] = scala.collection.immutable.List[Int]
scala> val m4 = manifest[List[String]]
m4: Manifest[List[String]] = scala.collection.immutable.List[java.lang.String]
scala> m3 == m4
res1: Boolean = true
scala>
I'm okay if this stuff is way hard, difficult to build proofs for and outside the scope of 2.8.
Thanks,
David
object Test
{
object Variances extends Enumeration {
val CO, IN, CONTRA = Value
}
import Variances.{ CO, IN, CONTRA }
object SubtypeRelationship extends Enumeration {
val NONE, SAME, SUB, SUPER = Value
}
import SubtypeRelationship.{ NONE, SAME, SUB, SUPER }
class VarianceTester[T, U, CC[_]](expected: Variances.Value)(
implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) {
def elements = List(ev1 <:< ev2, ev2 <:< ev1)
def containers = List(ev3 <:< ev4, ev4 <:< ev3)
def isUnrelated = typeCompare[T, U] == NONE
def isSame = typeCompare[T, U] == SAME
def isSub = typeCompare[T, U] == SUB
def isSuper = typeCompare[T, U] == SUPER
def showsCovariance = (elements == containers)
def showsContravariance = (elements == containers.reverse)
def showsInvariance = containers forall (_ == isSame)
def allContainerVariances = List(showsCovariance, showsInvariance, showsContravariance)
def showsExpectedVariance =
if (isUnrelated) allContainerVariances forall (_ == false)
else if (isSame) allContainerVariances forall (_ == true)
else expected match {
case CO => showsCovariance && !showsContravariance && !showsInvariance
case IN => showsInvariance && !showsCovariance && !showsContravariance
case CONTRA => showsContravariance && !showsCovariance && !showsInvariance
}
}
def showsCovariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
new VarianceTester[T, U, CC](CO) showsExpectedVariance
def showsInvariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
new VarianceTester[T, U, CC](IN) showsExpectedVariance
def showsContravariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
new VarianceTester[T, U, CC](CONTRA) showsExpectedVariance
def typeCompare[T, U](implicit ev1: Manifest[T], ev2: Manifest[U]) = (ev1 <:< ev2, ev2 <:< ev1) match {
case (true, true) => SAME
case (true, false) => SUB
case (false, true) => SUPER
case (false, false) => NONE
}
def assertAnyRef[T: Manifest] = List(
manifest[T] <:< manifest[Any],
manifest[T] <:< manifest[AnyRef],
!(manifest[T] <:< manifest[AnyVal])
) foreach (assert(_, "assertAnyRef"))
def assertAnyVal[T: Manifest] = List(
manifest[T] <:< manifest[Any],
!(manifest[T] <:< manifest[AnyRef]),
manifest[T] <:< manifest[AnyVal]
) foreach (assert(_, "assertAnyVal"))
def assertSameType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SAME, "assertSameType")
def assertSuperType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SUPER, "assertSuperType")
def assertSubType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SUB, "assertSubType")
def assertNoRelationship[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == NONE, "assertNoRelationship")
def testVariancesVia[T: Manifest, U: Manifest] = assert(
typeCompare[T, U] == SUB &&
showsCovariance[T, U, List] &&
showsInvariance[T, U, Set],
"testVariancesVia"
)
def runAllTests = {
assertAnyVal[AnyVal]
assertAnyVal[Unit]
assertAnyVal[Int]
assertAnyVal[Double]
assertAnyVal[Boolean]
assertAnyVal[Char]
assertAnyRef[AnyRef]
assertAnyRef[java.lang.Object]
assertAnyRef[java.lang.Integer]
assertAnyRef[java.lang.Double]
assertAnyRef[java.lang.Boolean]
assertAnyRef[java.lang.Character]
assertAnyRef[String]
assertAnyRef[scala.List[String]]
assertAnyRef[scala.List[_]]
// variance doesn't work yet
// testVariancesVia[String, Any]
// testVariancesVia[String, AnyRef]
assertSubType[List[String], List[Any]]
assertSubType[List[String], List[AnyRef]]
assertNoRelationship[List[String], List[AnyVal]]
assertSubType[List[Int], List[Any]]
assertSubType[List[Int], List[AnyVal]]
assertNoRelationship[List[Int], List[AnyRef]]
// Nothing
assertSubType[Nothing, Any]
assertSubType[Nothing, AnyVal]
assertSubType[Nothing, AnyRef]
assertSubType[Nothing, String]
assertSubType[Nothing, List[String]]
assertSubType[Nothing, Null]
assertSameType[Nothing, Nothing]
// Null
assertSubType[Null, Any]
assertNoRelationship[Null, AnyVal]
assertSubType[Null, AnyRef]
assertSubType[Null, String]
assertSubType[Null, List[String]]
assertSameType[Null, Null]
assertSuperType[Null, Nothing]
// Any
assertSameType[Any, Any]
assertSuperType[Any, AnyVal]
assertSuperType[Any, AnyRef]
assertSuperType[Any, String]
assertSuperType[Any, List[String]]
assertSuperType[Any, Null]
assertSuperType[Any, Nothing]
// Misc unrelated types
assertNoRelationship[Unit, AnyRef]
assertNoRelationship[Unit, Int]
assertNoRelationship[Int, Long]
assertNoRelationship[Boolean, String]
assertNoRelationship[List[Boolean], List[String]]
assertNoRelationship[Set[Boolean], Set[String]]
}
def main(args: Array[String]): Unit = runAllTests
}
--
Paul Phillips | A Sunday school is a prison in which children do
Imperfectionist | penance for the evil conscience of their parents.
Empiricist | -- H. L. Mencken
ha! spill, pupil |----------* http://www.improving.org/paulp/ *----------
--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics
Tue, 2010-03-02, 20:07
#7
Re: Pattern matching reified types in 2.8
On Tue, Mar 02, 2010 at 10:43:51AM -0800, David Pollak wrote:
> First, the hashCodes are never the same between Manifests of the same
> class:
>
> dpp@yak:~$ scala
> Welcome to Scala version 2.8.0.Beta1-prerelease (Java HotSpot(TM) 64-Bit
> Server VM, Java 1.6.0_15).
What? Come on. Next you'll be wondering if we're ever going to get
nixon out of the white house.
scala> val m1 = manifest[String]
m1: Manifest[String] = java.lang.String
scala> val m2 = manifest[String]
m2: Manifest[String] = java.lang.String
scala> m1.hashCode == m2.hashCode
res0: Boolean = true
https://lampsvn.epfl.ch/trac/scala/ticket/2838
"manifest overrides equals without hashCode"
Changed 5 weeks ago by extempore ¶
* status changed from new to closed
* resolution set to fixed
(In [20728]) Added hashCode implementations to Manifest types where necessary. Closes #2838. No review.
> scala> val m3 = manifest[List[Int]]
> m3: Manifest[List[Int]] = scala.collection.immutable.List[Int]
>
> scala> val m4 = manifest[List[String]]
> m4: Manifest[List[String]] =
> scala.collection.immutable.List[java.lang.String]
>
> scala> m3 == m4
> res1: Boolean = true
OK, that looks busted. In the future opening a ticket would be
appreciated, there is already way too much "report the bug on the
mailing list for faster attention" traffic and it's counterproductive.
Tue, 2010-03-02, 20:17
#8
Re: Pattern matching reified types in 2.8
On Tue, Mar 2, 2010 at 11:00 AM, Paul Phillips <paulp@improving.org> wrote:
On Tue, Mar 02, 2010 at 10:43:51AM -0800, David Pollak wrote:
> First, the hashCodes are never the same between Manifests of the same
> class:
>
> dpp@yak:~$ scala
> Welcome to Scala version 2.8.0.Beta1-prerelease (Java HotSpot(TM) 64-Bit
> Server VM, Java 1.6.0_15).
What? Come on. Next you'll be wondering if we're ever going to get
nixon out of the white house.
Nixon's in the whitehouse... what happened to JFK? ;-)
scala> val m1 = manifest[String]
m1: Manifest[String] = java.lang.String
scala> val m2 = manifest[String]
m2: Manifest[String] = java.lang.String
scala> m1.hashCode == m2.hashCode
res0: Boolean = true
https://lampsvn.epfl.ch/trac/scala/ticket/2838
"manifest overrides equals without hashCode"
Changed 5 weeks ago by extempore ś
* status changed from new to closed
* resolution set to fixed
(In [20728]) Added hashCode implementations to Manifest types where necessary. Closes #2838. No review.
> scala> val m3 = manifest[List[Int]]
> m3: Manifest[List[Int]] = scala.collection.immutable.List[Int]
>
> scala> val m4 = manifest[List[String]]
> m4: Manifest[List[String]] =
> scala.collection.immutable.List[java.lang.String]
>
> scala> m3 == m4
> res1: Boolean = true
OK, that looks busted. In the future opening a ticket would be
appreciated, there is already way too much "report the bug on the
mailing list for faster attention" traffic and it's counterproductive.
Will do.
--
Paul Phillips | Where there's smoke, there's mirrors!
Analgesic |
Empiricist |
i pull his palp! |----------* http://www.improving.org/paulp/ *----------
--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics
Tue, 2010-03-02, 20:27
#9
Re: Pattern matching reified types in 2.8
Also, the equals outcome is already documented behavior:
/** Tests whether the type represented by this manifest is equal to the
* type represented by `that' manifest. BE AWARE: the current
* implementation is an approximation, as the test is done on the
* erasure of the type. */
So I think I can improve it but it's "performing as specified."
You can, of course, hack your own:
case class OhPlease[T](x: T)(implicit val m: Manifest[T])
def matchIt(in: OhPlease[_]): String = in.m.toString match { case str if str == manifest[String].toString => in.x.asInstanceOf[String] case int if int == manifest[Int].toString => (in.x.asInstanceOf[Int] * 5).toString case _ => "None" }
--j
On Mon, Mar 1, 2010 at 4:50 PM, David Pollak <feeder.of.the.bears@gmail.com> wrote: