standard typeclass name for apply/unapply

39 views
Skip to first unread message

Matthew Pocock

unread,
Feb 18, 2015, 10:46:52 AM2/18/15
to scala-fu...@googlegroups.com
Hi,

Is there a standard typeclass name for a typeclass that exposes apply/unapply methods for the type?

trait ApplyUnapply[T] {
  def apply(a: A): T
  def unapply(t: T): Option[A]
}

This obviously has variants for different arities, so:

trait ApplyUnapply2[T] {
  def apply(a: A, b: B): T
  def unapply(t: T): Option((A, B))
}

And so on. There's also an obvious pair of laws:

unapply(apply(a)) == Option(a) // unapply gets the value out again
unapply(t).map(apply(_) == t).getOrElse(true) // apply puts the value in again

I realise that we can trivially derive an isomorphism from this typeclass family, but it seems to be a useful idea in its own right.

Anyway, I have a bunch of these in my code and I'd love to be able to suffix all the trait names with something systematically.

Cheers
Matthew
--
Dr Matthew Pocock
Turing ate my hamster LTD

Integrative Bioinformatics Group, School of Computing Science, Newcastle University

skype: matthew.pocock
tel: (0191) 2566550

Vlad Patryshev

unread,
Feb 18, 2015, 11:16:12 AM2/18/15
to scala-fu...@googlegroups.com
Good idea to have it, but I do not like the naming, too down-to-earth; there must be some kind of abstraction for this.

Thanks,
-Vlad

--
You received this message because you are subscribed to the Google Groups "scala-functional" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-function...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Miles Sabin

unread,
Feb 18, 2015, 1:19:31 PM2/18/15
to scala-fu...@googlegroups.com
This is fairly similar to shapeless's Generic,

Cheers,


Miles
> --
> You received this message because you are subscribed to the Google Groups
> "scala-functional" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to scala-function...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.



--
Miles Sabin
tel: +44 7813 944 528
skype: milessabin
gtalk: mi...@milessabin.com
g+: http://www.milessabin.com
http://twitter.com/milessabin

Stacy Curl

unread,
Feb 20, 2015, 2:50:18 PM2/20/15
to scala-fu...@googlegroups.com
FWIW I've toyed with this area a bit (not with a type class, as Miles has pointed out that would need to be poly-typic): https://github.com/stacycurl/extractors/blob/master/core/src/main/scala/sjc/extractors/extractors.scala#L353

Stacy

Tony Morris

unread,
Feb 20, 2015, 6:50:43 PM2/20/15
to scala-fu...@googlegroups.com
It is a specialisation of a Prism.

Matthew Pocock

unread,
Feb 20, 2015, 8:48:05 PM2/20/15
to scala-fu...@googlegroups.com
Hi Tony,

Is it the O1==>O2 thing that is a special case of a Prism?

Matthew

Matthew Pocock

unread,
Feb 20, 2015, 9:36:56 PM2/20/15
to scala-fu...@googlegroups.com
Sorry, ignore that last reply. Got two threads mixed up.

Tony Morris

unread,
Feb 21, 2015, 12:55:33 AM2/21/15
to scala-fu...@googlegroups.com
Hello Matthew,
I am not sure what you mean. Here is a compilable answer:
https://gist.github.com/tonymorris/42c491295488f804f882

// https://groups.google.com/d/msg/scala-functional/NAMWgC90KLA/sU0rw7ceoQMJ

object ApplyUnapply {

import language.higherKinds // I laugh every time

/*

In order to define Prism, we must define:

* Choice

  In order to define Choice, we must define Profunctor

* Applicative

  In order to define Applicative, we must define Functor

*/

// Let's define Profunctor
trait Profunctor[~>[_, _]] {
  def dimap[A, B, C, D](f: C => A, g: B => D): (A ~> B) => C ~> D
}

// so now we can define Choice
trait Choice[~>[_, _]] extends Profunctor[~>] {
  def left[A, B, C](f: A ~> B): (A Either C) ~> (B Either C)
  def right[A, B, C](f: A ~> B): (C Either A) ~> (C Either B)
}

// Functor as we know it
trait Functor[F[_]] {
  def fmap[A, B](f: A => B): F[A] => F[B]
}

// And so Applicative, as we know it
trait Applicative[F[_]] extends Functor[F] {
  def pure[A]: A => F[A]
  def ap[A, B](f: F[A => B]): F[A] => F[B]
}

// Finally, we can define Prism
trait Prism[S, T, A, B] {
  def prism[~>[_, _]: Choice, F[_]: Applicative]
    (f: A ~> F[B]): (S ~> F[T])
}

// And now we can start answering the question. Restated for clarity (fixed typos):

// ----

/*

Is there a standard typeclass name for a typeclass that exposes apply/unapply methods for the type?
*/
trait ApplyUnapply[T, A] {

