Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In functional Scala, what is a good way to go transform one parametrized type to another?

I need to implement a transformation from one data structure to another:

A[B] => C[D]

I could just implement it as a method:

def transform(in: A[B]): C[D] = ???

But I would like to do it in a type-driven development way, the code being scalable, loosely-coupled, and ready to be extended for new absurd requirements from business. So here's what I got:

type AB = A[B]
type CD = C[D]
trait Transformer[I,O] {
  def transform(in:I): O
}
implicit val abcdTransformer: Transformer[AB,CD] = 
  (in: AB) => ???

def transform[I,O](in: I)(implicit t: Transformer[I,O]): O = t.transform(in)

Not sure what exactly I'm gaining from it, and feels like an overkill. Is it really a good way to implement this transformation? Am I missing out on some library (cats) that already provides such boilerplate and more?

like image 351
Vasily802 Avatar asked Nov 15 '25 13:11

Vasily802


1 Answers

When there is single instance of a type class, there is not much difference between approaches based on type class and method.

With a type class you can define that you work with diffeferent types differently (type classes are type-level, compile-time "pattern-matching")

trait Transformer[I,O] {
  def transform(in:I): O
}
object Transformer {
  implicit val abcdTransformer: Transformer[AB,CD] = (in: AB) => ???
  implicit val efghTransformer: Transformer[EF,GH] = (in: EF) => ???
}

If your types "intersect", with a type class you can prioritize instances

trait Transformer[I,O] {
  def transform(in:I): O
}
trait LowPriorityTransformer {
  implicit val efghTransformer: Transformer[EF,GH] = (in: EF) => ???
}
object Transformer extends LowPriorityTransformer {
  implicit val abcdTransformer: Transformer[AB,CD] = (in: AB) => ???
}

With a type class you can define your logic inductively

trait Transformer[I,O] {
  def transform(in:I): O
}
object Transformer {  
  implicit def recurse(implicit t: Transformer[...]): Transformer[...] = ???
  implicit val base: Transformer[...] = ???
}

With a type class you can perform type-level calculations

trait Transformer[I] {
  type O
  def transform(in:I): O
}
object Transformer {
  implicit val abcdTransformer: Transformer[AB] { type O = CD } = ???
  implicit val efghTransformer: Transformer[EF] { type O = GH } = ???
}

def transform[I](in: I)(implicit t: Transformer[I]): t.O = t.transform(in)

Here are examples where replacing method with a type class makes the job

shapeless filter a list of options

How to overload generic method with different evidence without ambiguity?

When using HList with GADTs I am having to cast using asInstanceOf[H]. Is there a way to avoid the cast?

Also with a type class you can hide several implicit parameter in a single one encapsulating your logic in a type class

How to wrap a method having implicits with another method in Scala?

Implicit Encoder for TypedDataset and Type Bounds in Scala

Parameterized folding on a shapeless HList

Regarding hiding boilerplate, some boilerplate will be hidden in Dotty (Scala 3). There will not be much need in

def transform[I,O](in: I)(implicit t: Transformer[I,O]): O = t.transform(in) // (*)

any more. We can directly define type classes with extension methods

trait Transformer[I,O] {
  def (in:I) transform: O
}
object Transformer {
  given as Transformer[AB,CD] = (in: AB) => ??? // given is instead of implicit
}

import Transformer.{ given _}
ab.transform

In Scala 2 I have small library AUXify (not production-ready) to generate boilerplate like (*)

import com.github.dmytromitin.auxify.macros.delegated

@delegated
trait Transformer[I,O] {
  def transform(in:I): O
}
object Transformer {
  implicit val abcdTransformer: Transformer[AB,CD] = (in: AB) => ???
}

Transformer.transform(ab)

      // scalacOptions += "-Ymacro-debug-lite"
//Warning:scalac: {
//  abstract trait Transformer[I, O] extends scala.AnyRef {
//    def transform(in: I): O
//  };
//  object Transformer extends scala.AnyRef {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    def transform[I, O](in: I)(implicit inst$macro$1: Transformer[I, O]): O = inst$macro$1.transform(in);
//    implicit val abcdTransformer: Transformer[AB, CD] = ((in: AB) => $qmark$qmark$qmark)
//  };
//  ()
//}

or to generate extension methods (syntax)

import com.github.dmytromitin.auxify.macros.syntax

@syntax
trait Transformer[I,O] {
  def transform(in:I): O
}
object Transformer {
  implicit val abcdTransformer: Transformer[AB,CD] = (in: AB) => ???
}

import Transformer.syntax._
ab.transform[CD]

