对于uni任务,我们得到了一行Haskell代码,其中显示:
newtype TC a = TC ([Id] -> Either TypeError ([Id], a))首先,TypeError是需要我们在赋值中实现的东西,所以我不能在这里发布数据声明,但我的问题是。我怎么读上面的代码?a在newtype TC之后是什么?我也不明白TC是如何被重用到等号右边的。
我认为a是一个类型变量,因为newtype的工作方式类似于数据。我不知道知道这会对我的理解有什么帮助。
发布于 2022-11-16 09:57:26
,
a在newtype TC之后是什么?
新类型声明中的a
newtype TC a = ...表达式与函数声明中的x非常类似。
f x = ...a是一个类型参数。因此,您可以使用TC,例如,TC Int或TC Bool,类似于如何使用f (如f 1或f "bla bla" )(取决于其类型)。
这种情况下的TC Int相当于以下备选方案:
newtype TCInt = TCInt ([Id] -> Either TypeError ([Id], Int))我也不明白
TC是如何被重用到等号右边的。
在Haskell,这是一个令人困惑的怪癖。实际上,TC并没有被重用,而是声明了两个单独的实体,它们都被称为TC。你也可以用不同的方式称呼它们:
newtype TC_T a = TC_V ([Id] -> Either TypeError ([Id], a))TC_T是一个类型构造函数。这将出现在类型签名中,即TC_T Bool.TC_V或TC_T Int是一个值构造函数。当生成TC_T Int或TC_T Int类型的值时,可以使用此方法。因此,例如,您可以编写以下内容:
tci :: TC_T Int
tci = TC_V (\ids -> Right (ids, 37))
tcb :: TC_T Bool
tcb = TC_V (\ids -> Right (reverse ids, False))您的原始版本如下所示:
tci :: TC Int
tci = TC (\ids -> Right (ids, 37))
tcb :: TC Bool
tcb = TC (\ids -> Right (reverse ids, False))...but,这里仍然是两个分开的东西,都叫TV。Haskell中的大多数新类型调用类型和值构造函数相同,但通常只有类型构造函数从模块导出,值构造函数作为实现细节保留。
对于data和newtype,大多数情况都是一样的。你还不如
data TC a = TC ([Id] -> Either TypeError ([Id], a))..。新类型版本的唯一区别是一个微妙的间接:如果它是data,那么编译器就插入一个间接性,它允许更多的懒散,但在这种情况下,这样做几乎没有任何意义。
在实践中,通常只在需要多个构造函数和/或具有多个字段的构造函数时才使用data,而newtype不支持这些构造函数。
发布于 2022-11-16 22:23:45
newtype相对于数据的好处是,您可以通过它的基础类型进行派生:
{-# Language DerivingVia #-}
{-# Language StandaloneKindSignatures #-}
import Control.Applicative (Alternative)
import Control.Monad (MonadPlus)
import Control.Monad.Except (ExceptT(..), Except)
import Control.Monad.Fix (MonadFix)
import Control.Monad.State (StateT(..), MonadState)
import Data.Functor.Identity (Identity(..))
import Data.Kind (Type)
type TC :: Type -> Type
newtype TC a = MkTC ([Id] -> Either TypeError (a, [Id]))
deriving
( Functor, Applicative, Alterantive
, Monad, MonadPlus, MonadState [Id], MonadFix
)
via StateT [Id] (Except TypeError)
-- Alternative and MonadPlus rely on these
instance Semigroup TypeError ..
instance Monoid TypeError ..我不得不交换元组,但这是因为有三个newtype_s:TC a、StateT s m a和ExceptT。编译器为每个_newtype生成一个实例,见证它与底层类型具有相同的运行时表示。
这被称为代表性相等,并由Coercible见证。
-- instance Coercible (StateT s m a) (s -> m (a, s))
type StateT :: Type -> (Type -> Type) -> (Type -> Type)
newtype StateT s m a = MkStateT (s -> m (a, s))
-- instance Coercible (ExceptT e m a) (m (Either e a))
type ExceptT :: Type -> (Type -> Type) -> (Type -> Type)
newtype ExceptT e m a = MkExceptT (m (Either e a))这意味着TC a对StateT [Id] (Except TypeError) a是强制的,因此我们可以使用coerce-based派生策略在它们之间携带实例(比如GeneralizedNewtypeDeriving或DerivingVia)。
TC a
={Coercible}
[Id] -> Either TypeError (a, [Id])
={Coercible}
[Id] -> ExceptT TypeError Identity (a, [Id])
={Except e = ExceptT e Identity}
[Id] -> Except TypeError (a, [Id])
={Coercible}
StateT [Id] (Except TypeError) aGeneralizedNewtypeDeriving只在newtype_s上工作,但这并不意味着DerivingVia只在_newtype_s上工作。您可以为_data开发许多实例,例如,通过TC (Ap TC a)上的Applicative提升方法。
使它成为一个新类型确实可以简化事情,Applicative可以通过状态转换器派生,所以我们不需要手工编写任何实例。
data TC a = MkTC ..
..
deriving (Semigroup, Monoid, Num, Bounded)
via Ap TC aTC名称在代码中有两个用途(‘双关’)。我把它们分成两个不同的名字,TC和变压器。
左侧是类型构造函数:
>> :k TC
TC :: Type -> Type
>> :k StateT
StateT :: Type -> (Type -> Type) -> (Type -> Type)
>> :k ExceptT
ExceptT :: Type -> (Type -> Type) -> (Type -> Type)右边是值构造函数,我添加了一个消除歧义的Mk:
>> :t MkTC
MkTC :: ([Id] -> Either TypeError (a, [Id])) -> TC a
>> :t MkStateT
MkStateT :: (s -> m (a, s)) -> StateT s m a
>> :t MkExceptT
MkExceptT :: m (Either e a) -> ExceptT e m aa :: Type是类型构造函数的参数:
TC :: Type -> Type
TC a :: Type如果没有类型参数,所有这些实例都不会进行类型检查,因为所有这些实例都需要一个“一元类型构造函数”(Type -> Type)作为参数。也就是说,由类型参数化的类型:
Functor :: (Type -> Type) -> Constraint
Applicative :: (Type -> Type) -> Constraint
Alternative :: (Type -> Type) -> Constraint
Monad :: (Type -> Type) -> Constraint
MonadPlus :: (Type -> Type) -> Constraint
MonadState [Id] :: (Type -> Type) -> Constraint
MonadFix :: (Type -> Type) -> Constrainthttps://stackoverflow.com/questions/74458089
复制相似问题