【问题标题】:instance of "Type constructed with type argument" need not be constructed with data of that type, in Haskell在 Haskell 中,“使用类型参数构造的类型”的实例不需要使用该类型的数据构造
【发布时间】:2014-01-06 02:39:50
【问题描述】:

在 Haskell 中,类型构造函数当然可以接受类型参数。

函数a -> b,当被视为“具有有趣构造函数名称的类型”时,其类型为(->) a b。这使它成为一个类型构造函数(->),有两个参数ab。这在“阅读器”模式中经常遇到,例如在其 FunctorApplicative 实例中:

instance Functor ((->) a) where
  fmap = (.)


instance Applicative ((->) a) where
  pure = const
  (<*>) f g x = f x (g x)

当我第一次尝试了解此实例的用途时,如 fmap (+1) (*2) 3 (=== (+1) . (*2) $ 3 === 3*2+1 === 7)

我的反应是“好的,(+1) 的类型为 Int -&gt; Int,即 (-&gt;) Int Int,因此匹配 Functor....但 在哪里Int?我通过调用Just 1 来创建Maybe Int,但我从来没有通过对Int 应用任何东西来创建(-&gt;) Int Int。事实上,我销毁 ((-&gt;) Int Int) 通过应用它到Int!(是的,有Nothing,但那似乎……退化了。)”

这一切都有效(当然),只要我记得仅仅因为一个类型是从构造函数+参数构建的,这并不意味着它的值是从相应类型的构造函数+参数构建的。而一些最有趣、最强大(也很难理解)的类型构造函数就是这样的((-&gt;)LensArrow 等)

(好吧,真的是Num a =&gt; a,不是Int,但我们忽略它,不相关)

这个概念有名字吗? 什么是思考类型构造函数的适当心智模型,而不依赖于误导性和剥夺权力的拐杖解释“Foo a 是一个结构Foo 包含 a) 类型的值?

【问题讨论】:

    标签: haskell types


    【解决方案1】:

    这个概念被称为逆变函子,在 Haskell 中是 Contravariant 类型。

    class Contravariant f where
      contramap :: (b -> a) -> f a -> f b
    
    -- compare
    class Functor f where
      fmap :: (a -> b) -> f a -> f b
    

    更一般地说,我们可以将类型中的类型变量视为具有逆变或协变性质(最简单的)。例如,默认情况下我们有

    newtype Reader t a = Reader (t -> a)
    
    instance Functor (Reader t) where
      fmap ab (Reader ta) = Reader (ab . ta)
    

    这表明Reader的第二个类型参数是协变的,而如果我们颠倒顺序

    newtype RevReader a t = RevReader (t -> a)
    
    instance Contravariant (RevReader a) where
      contramap st (RevReader ta) = RevReader (ta . st)
    

    Contravariant 类型的一个有用直觉是它们能够使用零、一个或多个逆变参数值,而不是像我们经常想到的那样包含零、一个或多个协变参数值考虑Functors。

    结合这两个概念的是Profunctor

    class Profunctor p where
      dimap :: (a -> b) -> (c -> d) -> p b c -> p a d
    

    正如我们所注意到的,它要求p 类似于* -&gt; * -&gt; *,其中第一个类型参数是逆变的,第二个是协变的。这个类很好地描述了(-&gt;) 类型构造函数

    instance Profuntor (->) where
      dimap f g h = g . h . f
    

    同样,如果我们将逆变类型参数视为被消耗,而将协变类型参数视为产生,这完全符合围绕 (-&gt;) 类型的典型直觉。

    逆变参数类型的更多示例包括Relation

    newtype Relation t = Relation (t -> t -> Bool)
    
    instance Contravariant Relation where
      contramap g (Relation pred) = Relation $ \a b -> pred (g a) (g b)
    

    Fold 表示左折叠作为数据类型

    newtype Fold a b = Fold b (a -> Fold a b)
    
    instance Profunctor Fold where
      dimap f g (Fold b go) = Fold (g b) (go . f)
    
    sumF :: Num a => Fold a a
    sumF = go 0 where
      go n = Fold n (\i -> go (n + i))
    

    对于Fold a b,我们看到它消耗任意数量的a 类型来生成一个b 类型。

    通常我们发现,虽然我们经常有协变和“容器”(严格肯定的)类型,其中某些类型的值 c a 是从类型为 a -&gt; c a 的构造函数和一些填充值 @ 987654345@,一般来说不成立。特别是我们有像这样的协变类型,但也有逆变类型,它们通常是以某种方式消耗其参数化类型变量的值的过程,或者甚至更奇特的类型,例如完全忽略其类型变量的幻像类型

    newtype Proxy a = Proxy -- need no `a`, produce no `a`
    
    -- we have both this instance
    instance Functor Proxy where
      fmap _ Proxy = Proxy
    
    -- and this one, though both instances ignore the passed function
    instance Contravariant Proxy where
      contramap _ Proxy = Proxy
    

    and...“没什么特别的”类型变量,不能具有任何性质,通常是因为它们被用作协变 逆变类型。

    data Endo a = Endo (a -> a)
    
    -- no instance Functor Endo or Contravariant Endo, it needs to treat
    -- the input `a` differently from the output `a` such as in 
    --
    -- instance Profunctor (->) where
    

    最后,接受多个参数的类型构造函数可能对每个参数有不同的性质。不过,在 Haskell 中,最终类型参数通常会被特殊处理。

    【讨论】:

    • 谢谢!协变与逆变,容器与幻象与其他“自然”是我想知道的。如何对类型构造函数进行分类? (有些情况,比如没有 newtype 的 (->),实际上并没有构造函数)“Container”是最简单的情况。 “幻影”就像一个退化的容器,它折叠/忽略包含的值。其他性质是..“功能性质”,可以是相反的或共同的或不变的。这是否涵盖了所有可能性?是否存在 Proxy 为 0 级、Maybe sense of 和 List 为 1 级、功能为 2 级等的层次结构?
    • 请注意,类型参数可能出现在类型定义主体中的 co 和 contra-variant 位置,因此您根本不必对它们进行唯一分类。
    • 查看这里了解更多关于Contravariant的详细信息:ocharles.org.uk/blog/guest-posts/…
    • 有趣的是,你可以证明一个逆变函子不能包含类型参数的任何值,因为如果f是逆变的,就没有extract :: f a -&gt; a,因为否则你可以写extract . contramap absurd :: f a -&gt; Void,其中Void是没有值的类型,absurd :: Void -&gt; a
    猜你喜欢
    • 2017-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多