//Warning:scalac: {
//  abstract trait Transformer[I, O] extends scala.AnyRef {
//    def transform(in: I): O
//  };
//  object Transformer extends scala.AnyRef {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    object syntax extends scala.AnyRef {
//      def <init>() = {
//        super.<init>();
//        ()
//      };
//      implicit class Ops$macro$1[I] extends scala.AnyRef {
//        <paramaccessor> val in: I = _;
//        def <init>(in: I) = {
//          super.<init>();
//          ()
//        };
//        def transform[O]()(implicit inst$macro$2: Transformer[I, O]): O = inst$macro$2.transform(in)
//      }
//    };
//    implicit val abcdTransformer: Transformer[AB, CD] = ((in: AB) => $qmark$qmark$qmark)
//  };
//  ()
//}

or to generate materializer etc.

import com.github.dmytromitin.auxify.macros.apply

@apply
trait Transformer[I, O] {
  def transform(in:I): O
}
object Transformer {
  implicit val abcdTransformer: Transformer[AB, CD] = ???
}

Transformer[AB, CD].transform(ab)

//Warning:scalac: {
//  abstract trait Transformer[I, O] extends scala.AnyRef {
//    def transform(in: I): O
//  };
//  object Transformer extends scala.AnyRef {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    def apply[I, O](implicit inst: Transformer[I, O]): Transformer[I, O] = inst;
//    implicit val abcdTransformer: Transformer[AB, CD] = $qmark$qmark$qmark
//  };
//  ()
//}

Also extension methods (and materializer) for single-parameter type classes can be generated with Simulacrum

import simulacrum.typeclass

@typeclass
trait Transformer[I] {
  type O
  def transform(in:I): O
}
object Transformer {
  implicit val abcdTransformer: Transformer[AB] { type O = CD } = ???
}

Transformer[AB].transform(ab)

import Transformer.ops._
ab.transform

//Warning:scalac: {
//  @new _root_.scala.annotation.implicitNotFound("Could not find an instance of Transformer for ${I}") abstract trait Transformer[I] extends _root_.scala.Any with _root_.scala.Serializable {
//    type O;
//    def transform(in: I): O
//  };
//  object Transformer extends scala.AnyRef {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    implicit val abcdTransformer: Transformer[AB] {
//      type O = CD
//    } = $qmark$qmark$qmark;
//    @new scala.inline() def apply[I](implicit instance: Transformer[I]): Transformer[I] {
//      type O = instance.O
//    } = instance;
//    abstract trait Ops[I] extends scala.AnyRef {
//      def $init$() = {
//        ()
//      };
//      type TypeClassType <: Transformer[I];
//      val typeClassInstance: TypeClassType;
//      import typeClassInstance._;
//      def self: I;
//      def transform: O = typeClassInstance.transform(self)
//    };
//    abstract trait ToTransformerOps extends scala.AnyRef {
//      def $init$() = {
//        ()
//      };
//      @new java.lang.SuppressWarnings(scala.Array("org.wartremover.warts.ExplicitImplicitTypes", "org.wartremover.warts.ImplicitConversion")) implicit def toTransformerOps[I](target: I)(implicit tc: Transformer[I]): Ops[I] {
//        type TypeClassType = Transformer[I] {
//          type O = tc.O
//        }
//      } = {
//        final class $anon extends Ops[I] {
//          def <init>() = {
//            super.<init>();
//            ()
//          };
//          type TypeClassType = Transformer[I] {
//            type O = tc.O
//          };
//          val self = target;
//          val typeClassInstance: TypeClassType = tc
//        };
//        new $anon()
//      }
//    };
//    object nonInheritedOps extends ToTransformerOps {
//      def <init>() = {
//        super.<init>();
//        ()
//      }
//    };
//    abstract trait AllOps[I] extends Ops[I] {
//      type TypeClassType <: Transformer[I];
//      val typeClassInstance: TypeClassType
//    };
//    object ops extends scala.AnyRef {
//      def <init>() = {
//        super.<init>();
//        ()
//      };
//      @new java.lang.SuppressWarnings(scala.Array("org.wartremover.warts.ExplicitImplicitTypes", "org.wartremover.warts.ImplicitConversion")) implicit def toAllTransformerOps[I](target: I)(implicit tc: Transformer[I]): AllOps[I] {
//        type TypeClassType = Transformer[I] {
//          type O = tc.O
//        }
//      } = {
//        final class $anon extends AllOps[I] {
//          def <init>() = {
//            super.<init>();
//            ()
//          };
//          type TypeClassType = Transformer[I] {
//            type O = tc.O
//          };
//          val self = target;
//          val typeClassInstance: TypeClassType = tc
//        };
//        new $anon()
//      }
//    }
//  };
//  ()
//}
like image 87
Dmytro Mitin Avatar answered Nov 17 '25 07:11

Dmytro Mitin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!