Search code examples
scalahaskelltypes

Scala equivalent of Haskell type constructor/inverse?


I'm trying to port some code from Haskell to Scala and came across this pair of functions:

Screenshot from link

newtype OO f j a b

Composition of type constructors: unary with binary. Called StaticArrow in [1].

Constructors

OO     
    unOO :: f (a `j` b)

I tried to do this in Scala with something like

type OO[F[_], J[_, _], A, B] = F[J[A, B]]

but I think that's just unOO.

Can someone explain two things: (1) What do OO and unOO actually do? (I think they're doing some type coercions, but I can't be sure why.) (2) Is there an equivalent to these in Scala? Do I even need an equivalent to these in Scala?

For context, I'm trying to port the module fungll-combinators. There are examples of the use of OO and unOO in the file Join.hs file

Any help appreciated.


Solution

  • -- Haskell
    data OO f j a b = OO { unOO :: f (a `j` b) }
    

    corresponds to

    // Scala
    case class OO[F[_], J[_, _], A, B](unOO: F[A J B])
    

    while

    -- Haskell
    newtype OO f j a b = OO { unOO :: f (a `j` b) }
    

    corresponds to

    // Scala
    case class OO[F[_], J[_, _], A, B](unOO: F[A J B]) extends AnyVal
    

    But value classes (... extends AnyVal) have limitations: 1 2.

    Besides value classes, newtype can be implemented in Scala with the library scala-newtype (Scala 2)

    import io.estatico.newtype.macros.newtype
    
    @newtype case class OO[F[_], J[_, _], A, B](unOO: F[A J B])
    
    //scalac: {
    //  type OO[F[_$$1], J[_$$2, _$$3], A, B] = OO.Type[F, J, A, B];
    //  object OO extends scala.AnyRef {
    //    def <init>() = {
    //      super.<init>();
    //      ()
    //    };
    //    def apply[F[_$$1], J[_$$2, _$$3], A, B](unOO: F[J[A, B]]): OO[F, J, A, B] = unOO.asInstanceOf[OO[F, J, A, B]];
    //    final implicit class Ops$newtype[F[_$$1], J[_$$2, _$$3], A, B] extends AnyVal {
    //      <paramaccessor> val $this$: Type[F, J, A, B] = _;
    //      def <init>($this$: Type[F, J, A, B]) = {
    //        super.<init>();
    //        ()
    //      };
    //      def unOO: F[J[A, B]] = $this$.asInstanceOf[F[J[A, B]]]
    //    };
    //    implicit def opsThis[F[_$$1], J[_$$2, _$$3], A, B](x: Ops$newtype[F, J, A, B]): Type[F, J, A, B] = x.$this$;
    //    @new _root_.scala.inline() implicit def unsafeWrap[F[_$$1], J[_$$2, _$$3], A, B]: Coercible[Repr[F, J, A, B], Type[F, J, A, B]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def unsafeUnwrap[F[_$$1], J[_$$2, _$$3], A, B]: Coercible[Type[F, J, A, B], Repr[F, J, A, B]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def unsafeWrapM[M[_], F[_$$1], J[_$$2, _$$3], A, B]: Coercible[M[Repr[F, J, A, B]], M[Type[F, J, A, B]]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def unsafeUnwrapM[M[_], F[_$$1], J[_$$2, _$$3], A, B]: Coercible[M[Type[F, J, A, B]], M[Repr[F, J, A, B]]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def unsafeWrapK[T[_[F[_$$1], J[_$$2, _$$3], A, B]]]: Coercible[T[Repr], T[Type]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def unsafeUnwrapK[T[_[F[_$$1], J[_$$2, _$$3], A, B]]]: Coercible[T[Type], T[Repr]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def cannotWrapArrayAmbiguous1[F[_$$1], J[_$$2, _$$3], A, B]: Coercible[_root_.scala.Array[Repr[F, J, A, B]], _root_.scala.Array[Type[F, J, A, B]]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def cannotWrapArrayAmbiguous2[F[_$$1], J[_$$2, _$$3], A, B]: Coercible[_root_.scala.Array[Repr[F, J, A, B]], _root_.scala.Array[Type[F, J, A, B]]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def cannotUnwrapArrayAmbiguous1[F[_$$1], J[_$$2, _$$3], A, B]: Coercible[_root_.scala.Array[Type[F, J, A, B]], _root_.scala.Array[Repr[F, J, A, B]]] = Coercible.instance;
    //    @new _root_.scala.inline() implicit def cannotUnwrapArrayAmbiguous2[F[_$$1], J[_$$2, _$$3], A, B]: Coercible[_root_.scala.Array[Type[F, J, A, B]], _root_.scala.Array[Repr[F, J, A, B]]] = Coercible.instance;
    //    def deriving[TC$macro$1[_], F[_$$1], J[_$$2, _$$3], A, B](implicit ev: TC$macro$1[Repr[F, J, A, B]]): TC$macro$1[Type[F, J, A, B]] = ev.asInstanceOf[TC$macro$1[Type[F, J, A, B]]];
    //    def derivingK[TC$macro$1[_[_[_$$1], _[_$$2, _$$3], _, _]]](implicit ev: TC$macro$1[Repr]): TC$macro$1[Type] = ev.asInstanceOf[TC$macro$1[Type]];
    //    type Repr[F[_$$1], J[_$$2, _$$3], A, B] = F[J[A, B]];
    //    type Base = _root_.scala.Any {
    //      type __OO__newtype
    //    };
    //    abstract trait Tag[F[_$$1], J[_$$2, _$$3], A, B] extends _root_.scala.Any;
    //    type Type[F[_$$1], J[_$$2, _$$3], A, B] <: Base with Tag[F, J, A, B]
    //  };
    //  ()
    //}
    

    and opaque types (Scala 3)

    // outside the current scope it's not known that OO is F[A J B]
    
    opaque type OO[F[_], J[_, _], A, B] = F[A J B]
    
    object OO:
      def apply[F[_], J[_, _], A, B](x: F[A J B]): OO[F, J, A, B] = x
      def unOO[F[_], J[_, _], A, B](x: OO[F, J, A, B]): F[A J B] = x