我正在尝试理解 State newtype 并且我正在努力解决书中对同构的这种解释:
新类型必须与它们包装的类型具有相同的底层表示,因为新类型包装在编译时消失。因此 newtype 中包含的函数必须与它包装的类型同构。也就是说,必须有一种方法可以在不丢失信息的情况下从 newtype 到它包装的事物并再次返回。
应用于 State newtype 是什么意思?
newtype State s a = State { runState :: s -> (a, s) }
Run Code Online (Sandbox Code Playgroud)
这种解释“必须有一种方法可以从 newtype 到它包装的东西然后再回来”尚不清楚。
另外,你能不能说一下,在这个例子中哪里有同构,哪里没有,为什么。
type Iso a b = (a -> b, b -> a)
newtype Sum a = Sum { getSum :: a }
sumIsIsomorphicWithItsContents :: Iso a (Sum a)
sumIsIsomorphicWithItsContents = (Sum, getSum)
(a -> Maybe b, b -> Maybe a)
[a] -> a, a -> [a]
Run Code Online (Sandbox Code Playgroud)
您引用的声明没有State具体提及。这纯粹是关于newtypes的陈述。这是一个有点在提到“包含在NEWTYPE功能”,因为对于一个包裹类型没有要求误导newtype是一个函数类型-尽管这是对的情况下State被定义了许多其他常用的类型和newtype。
newtype一般来说, a 的关键正如它所说的那样:它必须简单地包装另一种类型,使从包装类型到包装类型变得微不足道,反之亦然,并且不会丢失信息 - 这是两种类型同构意味着什么,以及使两种类型具有相同的运行时表示完全安全的原因。
很容易演示data不可能实现这一点的典型声明。例如,采用具有 2 个构造函数的任何类型,例如Either:
data Either a b = Left a | Right b
Run Code Online (Sandbox Code Playgroud)
很明显,这与其任一构成类型都不是同构的。例如,Left构造函数嵌入a在 中Either a b,但您无法通过Right这种方式获取任何值。
并且即使使用单个构造函数,如果它需要多个参数——例如元组构造函数(,)——那么同样,您可以嵌入任何一种构成类型(给定另一种类型的任意值),但您不可能获得每个价值。
这就是为什么newtype关键字只允许用于带有单个参数的单个构造函数的类型。这总是提供同构,因为 given newtype Foo a = Foo a, thenFoo构造函数和函数\Foo a -> a彼此之间是微不足道的。对于更复杂的示例,其中类型构造函数采用更多类型参数,和/或包装类型更复杂,这同样适用。
情况正是如此State:
newtype State s a = State {runState :: s -> (a, s)}
Run Code Online (Sandbox Code Playgroud)
函数State和runState分别包装和解开底层类型(在这种情况下是一个函数),并且显然彼此相反 - 因此它们提供了同构。
最后请注意,这里在定义中使用记录语法没有什么特别之处——尽管在这种情况下为了拥有一个已经命名的“解包”函数,这很常见。除了这个小小的便利之外,与newtype没有记录的定义语法没有区别。
退一步说:newtype声明data与具有单个构造函数和单个参数的声明非常相似- 区别主要在于性能,因为关键字告诉编译器这两种类型是等效的,因此之间没有运行时转换开销两种类型,否则会有。(关于懒惰也有区别,但我不会提到这一点,除了这里的完整性。)至于为什么这样做而不是仅仅使用底层类型 - 这是为了提供额外的类型安全(这里有 2 种不同的类型)对于编译器,即使它们在运行时相同),并且还允许指定类型类实例而不将它们附加到基础类型。Sum并且Product是很好的例子,在这里,为他们提供Monoid 数字类型的实例,分别基于加法和乘法,没有给出作为底层类型的“Monoid”实例的不应有的区别。
类似的东西也在起作用State——当我们使用这种类型时,我们明确表示我们正在使用它来表示状态操作,如果我们只是使用碰巧返回一对的普通函数,情况就不会如此。