是否有 shorthand 用于 `fromNewtype 之类的操作。 F 。 toNewtype`?

Is there a shorthand for operations like `fromNewtype . f . toNewtype`?

通过 newtype 引入的类型安全性越多,越频繁出现的模式是将值 (或多个值) 投射到 newtypewrapper,做一些操作,然后收回投影。一个普遍存在的例子是 SumProduct 幺半群:

λ x + y = getSum $ Sum x `mappend` Sum y
λ 1 + 2
3

我想像 withSumwithSum2 等函数的集合可能会自动为每个 newtype 推出。或者可能会创建一个参数化的 Identity,以便与 ApplicativeDo 一起使用。或者还有其他一些我想不到的方法。

我想知道是否有相关的现有技术或理论。

P.S. 我对 coerce 不满意,原因有二:

是的,有!这是 base 包中的一个 coerce 函数。它允许从 newtype 自动转换为 newtype。 GHC 实际上有很大一部分强制转换背后的理论。

relude中我调用了这个函数under

ghci> newtype Foo = Foo Bool deriving Show
ghci> under not (Foo True)
Foo False
ghci> newtype Bar = Bar String deriving Show
ghci> under (filter (== 'a')) (Bar "abacaba")
Bar "aaaa"

您可以在这里看到整个模块:

也可以为二元运算符实现自定义函数:

ghci> import Data.Coerce 
ghci> :set -XScopedTypeVariables 
ghci> :set -XTypeApplications 
ghci> :{
ghci| via :: forall n a . Coercible a n => (n -> n -> n) -> (a -> a -> a)
ghci| via = coerce
ghci| :}
ghci> :{
ghci| viaF :: forall n a . Coercible a (n a) => (n a -> n a -> n a) -> (a -> a -> a)
ghci| viaF = coerce
ghci| :}
ghci> via @(Sum Int) @Int (<>) 3 4
7
ghci> viaF @Sum @Int (<>) 3 5
8
来自 Data.Coerce

coerce 对于这类事情来说可能非常棒。您可以使用它在具有相同表示的不同类型之间进行转换(例如在类型和新类型包装器之间,反之亦然)。例如:

λ coerce (3 :: Int) :: Sum Int
Sum {getSum = 3}
it :: Sum Int

λ coerce (3 :: Sum Int) :: Int
3
it :: Int

它的开发是为了解决免费的问题,例如。通过应用 SumInt 转换为 Sum Int,但它不一定是免费的,例如通过应用 [Int] 转换为 [Sum Int] =20=]。编译器可能能够优化从 map 遍历列表脊柱,也可能不会,但我们知道内存中的相同结构可以用作 [Int][Sum Int],因为列表结构不依赖于元素的任何属性,并且元素类型在这两种情况下具有相同的表示形式。 coerce(加上 role system)允许我们利用这个事实在两者之间进行转换,保证不做任何运行时工作,但仍然让编译器检查它是否安全这样做:

λ coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum {getSum = 1},Sum {getSum = 2},Sum {getSum = 3}]
it :: [Sum Int]

起初对我来说一点都不明显的是 coerce 不仅限于强制 "structures"!因为它所做的只是允许我们在表示相同时替换类型(包括复合类型的部分),所以它同样适用于强制 code:

λ addInt = (+) @ Int
addInt :: Int -> Int -> Int

λ let addSum :: Sum Int -> Sum Int -> Sum Int
|     addSum = coerce addInt
| 
addSum :: Sum Int -> Sum Int -> Sum Int

λ addSum (Sum 3) (Sum 19)
Sum {getSum = 22}
it :: Sum Int

(在上面的示例中,我必须定义 + 的单版版本,因为 coerce 是如此通用,类型系统否则不知道 + 的哪个版本我' m 要求强制转换为 Sum Int -> Sum Int -> Sum Int;我本可以在 coerce 的参数上给出一个内联类型签名,但这看起来不太整洁。通常在实际使用中,上下文足以确定 "source" 和 "target" 类型的 coerce)

我曾经写过一个库,它提供了几种通过新类型对类型进行参数化的不同方式,并为每个方案提供了类似的 API。实现 API 的模块充满了类型签名和 foo' = coerce foo 样式定义;除了说明我想要的类型之外,我几乎没有做任何工作,感觉真的很好。

您的示例(在 Sum 上使用 mappend 来实现加法,而无需明确地来回转换)可能如下所示:

λ let (+) :: Int -> Int -> Int
|     (+) = coerce (mappend @ (Sum Int))
| 
(+) :: Int -> Int -> Int

λ 3 + 8
11
it :: Int

您的 "spiked monster" 示例通过将被加数放入列表并使用可用的 ala 函数 here 得到更好的处理,其类型为:

ala :: (Coercible a b, Coercible a' b') 
    => (a -> b) 
    -> ((a -> b) -> c -> b')   
    -> c 
    -> a' 

哪里

  • a展开 基本类型。
  • b 是包装 a.
  • 的新类型
  • a -> b 是新类型构造函数。
  • ((a -> b) -> c -> b') 是一个函数,知道如何包装基本类型 a 的值,知道如何处理类型 c 的值(几乎总是 as) 和 return 包装结果 b'。实际上这个函数几乎总是 foldMap.
  • a' 展开 最终结果。展开由 ala 本身处理。

在你的情况下,它会是这样的:

ala Sum foldMap [1,2::Integer]

"ala"功能可以通过coerce以外的方式实现,例如using generics to handle the unwrapping, or even lenses.