scala 类型类和代数数据类型之间的差异

up9lanfz  于 2023-01-02  发布在  Scala
关注(0)|答案(3)|浏览(160)

据我所知,类型类不是具体的东西,而只是一个用于ad-hoc和参数多态的构造。Eq半群是类型类的例子。另一方面,有代数数据类型,这是一个具体的复合类型,例如,EitherMaybe。它们也是函子
因此,JavaScript有一个代数数据类型规范:https://github.com/fantasyland/fantasy-land。在本页中,Setoid(Eq)、Ord半群也是ADT。但它是否正确?如果正确,它们是什么类型的复合?
我还找到了这篇关于类型类的文章,这里的Functor和Monad都是类型类。https://typelevel.org/cats/typeclasses.html#type-classes-in-cats。这是否意味着或者也许也是类型类?

Eq是 * 类型类 * 还是 * 代数数据类型 *?还是两者都是?Functor相同

pvabu6sv

pvabu6sv1#

我们将数据(值、项)收集到 * types *(数据类型)中,也可以将类型收集到 * type class * 中。
1"a"trueSome(1)None,...是值。它们属于类型IntStringBooleanOption[Int],...。类型可以属于类型类EqOrdFunctor,...
ADT(代数数据类型)是通过求和和乘积构造的数据类型

// Scala
sealed trait MyTrait
case class MyClass1(i: Int, s: String) extends MyTrait
case class MyClass2(b: Boolean) extends MyTrait
-- Haskell
data MyTrait = MyClass1 { i :: Int, s :: String } | MyClass2 { b :: Bool }

其中MyTraitMyClass1MyClass2之和,MyClass1IntString的乘积,MyClass2是单个乘法器Boolean的乘积。
也有一些数据类型不是ADT。例如,函数类型A => B、联合类型A | B、交集类型A & BA with B、存在类型等。Scala ADT是自动泛化的ADT(GADT)。在依赖类型语言中有ADT的推广,Sigma-和Pi-类型(即依赖和与积)。
类型类是一种描述行为的FP方式(通过adhoc多态性、早期绑定、静态/编译时调度)。我们可以使类型成为类型类的 * 示例 *
一个二个一个一个
这里MyClass1MyClass2MyTrait(在Haskell中只有MyTrait)是类型,它们是类型类MyTypeclass的示例。
类型类的另一种选择(描述行为的另一种方式)是OOP继承(通过子类型多态性、后期绑定、动态/运行时分派)

sealed trait MyTrait {
  def foo(): Unit
}
case class MyClass1(i: Int, s: String) extends MyTrait {
  override def foo(): Unit = println(s"MyClass1: i=$i, s=$s")
}
case class MyClass2(b: Boolean) extends MyTrait {
  override def foo(): Unit = println(s"MyClass2: b=$b")
}

我们可以将ADT "提升"到类型类中,例如标准ADT

sealed trait Option[+A]
case class Some[+A](a: A) extends Option[A]
case object None extends Option[Nothing]
data Maybe a = Just a | Nothing

可以成为类型类及其示例

trait Option[A]

case class Some[A](a: A)
case object None
type None = None.type

implicit def someOption[A]: Option[Some[A]] = new Option[Some[A]] {}
implicit val noneOption: Option[None] = new Option[None] {}
class Maybe a
 
newtype Just a = Just a
data Nothing a = Nothing
 
instance Maybe (Just a)
instance Maybe (Nothing a)

在本页中,SetoidEq)、OrdSemigroup也是ADT,但是否正确?如果正确,它们是由哪些类型组成的?
通常,EqOrdSemigroup不是ADT。它们不是通过求和和乘积构造的。它们是类型类。它们描述行为,即如何比较元素的相等性、如何比较元素的顺序、如何添加元素、什么是单位元素。它们由声明为示例的所有类型组成,例如IntString等(即,其中以某种特定方式实现对应的行为)。
是否意味着EitherMaybe也是类型类?
通常,EitherMaybe不是类型类,它们是ADT。但是如果我们真的需要,我们可以将ADT提升到类型类中,如我上面所示。
Eq是类型类还是代数数据类型?或者两者都是?Functor相同。
EqFunctor是类型类,而不是ADT。

