通过newtype
引入类型安全性越强的一种表示自身的模式越多,它会将一个值(或多个值)投射到一个newtype
包装器中,做一些操作,然后缩回投影。一个普遍存在的例子是Sum
和Product
id半群的例子:
λ x + y = getSum $ Sum x `mappend` Sum y
λ 1 + 2
3
我想象withSum
,withSum2
等功能的集合可能会针对每个newtype
自动推出。或者,可能会创建参数化的Identity
,以与ApplicativeDo
一起使用。也许还有其他我想不到的方法。
我想知道是否存在一些现有技术或理论。
PS 。我对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
对于速记的作用不大
某些操作。我的帖子的主要示例,在此重复:
λ getSum $ Sum 1 `mappend` Sum 2
3
—变成了与这个尖刺怪兽相似的东西:
λ coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
3
—几乎没有任何好处。
答案 0 :(得分:9)
通过将求和项放入列表并使用here类型的ala
函数,可以更好地处理您的“尖峰怪兽”示例,
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
的值(几乎总是{{1 }}},然后返回包装的结果a
。实际上,此功能几乎总是foldMap
。b'
是未包装的最终结果。展开由a'
本身处理。在您的情况下,可能是这样的:
ala
“ ala”功能可以通过ala Sum foldMap [1,2::Integer]
以外的其他方式来实现,例如using generics以处理展开,甚至是lenses。
答案 1 :(得分:6)
是的,有!这是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
答案 2 :(得分:6)
coerce
对于这种事情可能非常有用。您可以使用它在具有相同表示形式的不同类型之间进行转换(例如在类型和新类型包装之间进行转换,反之亦然)。例如:
λ coerce (3 :: Int) :: Sum Int
Sum {getSum = 3}
it :: Sum Int
λ coerce (3 :: Sum Int) :: Int
3
it :: Int
它的开发是为了解决例如无需花费任何费用的问题。通过应用Int
将Sum Int
转换为Sum
,但是例如通过应用{将[Int]
转换为[Sum Int]
不一定是免费的{1}}。编译器也许可以优化从map Sum
遍历列表主干的过程,也可以不优化,但是我们知道内存中的相同结构可以充当map
或{{1} },因为列表结构不依赖于元素的任何属性,并且这两种情况之间的元素类型具有相同的表示形式。 [Int]
(加上role system)使我们可以利用这一事实在两者之间进行转换,以确保不执行任何运行时工作,但仍让编译器检查是否可以安全进行这样做:
[Sum Int]
起初对我一点都不明显的是coerce
不仅限于强制“结构”!因为这样做是允许我们在表示形式相同时替换类型(包括复合类型的一部分),所以它也可以强制执行 code :
λ coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum {getSum = 1},Sum {getSum = 2},Sum {getSum = 3}]
it :: [Sum Int]
(在上面的示例中,我必须定义coerce
的单型版本,因为λ 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
;相反,我可以在+
的参数上使用内联类型签名,但是看起来不太整洁。通常在实际使用中,上下文足以确定“源”和“目标”类型的Sum Int -> Sum Int -> Sum Int
我曾经写过一个库,它通过newtypes提供了几种不同的类型化参数类型的方法,并且每种方案都提供了相似的API。实现API的模块充满了类型签名和coerce
样式定义。除了声明自己想要的类型外,我几乎没有做任何其他工作,真是太好了。
您的示例(在coerce
上使用foo' = coerce foo
来实现加法,而不必显式地来回转换)看起来像:
mappend