Haskell:为什么我不能在另一个函数中使用id,其中id的域显然是函数中所需类型的超集?

Mik*_*ynn 3 haskell

这是一个简单的问题.我是Haskell的新手,使用JuicyPixels包玩一些图像.我已将图像加载到DynamicImageGHCI中的对象中decodePng.DynamicImage类型只是包含几种不同像素类型的图像的包装:

data DynamicImage =
       -- | A greyscale image.
       ImageY8    (Image Pixel8)
       -- | A greyscale image with 16bit components
     | ImageY16   (Image Pixel16)
       -- | A greyscale HDR image
     | ImageYF    (Image PixelF)
       -- | An image in greyscale with an alpha channel.
     | ImageYA8   (Image PixelYA8)
      -- | An image in greyscale with alpha channel on 16 bits.
     | ImageYA16  (Image PixelYA16)
     ...
Run Code Online (Sandbox Code Playgroud)

我想要做的就是使用底层数据dynamicMap并查看我正在加载的像素类型.dynamicMap的类型签名使用Rank2Types:

dynamicMap :: (forall pixel . (Pixel pixel) => Image pixel -> a)
           -> DynamicImage -> a
dynamicMap f (ImageY8    i) = f i
dynamicMap f (ImageY16   i) = f i
dynamicMap f (ImageYF    i) = f i
dynamicMap f (ImageYA8   i) = f i
...
Run Code Online (Sandbox Code Playgroud)

它需要一个从图像到任何东西的函数,一个dynamicImage,以及应用于底层数据的函数.

为什么不呢

getImage :: Pixel a => DynamicImage -> Image a
getImage img = dynamicMap id img
Run Code Online (Sandbox Code Playgroud)

类型检查?该错误似乎是因为该id函数在其输入中过于包容.

Couldn't match type `pixel' with `a'
      `pixel' is a rigid type variable bound by
              a type expected by the context:
                Pixel pixel => Image pixel -> Image a
              at <path>:24:16
      `a' is a rigid type variable bound by
          the type signature for
            getImage :: Pixel a => DynamicImage -> Image a
          at <path>:23:13
    Expected type: Image pixel -> Image a
      Actual type: Image a -> Image a
    Relevant bindings include
      getImage :: DynamicImage -> Image a
        (bound at <path>:24:1)
    In the first argument of `dynamicMap', namely `id'
    In the expression: dynamicMap id img
Run Code Online (Sandbox Code Playgroud)

chi*_*chi 6

假设我们拥有img :: Image Pixel8并运行

getImage (ImageY8 img) :: Image Pixel16
Run Code Online (Sandbox Code Playgroud)

如何通过不操纵位图的代码进行神奇的转换?肯定有些事情是错的.实际上,如果类型系统允许这样做,它将允许来自两种不同类型的危险演员,可能导致崩溃.在实践中,类型系统是健全的并且正确地拒绝这一点.

关键点在于:

dynamicMap :: (forall pixel . (Pixel pixel) => Image pixel -> a)
           -> DynamicImage -> a
Run Code Online (Sandbox Code Playgroud)

此类型是调用者和被调用者之间的契约.来电者可以选择a.然后调用者必须传递一个类型的函数参数forall pixel . (Pixel pixel) => Image pixel -> a.这必须适用于所有pixel类型.换句话说,callee(dynamicMap)可以选择pixel.无法保证被叫方会选择pixel这样来满足Image pixel ~ a.实际上,它不会在发布的代码中.因此,编译器假设Image pixel并且a可能不同.但id强迫它们是相同的:调用者施加限制,限制被调用者的选择.

因此类型错误.

一个更简单的案例:

foo :: (forall a. a -> Int) -> Int
foo f = f "hello" + f (42 :: Int) + f True

bar :: Int
bar = foo id
Run Code Online (Sandbox Code Playgroud)

这里bar传递一个函数id :: Int -> Int,它不像通常那样forall a. a->Int- 后者承诺将任何东西转换为Int被调用者的选择.因此,报告了类型错误.

从技术上讲,id有类型forall b. b->b,我们的目标forall a. a->Int.没有办法替换类型b(可能涉及类型变量的类型a),因此b->b变为a->Int.