【问题标题】:Isomorphism in newtype新类型中的同构
【发布时间】:2020-06-23 17:01:30
【问题描述】:

我正在尝试理解 State newtype,但我正在为一本书中同构的这种解释而苦苦挣扎:

Newtypes 必须具有与它们包装的类型相同的底层表示,因为 newtype 包装器在编译时会消失。所以 newtype 中包含的函数必须与它包装的类型同构。也就是说,必须有一种方法可以在不丢失信息的情况下从 newtype 转到它所包装的东西并再次返回。

应用于 State newtype 是什么意思?

newtype State s a = State { runState :: s -> (a, s) } 

“必须有一种方法可以从 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] 

【问题讨论】:

  • "应用于 State newtype 是什么意思?" -- State 构造函数朝一个方向运行,runState 函数朝相反方向运行。 runState . State = idState . runState = id

标签: haskell


【解决方案1】:

你引用的声明没有特别提到State。这纯粹是关于newtypes 的声明。提到“新类型中包含的函数”有点误导,因为newtype 包装的类型不需要是函数类型 - 尽管State 和许多其他常用的情况就是这种情况newtype定义的类型。

一般来说,newtype 的关键是正如它所说的那样:它必须简单地包装另一种类型,使得从包装类型到包装类型变得微不足道,反之亦然,没有信息丢失 - 这就是两种类型同构的含义,也是使两种类型具有相同的运行时表示完全安全的原因。

很容易演示无法实现这一点的典型data 声明。例如采用任何具有 2 个构造函数的类型,例如 Either:

data Either a b = Left a | Right b

很明显,这与其任何一个组成类型都不同构。例如,Left 构造函数将a 嵌入到Either a b 中,但您无法通过这种方式获取任何Right 值。

即使使用单个构造函数,如果它需要多个参数 - 例如元组构造函数 (,) - 再一次,您可以嵌入任何一个组成类型(给定另一种类型的任意值),但您不可能得到每一个值。

这就是为什么 newtype 关键字只允许用于具有单个构造函数的类型,该构造函数采用单个参数。这总是提供同构,因为给定newtype Foo a = Foo a,那么Foo 构造函数和函数\Foo a -> a 是彼此微不足道的逆。对于类型构造函数采用更多类型参数和/或包装类型更复杂的更复杂的示例,这同样适用。

State 正是如此:

newtype State s a = State {runState :: s -> (a, s)}

函数StaterunState 分别包装和解包底层类型(在本例中是一个函数),并且显然彼此相反 - 因此它们提供了同构。

最后请注意,在定义中使用记录语法并没有什么特别之处——尽管在这种情况下为了拥有一个已经命名的“解包”函数而很常见。除了这个小小的便利之外,与没有记录语法定义的newtype 没有区别。

退一步说:newtype 声明与 data 声明非常相似,只有一个构造函数和一个参数 - 区别主要在于性能,因为关键字告诉编译器这两种类型是等价的,所以这两种类型之间没有运行时转换开销,否则会有。 (关于惰性也有区别,但我不会提及,除了这里是为了完整性。)至于为什么这样做而不是仅仅使用底层类型——这是为了提供额外的类型安全(这里有 2 种不同的类型对于编译器,即使它们在运行时相同),并且还允许指定类型类实例而不将它们附加到基础类型。 SumProduct 在这里是很好的例子,因为它们分别基于加法和乘法提供了数字类型的 Monoid 实例,而没有给出作为底层类型的“the” Monoid 实例的不应有的区别。

State 也有类似的情况——当我们使用这种类型时,我们明确表示我们正在使用它来表示状态操作,如果我们只是使用发生的普通函数,情况就不会如此返回一对。

【讨论】:

  • 不只使用底层类型的一个重要原因是如果您需要递归。 newtype Fix f = In {out :: f (Fix f)} 是一个经典的例子。编译器会拒绝type Fix f = f (Fix f)
猜你喜欢
  • 2018-10-31
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-01
  • 2014-08-03
  • 1970-01-01
相关资源
最近更新 更多