2017-06-14 65 views
1

最初由3个类型类型设计的软件已经发展到使用大量的类型类。让我们以这个例子:存在越来越多的类型分类时的分层软件

  • 你有Service[A,B]
  • 你有TypeClass1[A]
  • 你有TypeClass2[B]
  • 你有TypeClass3[A,B,C]
  • 你有一个TypeClass4[A]
  • 你有TypeClass5[B]
  • 您有TypeClass6[C]
  • 你有TypeClass7[C]
  • 你有TypeClass8[B]

我们有这是完全不相交的A,B,C的6个已知组合(即每种组合都包含仅在该组合中使用的A,B,C类型)。所以,现在我们有两个选择:

  1. 写这将需要8种不同类型类
  2. 创建中间类型类,我们可以推导出如 TypeClass14[A]Typeclass67[C]等顶级方法,所以最后你 能最终得到一个UberTypeClass[A,B,C],它提供了所有的 所需的行为。

作为一种通用的编程方法,第二种解决方案显然更合理:引入中间层和抽象提高了代码质量的多种方式。我们面临的挑战是:

  1. 如果我们没有在中间层通过提供堆叠起来的隐式转换,在现实中我们只是提高了语法召唤较低级别的类型类(但不管怎么说,这些都是implicits!),但如果我们不召唤下级类型类我们没有解决的代码缺乏层次感

  2. 问题,我们最终写了很多中间样板(TypeClass1InstancesTypeClass2Instances的,Typeclass3Instances

当使用与软件分层相关的类型类时,什么是最佳实践?

回答

1

你可以使用类似下面的东西来捆绑类型类。

你也可以使用无形状的HList将这个代码推广到任意的形式。

trait Lemma2[F1[_], F2[_], A] { 
    val F1: F1[A] 
    val F2: F2[A] 
} 

object Lemma2 { 
    implicit def lemma[A, F1[_], F2[_]](implicit FA: F1[A], FB: F2[A]): 
    Lemma2[F1, F2, A] = 
    new Lemma2[F1, F2, A] { 
     val F1 = FA 
     val F2 = FB 
    } 

} 

trait LemmaSyntax { 
    implicit def f1[F1[_], F2[_], A](implicit lemma: Lemma2[F1, F2, A]): F1[A] = { 
    lemma.F1 
    } 

    implicit def f2[F1[_], F2[_], A](implicit lemma: Lemma2[F1, F2, A]): F2[A] = { 
    lemma.F2 
    } 
} 

import cats._ 
import cats.implicits._ 

object Test extends LemmaSyntax { 
    type Proof[A] = Lemma2[Show, Eq, A] 


    def foo[A : Proof](a: A): String = { 
    a.show 
    } 
} 

object Other { 
    Test.foo("moo") 
} 
+0

这样做的方向是将所有类型暴露给顶层而不构建中间抽象。这不违反模块化吗? – Edmondo1984

+0

这取决于你如何看待它。让我们考虑一下我有一个需要'TC1 [_]'和'TC2 [_]'的函数。在没有这种复合类型类服从的规律的情况下,我真的只想对类型系统说,这个函数需要这两个类型类遵循类型类的自身定律。这经常发生在应用程序代码中。在另一种情况下,如果有法律要遵守,单纯的存在是不够的,所以我会引入另一个'TC'。 – yw3410

+1

后者的一个很好的例子是一个'编码器[A]'和一个'解码器[A]'。把它们放在一起,你会得到一个需要遵循法律的'Codec [A]'a.encode.decode === a'。另一方面,我不能在示例中的'Show'和'Eq'之间陈述任何新的东西。 – yw3410