Ign*_*rov 8 haskell coerce newtype
呈现本身往往越式安全正在通过引入的模式newtype是项目的值(或多个值)的newtype包装,做一些操作,然后收回投影.一个无处不在的例子是Sum和Product幺半群:
? x + y = getSum $ Sum x `mappend` Sum y
? 1 + 2
3
Run Code Online (Sandbox Code Playgroud)
我想象可以为每个函数自动推出一系列函数,如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’
...
Run Code Online (Sandbox Code Playgroud)
但是我仍然不欢迎coerce,因为一旦达到它的习惯性,剥去一个安全标签并射击某人就太容易了.想象一下,在加密应用程序中,有两个值:x :: Prime Int和x' :: Sum Int.我宁愿打字getPrime,getSum每次使用它们,而不是coerce一切,有一天会发生灾难性的错误.
对于某些操作coerce的简写而言,有用性并没有带来太大的影响.我的帖子的主要例子,我在此重复:
? getSum $ Sum 1 `mappend` Sum 2
3
Run Code Online (Sandbox Code Playgroud)
- 变成了这个尖刺怪物的线条:
? coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
3
Run Code Online (Sandbox Code Playgroud)
- 这几乎没有任何好处.
通过将summands放入一个列表并使用此处ala提供的函数,可以更好地处理您的"尖刺怪物"示例,该函数具有以下类型:
ala :: (Coercible a b, Coercible a' b')
=> (a -> b)
-> ((a -> b) -> c -> b')
-> c
-> a'
Run Code Online (Sandbox Code Playgroud)
哪里
a是未打开的基本类型.b是包装的新类型a.a -> b 是newtype构造函数.((a -> b) -> c -> b')是一个函数,知道如何包装基类型的值a,知道如何处理类型的值c(几乎总是as 的容器)并返回包装结果b'.实际上,这个功能几乎总是如此foldMap.a'在展开最后的结果.展开是由ala它自己处理的.在你的情况下,它将是这样的:
ala Sum foldMap [1,2::Integer]
Run Code Online (Sandbox Code Playgroud)
"ala"功能可以通过除了coerce例如使用泛型来处理展开或甚至透镜之外的其他方式来实现.
是的,有!这是包的coerce功能base.它允许自动转换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"
Run Code Online (Sandbox Code Playgroud)
你可以在这里看到整个模块:
也可以为二元运算符实现自定义函数:
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
Run Code Online (Sandbox Code Playgroud)
coerce来自Data.Coerce对于这类事情来说非常棒.您可以使用它在具有相同表示形式的不同类型之间进行转换(例如在类型和新类型包装器之间转换,反之亦然).例如:
? coerce (3 :: Int) :: Sum Int
Sum {getSum = 3}
it :: Sum Int
? coerce (3 :: Sum Int) :: Int
3
it :: Int
Run Code Online (Sandbox Code Playgroud)
它的开发来解决,这是经济自由转换,例如一个问题Int变成一个Sum Int应用Sum,但它不一定是成本自由例如转换[Int]为[Sum Int]应用map Sum.编译器可能能够优化map或不执行列表脊椎的遍历,但我们知道内存中的相同结构可以充当a [Int]或a [Sum Int],因为列表结构不依赖于任何属性.元素和元素类型在这两种情况之间具有相同的表示.coerce(加上角色系统)允许我们利用这个事实在两者之间进行转换,保证不做任何运行时工作,但仍然让编译器检查它是否安全:
? coerce [1, 2, 3 :: Int] :: [Sum Int]
[Sum {getSum = 1},Sum {getSum = 2},Sum {getSum = 3}]
it :: [Sum Int]
Run Code Online (Sandbox Code Playgroud)
对我来说一点都不明显的事情是,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
Run Code Online (Sandbox Code Playgroud)
(在上面的例子中,我必须定义一个monotype版本,+因为它coerce是如此通用的类型系统否则不知道+我要求强制使用哪个版本Sum Int -> Sum Int -> Sum Int;我可以在参数上给出一个内联类型签名coerce,但这看起来不那么整洁.通常在实际使用中,上下文足以确定"源"和"目标"类型的coerce)
我曾经写过一个库,通过newtypes提供了一些不同类型的参数化方法,并为每个方案提供了类似的API.实现API的模块充满了类型签名和foo' = coerce foo样式定义; 除了陈述我想要的类型之外,我几乎没有做任何工作,这感觉非常好.
您的示例(使用mappendon Sum来实现添加,而不必显式地来回转换)可能如下所示:
? let (+) :: Int -> Int -> Int
| (+) = coerce (mappend @ (Sum Int))
|
(+) :: Int -> Int -> Int
? 3 + 8
11
it :: Int
Run Code Online (Sandbox Code Playgroud)