  def apply(a: A): T
  def unapply(t: T): Option[A]
}
/*

This obviously has variants for different arities, so:
*/
trait ApplyUnapply2[T, A, B] {

  def apply(a: A, b: B): T
  def unapply(t: T): Option[(A, B)]
}
/*

And so on. There's also an obvious pair of laws:

unapply(apply(a)) == Option(a) // unapply gets the value out again
unapply(t).map(apply(_) == t).getOrElse(true) // apply puts the value in again
*/

// ----

// To the answer.

// First, let's define a construction function for prisms. This function is
// similar, but not the same, as apply/unapply.

def prism[S, T, A, B]
  (
    a: B => T // "apply"
  , u: S => (T Either A) // "unapply"
  ): Prism[S, T, A, B] =
  new Prism[S, T, A, B] {
    def prism[~>[_, _]: Choice, F[_]: Applicative]
      (f: A ~> F[B]): (S ~> F[T]) = {
        val C = implicitly[Choice[~>]]
        val F = implicitly[Applicative[F]]
        // the important part
        C.dimap(
          u
        , (_: T Either F[B]).fold(F.pure, F.fmap(a))
        )(C.right(f))
      }
  }

// Second, let us define a type-alias that specialises Prism. This type-alias
// removes the "polymorphic update" aspect of Prism. That's because the original
// question does not have a need for it (at least, not the way it was phrased).
// That is to say, if we turn (T) into (A), we are also turning (A) into (T) on
// the way back. We are not turning (A) into (B) and then (S) into (T).

// The `Prismy` type-alias specialises Prism by removing the polymorphism during
// transformation of values. We have only (T) and (A) values.
type Prismy[T, A] =
  Prism[T, T, A, A]
 
// As a consequence of our `Prismy` type-alias, we can further specialise our
// construction function, by turning the polymorphic `Either` result to
// `Option`.

def prismy[T, A](
    a: A => T // "apply"
  , u: T => Option[A] // "unapply"
  ): Prismy[T, A] = // a note on this return type below
  prism(
    a
  , x => u(x).fold(
           Left(x): T Either A)( // pardon me Scala, but did you say "type inference"?
           Right(_)
         )
  )

// We have declared the return type of `prismy` to be `Prismy[T, A]`, which is
// the same as `Prism[T, T, A, A]`. However, this is over-specialised, since
// none of the operations demand that the loop back from the "unapply" to
// "apply" are the same type. That is to say, the (A) to (A) part is
// over-specialised and so the return type could well have been
// `Prism[T, T, A, B]`.

// However, to get more to the point of the question, we can now start to see a
// similarity. The `prismy` function captures the idea of `ApplyUnapply` in the
// original question statement.

// There is a catch though. These construction functions are unrecoverable from
// the prism. That is to say, given `ApplyUnapply`, we can obtain the `apply`
// and `unapply` functions. This is not true for a `Prism`.

// However, in practice, these are rarely necessary. Instead, the derivable
// operations of a prism are an intersecting set where:
// a) the few operations on `ApplyUnapply`, but not `Prism`, are almost never
//    necessary
// b) the much larger set of operations on `Prism` but not `ApplyUnapply` are
//    usually contributory to the solution.
// We sacrifice very little (in practice, nothing) for a large benefit.

// Defining the remainder of the `Prism` library is left as an exercise.

}

Matthew Pocock

unread,
Feb 22, 2015, 5:19:24 AM2/22/15
to scala-fu...@googlegroups.com
Thanks Tony. I have some more reading to do, it seems.

Matthew
Reply all
Reply to author
Forward
0 new messages