Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 在类型类中使用类型构造函数有什么好处吗?_Haskell_Typeclass - Fatal编程技术网

Haskell 在类型类中使用类型构造函数有什么好处吗?

Haskell 在类型类中使用类型构造函数有什么好处吗?,haskell,typeclass,Haskell,Typeclass,以类函子为例: class Functor a instance Functor Maybe 这里的可能是一个类型构造函数 但我们可以通过其他两种方式做到这一点: 首先,使用多参数类型类: class MultiFunctor a e instance MultiFunctor (Maybe a) a 其次,使用类型族: class MonoFunctor a instance MonoFunctor (Maybe a) type family Element type instance

以类
函子
为例:

class Functor a
instance Functor Maybe
这里的
可能是一个类型构造函数

但我们可以通过其他两种方式做到这一点:

首先,使用多参数类型类:

class MultiFunctor a e
instance MultiFunctor (Maybe a) a
其次,使用类型族:

class MonoFunctor a
instance MonoFunctor (Maybe a)

type family Element
type instance Element (Maybe a) a
后两种方法有一个明显的优点,即它允许我们这样做:

instance Text Char
instance MultiFunctor (Maybe Int) (Maybe Int) Int Int where
    mfmap _ Nothing = Nothing
    mfmap f (Just a) = Just (if f a == a then a else f a * 2)
或:

所以我们可以使用单态容器

第二个优点是,我们可以创建没有类型参数作为最终参数的类型实例。假设我们制作了一个
样式类型,但将类型放错了方向:

data Silly t errorT = Silly t errorT

instance Functor Silly -- oh no we can't do this without a newtype wrapper
鉴于

instance MultiFunctor (Silly t errorT) t
工作得很好

instance MonoFunctor (Silly t errorT)
type instance Element (Silly t errorT) t
也很好


考虑到在类型类定义中只使用完整类型(而不是类型签名)的这些灵活性优势,假设您使用的是GHC并且不介意使用扩展,那么是否有理由使用原始样式定义?也就是说,在类型类中放置一个类型构造函数,而不仅仅是一个完整的类型,你能做什么特别的事情吗?这是多参数类型类或类型族所不能做的?

首先,传统的函子类要简单得多。这本身就是喜欢它的正当理由,即使这是哈斯克尔。它还更好地代表了函子应该是什么的数学思想:从对象到对象的映射(
f:*->*
),具有额外的属性(
->约束
),即每个(
forall(a:*)(b:*)
)态射(
a->b
)被提升到映射到的相应对象上的态射(
->fa->fb
)。
在类的
*->*->Constraint
版本或其TypeFamilies等价物中,这些都看不清楚

从更实际的角度来看,是的,还有一些事情只能通过
(*->*)->约束来完成

特别是,此约束立即向您保证的是,所有Haskell类型都是可以放入函子的有效对象,而对于
MultiFunctor
,您需要逐个检查每个可能包含的类型。有时这是不可能的(或者是吗?),比如当您映射无限多个类型时:

data Tough f a = Doable (f a)
               | Tough (f (Tough f (a, a)))

instance (Applicative f) = Semigroup (Tough f a) where
  Doable x <> Doable y = Tough . Doable $ (,)<$>x<*>y
  Tough xs <> Tough ys = Tough $ xs <> ys
  -- The following actually violates the semigroup associativity law. Hardly matters here I suppose...
  xs <> Doable y = xs <> Tough (Doable $ fmap twice y)
  Doable x <> ys = Tough (Doable $ fmap twice x) <> ys

twice x = (x,x)
data-Tough f a=Doable(f a)
|坚韧的(f(坚韧的f(a,a)))
实例(应用f)=半群(坚韧f a),其中
可行的x可行的y=艰难的。可行的$(,)xy
坚韧的xs坚韧的ys=坚韧的$xs ys
--下面的事实违反了半群结合定律。我想这里没什么关系。。。
xs可行y=xs艰难(可行$fmap两次y)
可行x ys=艰难(可行$fmap两次x)ys
两次x=(x,x)
请注意,这不仅在
a
类型上使用了
f
Applicative
实例,而且在其任意元组上也使用了该实例。我看不出如何使用基于
MultiParamTypeClasses
TypeFamilies
的应用类来表达这一点。(如果你让
变得强硬
成为一个合适的GADT,这可能是可能的,但如果没有这一点……可能不会。)


顺便说一句,这个例子可能并不像看上去那么无用——它基本上是在一元状态下表示长度为2n的只读向量。

扩展的变体确实更灵活。例如,Oleg Kiselyov使用它来定义。大致上,您可以

 class MN2 m a where
     ret2  :: a -> m a

 class (MN2 m a, MN2 m b) => MN3 m a b where
     bind2 :: m a -> (a -> m b) -> m b
允许通过
a
b
参数化monad实例。这很有用,因为您可以将这些类型限制为其他类的成员:

import Data.Set as Set

instance MN2 Set.Set a where
    -- does not require Ord
    return = Set.singleton 

instance Prelude.Ord b => MN3 SMPlus a b where
    -- Set.union requires Ord
    m >>= f = Set.fold (Set.union . f) Set.empty m
请注意,由于
Ord
约束,我们无法使用不受限制的Monad定义
Monad Set.Set
。事实上,Monad类要求Monad在所有类型上都可用


另请参见:.

您的建议忽略了有关现有的
Functor
定义的一些相当重要的细节,因为您没有完成编写类的成员函数会发生什么的细节

class MultiFunctor a e where
    mfmap :: (e -> ??) -> a -> ????

instance MultiFunctor (Maybe a) a where
    mfmap = ???????
目前,
fmap
的一个重要特性是它的第一个参数可以改变类型。
fmap show::(Functor f,show a)=>f a->f String
。你不能把它扔掉,否则你会失去
fmap
的大部分值。所以实际上,
MultiFunctor
需要看起来更像

class MultiFunctor s t a b | s -> a, t -> b, s b -> t, t a -> s where
    mfmap :: (a -> b) -> s -> t

instance (a ~ c, b ~ d) => MultiFunctor (Maybe a) (Maybe b) c d where
    mfmap _ Nothing = Nothing
    mfmap f (Just a) = Just (f a)
请注意,要使推断至少接近可能,这是多么复杂。所有函数依赖项都已就位,允许在不注释所有类型的情况下选择实例。(我可能遗漏了其中的几个可能的函数依赖项!)实例本身增加了一些疯狂的类型相等约束,使实例选择更加可靠。最糟糕的是-这仍然比
fmap
具有更糟糕的推理属性

假设我的前一个实例不存在,我可以编写这样的实例:

instance Text Char
instance MultiFunctor (Maybe Int) (Maybe Int) Int Int where
    mfmap _ Nothing = Nothing
    mfmap f (Just a) = Just (if f a == a then a else f a * 2)
当然,这是不正确的-但是它以一种以前不可能的新方式被打破了。
函子
定义的一个非常重要的部分是
fmap
中的
a
b
类型不会出现在实例定义的任何地方。只需查看类就足以告诉程序员
fmap
的行为不能依赖于
a
b
的类型。您可以免费获得该保证。您不需要相信实例编写正确

因为
fmap
免费为您提供了这种保证,所以在定义实例时,您甚至不需要同时检查
Functor
定律。检查定律
fmap id x==x
就足够了。当第一定律被证明时,第二定律免费出现。但是由于我刚才提供的
mfmap
定律已经失效,
mfmap>id x==x
是真的,即使第二定律不是真的

作为
mfmap
的实现者,您有更多的工作要做