В функционале Scala, как лучше go преобразовать один параметризованный тип в другой? - PullRequest
2 голосов
/ 07 мая 2020

Мне нужно реализовать преобразование из одной структуры данных в другую:

A[B] => C[D]

Я мог бы просто реализовать это как метод:

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

Но я бы хотел это сделать при разработке, управляемой типами, код является масштабируемым, слабо связанным и готовым к расширению для удовлетворения новых абсурдных требований бизнеса. Итак, вот что я получил:

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)

Не уверен, что именно я получаю от этого, и чувствую себя излишним. Действительно ли это хороший способ реализовать эту трансформацию? Я упускаю какую-то библиотеку (кошек), которая уже предоставляет такой шаблон и многое другое?

Ответы [ 2 ]

3 голосов
/ 07 мая 2020

Когда существует единственный экземпляр класса типа, нет большой разницы между подходами, основанными на классе типа и методе.

С классом типа вы можете определить, что вы работаете с разными типами по-разному (классы типов являются уровнем типа, "сопоставление с образцом" во время компиляции)

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) => ???
}

Если ваши типы «пересекаются», с классом типа вы можете установить приоритет экземпляров

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) => ???
}

С типом class вы можете определить свой logi c индуктивно

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

С помощью класса типа вы можете выполнять вычисления на уровне типа

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)

Вот примеры, в которых замена метода классом типа делает задание

бесформенный фильтр список параметров

Как перегрузить общий c метод с разными доказательствами без двусмысленности?

При использовании HList с GADT мне приходится выполнять приведение с использованием asInstanceOf [H]. Есть ли способ избежать приведения?

Также с классом типа вы можете скрыть несколько неявных параметров в одном, инкапсулируя ваш лог c в класс типа

Как обернуть метод, имеющий неявные признаки, в другой метод в Scala?

Неявный кодировщик для TypedDataset и границ типов в Scala

Параметризованное сворачивание в бесформенном HList

Что касается скрытия шаблона, некоторые шаблоны будут скрыты в Dotty (Scala 3).

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

больше не понадобится. Мы можем напрямую определить классы типов с помощью методов расширения

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

В Scala 2 У меня есть небольшая библиотека AUXify (не производственная - готов), чтобы сгенерировать шаблон вроде (*)

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)
//  };
//  ()
//}

или сгенерировать методы расширения (синтаксис)

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)
//  };
//  ()
//}

или сгенерировать материализатор et c.

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
//  };
//  ()
//}

Также методы расширения (и материализатор) для классов с одним параметром могут быть сгенерированы с помощью 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()
//      }
//    }
//  };
//  ()
//}
1 голос
/ 07 мая 2020

Стандартная библиотека (2.13.x) очень близка к тому, что вам нужно, в зависимости от того, что именно вам нужно.

import scala.collection.Factory

implicit class AB2CD[A,B](from :IterableOnce[A]) {
  def transit[CC[_]](f :A => B
                   )(implicit fctry: Factory[B, CC[B]]
                   ) :CC[B] = {
    val bs = LazyList.unfold(from.iterator) { itr =>
               Option.when(itr.hasNext) (f(itr.next()), itr)
             }
    fctry.fromSpecific(bs)
  }
}

тестирование:

Option(88).transit[Vector](_.toString)//res0: Vector[String] = Vector(88)
Seq('c','x').transit[Set](_.asDigit)  //res1: Set[Int] = Set(12, 33)
List(1.1,2.2).transit[Array](_ < 2)   //res2: Array[Boolean] = Array(true, false)

Из-за ограничения IterableOnce, это не будет проходить из Array и не будет передано в String или из него. Для этого есть обходной путь, но я хотел go только пока с ним.

...