是否有类似`fromNewtype'这样的操作的简写形式。 F 。 toNewtype`?

时间:2018-08-19 06:47:07

标签: haskell newtype coerce

通过newtype引入类型安全性越强的一种表示自身的模式越多,它会将一个值(或多个值)投射到一个newtype包装器中,做一些操作,然后缩回投影。一个普遍存在的例子是SumProduct id半群的例子:

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

我想象withSumwithSum2等功能的集合可能会针对每个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 Intx' :: Sum Int。我宁愿 每次使用getPrimegetSum时,输入coerce并有一天 犯了灾难性的错误。

  • 有用性 coerce对于速记的作用不大 某些操作。我的帖子的主要示例,在此重复:

    λ getSum $ Sum 1 `mappend` Sum 2
    3
    

    —变成了与这个尖刺怪兽相似的东西:

    λ coerce $ mappend @(Sum Integer) (coerce 1) (coerce 2) :: Integer
    3
    

    —几乎没有任何好处。

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)

Data.Coerce中的

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

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

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

它的开发是为了解决例如无需花费任何费用的问题。通过应用IntSum 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