实现某个类型类的类列表

时间:2018-03-12 16:18:57

标签: scala typeclass

我想定义实现公共类型类的List个元素。 E.g。

  trait Show[A] {
    def show(a: A): String
  }
  implicit val intCanShow: Show[Int] = new Show[Int] {
      def show(int: Int): String = s"int $int"
  }
  implicit val stringCanShow: Show[String] = new Show[String] {
    def show(str: String): String = str
  }

问题是,如何定义list = List(1, "abc")以确保这些值的Show实例在范围内?然后,我想将此列表映射到list map {_.show}

之类的节目上

4 个答案:

答案 0 :(得分:5)

我将首先草拟一个解决方案,然后解释为什么使用List[Any](1, "abc")的天真方法无效。

你能做什么

定义一个包装类,它可以包含A类型的实例以及Show[A]的实例:

case class Showable[A](a: A, showInst: Show[A]) {
  def show: String = showInst.show(a)
}

将您的列表定义为List[Showable[_]]

var showableList: List[Showable[_]] = Nil

也许定义一个单独的方法来填充这个列表(考虑将列表本身和类中的builder-method打包):

def addShowable[A: Show](a: A): Unit = {
  showableList ::= Showable[A](a, implicitly[Show[A]])
}

或者,您可以仔细添加(非常严格范围的)隐式转换:

implicit def asShowable[A](a: A)(implicit s: Show[A]): Showable[A] = 
  Showable(a, s)

然后按如下方式对列表进行组合(请注意显式类型归属):

val showableList = List[Showable[_]](1, "abc")

现在,您可以查看列表并致电show

showableList.map(_.show)

获取String

的列表

你不能做什么

你不能简单地定义

val list: List[Any] = List(1, "abc", <showable3>, ..., <showableN>)

然后希望能够拨打show,因为要拨打Show.show,您需要实际Show个实例。这些东西不是可以在运行时擦除的 type-hints ,它们是实际对象,它们必须由编译器提供。一旦创建了List[Any],就会丢失所有类型,因为所有类型都被合并到一个未表达的上限Any中,并且编译器无法注入所有必要的含义Show[T_1], ......,Show[T_N]。该论点与第三部分"Dealing with implicits when defining interpreter for the Free monad" of this lengthy answer of mine非常相似。

答案 1 :(得分:4)

处理此问题的另一种方法是使用无形库。 我真的会推荐this book,它以简明扼要的方式解释无形。

在这种情况下,Shapeless提供了两件我认为可以帮到你的东西:

  1. 异构列表(HList)
  2. 支持HList映射操作的多态函数。
  3. 首先导入所需的库(shapeless):

    import shapeless.{HNil, Poly1, ::}
    

    创建所需类型的异构列表。请注意,类型注释仅用于清晰。

    val data : Int :: String :: HNil = 1 :: "hello" :: HNil
    

    创建一个多态函数,为您需要的每种类型定义隐式值。

    object Show extends Poly1 {
        implicit def atT[T: Show] = at[T] (implicitly[Show[T]].show)
    }
    

    Shapeless为HList上的map提供扩展方法,以便将show函数应用于列表中的每个元素

    val result : String :: String :: HNil = data.map(Show)
    

    编辑:感谢@ dk14建议改进Show多态函数的定义。

答案 2 :(得分:4)

这里的核心问题是您要创建异类列表,例如List[Int, String]而不是List[Any]。这意味着您需要一个不同的结构来保留IntString类型,但仍然可以&#34;可映射&#34;喜欢List。 scala-library中可以包含异类型的一个结构是元组:

val tuple = (1, "abc")
val result = List(implicitly[Show[Int]].show(tuple._1), implicitly[Show[Int]].show(tuple._2))

然而,scala-library不能map超过元组 - 你可能需要一些语法糖来提高可读性。

所以显而易见的解决方案是HList来自Shapeless:Int :: String :: HNil(或者你可以使用元组操作并留在(Int, String)

import shapeless._
import poly._


//show is a polymorphic function
//think of it as `T => String` or even `(Show[T], T) => String` 
object show extends Poly1 {
  implicit def atT[T: Show] = at[T](implicitly[Show[T]].show)
}

@ (1 :: "aaaa" :: HNil) map show
res8: String :: String :: HNil = "int 1" :: "aaaa" :: HNil

或者您可以使用at[Int] / at[String]代替类型类,例如@Steve Robinson的答案。

P.S。可以找到lib here。它们还提供单层衬里以获得无形集成的Ammonite REPL,因此您可以尝试使用我的示例:

curl -s https://raw.githubusercontent.com/milessabin/shapeless/master/scripts/try-shapeless.sh | bash    

注意:

  1. 实际上,无形状解决方案需要与基于元组的解决方案相同的维护量。这是因为您必须始终跟踪IntString类型 - 您永远不会忘记这些(与同源List[T]案例不同)。所有Shapeless都为你做的是更好的语法,有时更好的类型推断。

  2. 如果你使用元组 - 你可以使用implicit class代替类似Haskell的风格来提高可读性,或者如果你仍然想要类似Haskell,那么有一个Simulacrum宏可以更好类型语法。

  3. 鉴于其他scala-library-only替代方案只是在一些常规类中捕获类型类实例,那么使用常规OOP包装类可能会更好:

    trait Showable[T]{def value: T; def show: String}
    class IntShow(val value: Int) extends Showable[Int]{..}
    class StringShow(val value: String) extends Showable[String] {..}
    
    val showables: List[Showable[_]] = List(new Showable(5), new Showable("aaa"))
    showables.map(_.show)
    

    对我来说看起来更干净,更具可读性:)

    如果您想以FP风格重写动态调度:

    sealed trait Showable
    final case class ShowableInt(i: Int) extends Showable
    final case class ShowableString(s: String) extends Showable
    
    implicit class ShowableDispatch(s: Showable){
      def show = s match{ //use `-Xfatal-warnings` scalac option or http://www.wartremover.org/ to guarantee totality of this function
        case ShowableInt(i) => ...
        case ShowableString(s) => ...
      }
    }
    
    List(ShowableInt(5), ShowableString("aaa")).map(_.show)
    

    如果你真的想要静态调度(或ad-hoc多态),假设其他解决方案引入Showable[_]实际上是Showable[Any]

    case class Showable[T](v: T, show: String)
    def showable(i: Int) = Showable(i, s"int $i") 
    def showable(s: String) = Showable(i, s) 
    List(showable(5), showable("aaa")).map(_.show)
    

答案 3 :(得分:0)

Disclamer:我已经提出这个答案来解决具体的开发问题,而不是使用类型类的理论问题

我会这样做:

trait Showable{ def show(): String }

implicit class IntCanShow(int: Int) extends Showable {
  def show(): String = s"int $int"
}

implicit class StringCanShow(str: String) extends Showable {
  def show(): String = str
}

val l: List[Showable] = List(1,"asd")

请注意,我将特征Show的含义更改为Showable,以便将实现类用作包装器。因此,我们可以简单地要求我们想要Showable实例 (因为这些类是隐式的,List的输入会自动换行)