是否有 shorthand 用于 `fromNewtype 之类的操作。 F 。 toNewtype`?
Is there a shorthand for operations like `fromNewtype . f . toNewtype`?
通过 newtype
引入的类型安全性越多,越频繁出现的模式是将值 (或多个值) 投射到 newtype
wrapper,做一些操作,然后收回投影。一个普遍存在的例子是 Sum
和 Product
幺半群:
λ x + y = getSum $ Sum x `mappend` Sum y
λ 1 + 2
3
我想像 withSum
、withSum2
等函数的集合可能会自动为每个 newtype
推出。或者可能会创建一个参数化的 Identity
,以便与 ApplicativeDo
一起使用。或者还有其他一些我想不到的方法。
我想知道是否有相关的现有技术或理论。
P.S. 我对 coerce
不满意,原因有二:
安全 我觉得不是很安全。在被指出它实际上是安全的之后,我
尝试了一些东西,我不能做任何有害的事情,因为它需要一个类型注释
当有歧义的可能性时。例如:
λ newtype F = F Int deriving Show
λ newtype G = G Int deriving Show
λ coerce . (mappend (1 :: Sum Int)) . coerce $ F 1 :: G
G 2
λ coerce . (mappend (1 :: Product Int)) . coerce $ F 1 :: G
G 1
λ coerce . (mappend 1) . coerce $ F 1 :: G
...
• Couldn't match representation of type ‘a0’ with that of ‘Int’
arising from a use of ‘coerce’
...
但我还是不欢迎 coerce
,因为撕掉安全标签太容易了
射击某人,一旦伸手去拿它成为习惯。想象一下,在密码学中
应用程序,有两个值:x :: Prime Int
和 x' :: Sum Int
。我宁愿
每次我使用它们时输入 getPrime
和 getSum
,而不是 coerce
一切,有一天
犯了一个灾难性的错误。
有用性 coerce
对于 shorthand[ 并没有给 table 带来多少=58=] 为
某些操作。我在此处重复的 post 的主要示例:
λ getSum $ Sum 1 `mappend` Sum 2
3
— 变成类似于这个尖刺怪物的东西:
λ coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
3
— 这几乎没有任何好处。
是的,有!这是 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
它的开发是为了解决免费的问题,例如。通过应用 Sum
将 Int
转换为 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
的值(几乎总是 a
s) 和 return 包装结果 b'
。实际上这个函数几乎总是 foldMap
.
a'
展开 最终结果。展开由 ala
本身处理。
在你的情况下,它会是这样的:
ala Sum foldMap [1,2::Integer]
"ala"功能可以通过coerce
以外的方式实现,例如using generics to handle the unwrapping, or even lenses.
通过 newtype
引入的类型安全性越多,越频繁出现的模式是将值 (或多个值) 投射到 newtype
wrapper,做一些操作,然后收回投影。一个普遍存在的例子是 Sum
和 Product
幺半群:
λ x + y = getSum $ Sum x `mappend` Sum y
λ 1 + 2
3
我想像 withSum
、withSum2
等函数的集合可能会自动为每个 newtype
推出。或者可能会创建一个参数化的 Identity
,以便与 ApplicativeDo
一起使用。或者还有其他一些我想不到的方法。
我想知道是否有相关的现有技术或理论。
P.S. 我对 coerce
不满意,原因有二:
安全 我觉得不是很安全。在被指出它实际上是安全的之后,我 尝试了一些东西,我不能做任何有害的事情,因为它需要一个类型注释 当有歧义的可能性时。例如:
λ newtype F = F Int deriving Show λ newtype G = G Int deriving Show λ coerce . (mappend (1 :: Sum Int)) . coerce $ F 1 :: G G 2 λ coerce . (mappend (1 :: Product Int)) . coerce $ F 1 :: G G 1 λ coerce . (mappend 1) . coerce $ F 1 :: G ... • Couldn't match representation of type ‘a0’ with that of ‘Int’ arising from a use of ‘coerce’ ...
但我还是不欢迎
coerce
,因为撕掉安全标签太容易了 射击某人,一旦伸手去拿它成为习惯。想象一下,在密码学中 应用程序,有两个值:x :: Prime Int
和x' :: Sum Int
。我宁愿 每次我使用它们时输入getPrime
和getSum
,而不是coerce
一切,有一天 犯了一个灾难性的错误。有用性
coerce
对于 shorthand[ 并没有给 table 带来多少=58=] 为 某些操作。我在此处重复的 post 的主要示例:λ getSum $ Sum 1 `mappend` Sum 2 3
— 变成类似于这个尖刺怪物的东西:
λ coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer 3
— 这几乎没有任何好处。
是的,有!这是 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
coerce
对于这类事情来说可能非常棒。您可以使用它在具有相同表示的不同类型之间进行转换(例如在类型和新类型包装器之间,反之亦然)。例如:
λ coerce (3 :: Int) :: Sum Int
Sum {getSum = 3}
it :: Sum Int
λ coerce (3 :: Sum Int) :: Int
3
it :: Int
它的开发是为了解决免费的问题,例如。通过应用 Sum
将 Int
转换为 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
的值(几乎总是a
s) 和 return 包装结果b'
。实际上这个函数几乎总是foldMap
.a'
展开 最终结果。展开由ala
本身处理。
在你的情况下,它会是这样的:
ala Sum foldMap [1,2::Integer]
"ala"功能可以通过coerce
以外的方式实现,例如using generics to handle the unwrapping, or even lenses.