kiz8lqtg

kiz8lqtg2#

代数数据类型和类型类都是“具有多种类型的事物”,我认为这就是让你困惑的地方。我认为我们很难回答你的问题(甚至很难弄清楚它是什么)的原因是,除此之外,它们几乎没有任何共同之处。
类型类可以被看作是一个“契约”或“规范”,它独立于任何具体类型而存在,并且它们是“开放的”:并且你所想到的任何类型都可以实现必要的细节来成为集合的成员。我们在包含这个概念的不同语言中以不同的方式来指定它。
例如,生 rust 性状Display

use std::fmt;

struct Foo(i32);

impl fmt::Display for Foo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

或者像Show这样的Haskell类型类:

data Foo = Int deriving Show

在这两种情况下,我们都基于整数类型创建了一个非常基本的类型Foo,它在各自的语言中实现了“has a string representation”类型类,正如我之前所说的,实现Show/Display的类型集是 open:我们可以用任何合适的类型永远这样做。
Maybe形成对比,Maybe是一个标记的并集(它将其限定为代数数据类型)aMaybe并集中的类型集是 * 封闭的 :你可以使用任何你想要的类型a,但是你不能给联合体添加新的东西。
Maybe是一个 type(或者更准确地说,是一个类型构造函数),而不是一个 typeclass。说Foo实现/派生Maybe是没有意义的。Maybe不是其他类型要遵循的规则集,
任何东西 * 都可以是Maybe Whatever
但是Maybe作为一个类型确实 * 实现 * 了一些类型类,例如Monad类型类(以及隐含的FunctorApplicative等)。

5lhxktic

5lhxktic3#

混淆的根本原因在于概念与实现的区别。
所以让我们试着把其中的一些弄清楚。

类型类

是实现多态的模式;准确地说是特别多态性。
在某些语言中,这是该语言提供的一个特征;例如Haskell或** rust eclipse
其他,允许您使用其他功能来表达它们;例如,Scala使用隐式,这意味着它们是该语言中的值,而不是
Rust***(AFAIK)* 中的值
最后,在理论上,你可以用任何语言建模,但是你需要手动传递示例,这就是通常所说的策略模式;一个例子是JavaComparator,有些人认为如果值必须显式传递,那么它就不是类型类,我个人不同意,但我们不要打开潘多拉的盒子。

代数数据类型(ADT)

是一种基于简单构造函数(称为乘积和联合)对数据建模的模式。
同样,有些语言提供了开箱即用的构建块,如Haskell;还有一些语言在编译器的帮助下,在经典的子类型和对象上模拟它们,如Scala
同样,理论上,您可以用任何语言对它们建模,并用fold替换模式匹配,但是,在这种情况下,我确实同意,如果UX不令人愉快,并且语言中没有内置的模式匹配,那么就很难谈论ADT。

函数

是一个抽象概念,一个从范畴论中衍生出来的概念。
从本质上讲,它由三个部分组成:

  • 类型* -> * * 的类型F[_](通常称为类型构造函数)*;例如List
  • 给定类型Fmap[A, B](fa: F[A])(f: A => B): F[B]实现
  • 这种实现满足Functor定律的形式证明。

我不会试图在这里解释Functor是什么...
但是,一个快速的TL; DR;表示通过将函数应用于它将计算的值来转换某些上下文的可能性。
由于Functor是一种抽象,我们需要某种形式的多态来表示它。经验告诉我们,类型类是对FunctorMonadMonoid等进行建模的最自然的方式。但是,您可以尝试使用其他方法,如结构类型或子类型......但实际上,您将很快遇到瓶颈。

额外

除此之外,我们还需要添加每种语言可能具有的其他间接层。
例如,在Scala中,我们使用子类型来建模类型类层次结构,因此Monad[F] extends Functor[F],并且使用相同的关键字来建模子类型抽象和类型类抽象trait
因此,始终将概念/思想与其底层实现分离是很重要的;在一天结束的时候,一切都将只是一个奇怪的机器上的电脉冲:p

相关问题