所以我们有Hask类别,其中:
同样对Functor
我们有:
fmap
用于将状态从一个类别映射到另一个类别。现在,当我们编写程序时,我们基本上会转换值(而不是类型),而Hask的类别似乎根本不会讨论值。我试图在整个等式中拟合值,并得出以下观察结果:
Int -> Int
现在我的问题是 - 在Hask类别(或一般类别理论)中,值是否有意义?如果是,那么任何关于它的参考或如果没有,那么任何原因。
我希望这个问题有道理:))
答案 0 :(得分:24)
(除非我mark it as code
,否则我会使用数学/类别理论而不是编程的含义。)
类别理论的一个重要思想是将大型复杂事物视为一个重点,因此,如果你想要形成所有整数的集合/组/环/类/类别,那么它们就被认为是一个单点。想到Hask类别。
类似地,你可以对整数有一个非常复杂的函数,但它只被认为是态射集合(集合/类)的单个元素(点/箭头)。
你在类别理论中做的第一件事是忽略细节。因此,Hask类别并不关心Int可以被视为一个类别 - 它处于不同的级别。 Int只是Hask中的一个点(对象)。
每个monoid都是一个具有一个对象的类别。让我们使用它。
对此有不止一个答案(因为整数是加法中的幺半群和乘法下的幺半群)。让我们做补充:
您可以将整数视为具有单个对象的类别,而态射是诸如(+ 1),(+ 2),(减去4)之类的函数。
你必须坚持认为我将整数7视为数字7,但使用表示法(+7)使其看起来像是一个类别。类别理论的法则故意不说你的态射必须是函数,但是如果它具有一组函数的结构,它就更清楚了一个类别包含身份和封闭的组成。
任何monoid都会以与我们对整数完成相同的方式创建单个对象类别。
作为操作f
下的类别的整数中的函数+
,以及具有形成类别的操作£
的其他类型的函数f(x+y) = f(x) £ f(y)
只能是{仿函数} { {1}}。 (这称为monoid同态)。大多数函数都不是态射。
String
是++
下的幺半群,所以他们是一个类别。
len :: String -> Int
len = length
len
是从String
到Int
的幺半形态,因为len (xs ++ ys) = len xs + len ys
,所以如果您正在考虑(String
,{{1} }}和(++
,Int
)作为类别,+
是一个仿函数。
(len
,Bool
)是一个幺半群,以||
为标识,因此它是一个对象类别。功能
False
由于quiteLong :: String -> Bool
quiteLong xs = length xs > 10
为quiteLong "Hello "
且False
也为quiteLong "there!"
,但False
为quiteLong ("Hello " ++ "there!")
,而且{{}不是{+ 1}} {1}}不是True
。
因为False || False
不是一个态射,它也不是一个仿函数。
我的观点是一些 Haskell类型可以被视为类别,但并非它们之间的所有函数都是morhpisms。
我们不会同时考虑不同级别的类别(除非您为了某些奇怪的目的而使用这两个类别),并且这些级别之间故意没有理论上的相互作用,因为故意没有关于物体和态射的细节。
这部分是因为类别理论在数学中起飞,提供了一种语言来描述伽罗瓦理论在有限群/子群与字段/字段扩展之间的可爱交互,两种显然完全不同的结构结果是密切相关的。后来,同源/同伦理论在拓扑空间和群体之间建立了仿函数,这些仿函数既有趣又有用,但主要的一点是物体和态射在两个类别的仿函数中被允许彼此非常不同。 。
(通常类别理论以Hask到Hask的仿函数形式进入Haskell,因此在函数式编程的实践中,这两个类别是相同的。)
- 每个类型本身就是一个类别。例如:Int是所有整数的类别。
如果你以特殊的方式思考它们。有关详细信息,请参阅PhilipJF的答案。
- 从值到相同类型的另一个值的函数是类别的态射。例如:
True
我认为你把这两个级别搞混了。函数可以是Hask中的态射,但并非所有函数quiteLong
都是加法结构下的函数,例如Int -> Int
不是Int和Int之间的函数,所以它不是一个类别从(Int -> Int
,f x = 2 * x + 10
)到(Int
,+
)的态射(另一种说算函数的方式)但它是Hask类别中的态射Int
- 从一个值到另一个不同类型的值的函数是用于将一种类型的值映射到另一种类型的函数。
不,并非所有功能都是仿函数,例如+
isn&#t; t。
在Hask类别(或一般类别理论)中,值是否有意义?
类别在类别理论中没有价值,它们只有对象和态射,它们被视为顶点和有向边。对象不必具有值,并且值不属于类别理论。
答案 1 :(得分:12)
当我评论Andrew的答案(其他方面非常好)时,可以将类型中的值视为该类型的对象作为类别,并将函数视为仿函数。为了完整起见,有两种方法:
数学中最常用的工具之一是“setoid” - 也就是说,与它有等价关系的集合。我们可以通过“groupoid”的概念明确地想到这一点。 groupoid是一个类别,其中每个态射都有一个逆f . (inv f) = id
和(inv f) . f = id
。
为什么这会捕捉到等价关系的概念?嗯,等价关系必须是反身的,但这只是它具有同一性箭头的明确主张,它必须是传递性的,但这只是组合,最后它需要是对称的(这就是我们添加反转的原因)。 / p>
因此,任何一组数学中平等的普通概念都会产生一种群体结构:即唯一的箭头就是身份箭头!这通常被称为“离散类别”。
向读者留下一个练习,表明所有函数都是离散类别之间的函子。
如果你认真对待这个想法,你会开始怀疑具有“平等”的类型,而不仅仅是身份。这将允许我们编码“商类型”。更重要的是,群体结构有一些更多的公理(结合等),这些公理是关于“平等证明的平等”的主张,它引导了n-群体和更高范畴理论的道路。这很酷,虽然有用,你需要依赖类型和一些没有完全解决的位,当它最终进入编程语言时应该允许
data Rational where
Frac :: Integer -> Integer -> Rational
SameRationa :: (a*d) ~ (b*c) -> (Frac a b) ~ (Frac c d)
这样,每次你匹配图案时,你也必须匹配额外的相等公理,从而证明你的函数尊重Rational
上的等价关系
但不要担心这一点。带走只是“离散类别”解释是一个非常好的解释。
Haskell中的每个类型都有一个额外的值,即undefined
。这是怎么回事?好吧,我们可以在每种类型上定义一个与“定义”值如何相关的部分顺序,例如
forall a. undefined <= a
还有像
这样的东西forall a a' b b'. (a <= a') /\ (b <= b') -> ((a,b) <= (a',b'))
未定义的定义较少,因为它引用的是未终止的值(实际上,undefined
函数是通过在每个haskell中抛出异常来实现的,但我们假装它是undefined = undefined
。不能确定某些东西不会终止。如果给你一个undefined
所有你可以做的就是拭目以待。因此,它可以是任何东西。
部分订单以标准方式产生类别。
因此,每种类型都会产生一个类别,其中值是这样的对象。
为什么功能函子?好吧,由于停止问题,函数无法告诉它已经获得了undefined
。因此,它必须在遇到undefined
时回复它,或者无论给出什么,它都必须产生相同的答案。由你来表明它真的是一个仿函数。
答案 2 :(得分:9)
虽然这里有其他一些非常精彩的答案,但他们都会错过你的第一个问题。要清楚,值根本不存在,而在Hask类别中没有任何含义。这不是Hask所要讨论的内容。
上述说法或感觉似乎有点愚蠢,但我提出来是因为重要的是要注意类别理论只提供一个镜头来检查更复杂的交互和结构,这些交互和结构可用于复杂的事物。编程语言。期望所有这种结构都被类别的相当简单的概念所包含在内并不富有成效。 [1]
另一种说法是我们尝试分析一个复杂的系统,它有时会将视为一个类别以寻找有趣的模式。这个让我们介绍Hask的心态,检查它是否真的形成一个类别,注意Maybe
似乎表现得像一个Functor,然后使用所有这些机制来记下一致性条件。
fmap id = id
fmap f . fmap g = fmap (f . g)
无论我们是否引入Hask,这些规则都是有意义的,但是通过将它们视为简单结构的简单结果,我们可以在Haskell中发现它们理解它们的重要性。
作为一个技术说明,这个答案的全部内容都假设Hask实际上是&#34;柏拉图式的&#34; Hask,即我们可以根据需要忽略底部(undefined
和非终止)。如果没有这一点,几乎整个论点都会分崩离析。
让我们更仔细地研究这些法律,因为它们似乎几乎与我最初的陈述背道而驰 - 它们显然在价值水平上运作,但是价值观并不存在在Hask&#34;,对吧?
好吧,一个答案是仔细研究一下分类函子是什么。显然,它是两个类别(比如C和D)之间的映射,它将C的对象与D的对象和C的箭头对准D的箭头。值得注意的是,通常这些&#34;映射&#34; s不是分类箭头 - 它们只是形成类别之间的关系,并不一定与类别共享结构。
这很重要,因为即使考虑Haskell Functor
s,Hask中的endofunctors,我们也要小心。在Hask中,对象是Haskell 类型,箭头是这些类型之间的Haskell 函数。
让我们再看看Maybe
。如果它将成为Hask上的endofunctor,那么我们需要一种方法将Hask中的所有类型转换为Hask中的其他类型。这个映射不是一个Haskell函数,即使它可能感觉像一个:pure :: a -> Maybe a
没有资格,因为它在值级别运行。相反,我们的对象映射本身是Maybe
:对于任何类型a
,我们都可以形成Maybe a
类型。
这已经强调了在没有值的情况下在Hask中工作的价值 - 我们确实希望找出Functor
的概念,它不依赖于pure
。
我们通过检查Functor
endofunctor的箭头映射来开发剩余的Maybe
。在这里,我们需要一种方法将Hask的箭头映射到Hask的箭头。我们现在假设这是不一个Haskell函数 - 它不必如此强调它我们将以不同的方式编写它。如果f
是Haskell函数a -> b
,那么Maybe [f
]是其他一些Haskell函数Maybe a -> Maybe b
。
现在,很难不提前跳过并开始调用Maybe [f
]&#34; fmap f
&#34;,但我们之前可以做更多的工作做那个跳。也许[f
]需要具有一定的连贯性条件。特别是,对于Hask中的任何类型a
,我们都有一个id箭头。在我们的元语言中,我们可能将其称为id [a
],而我们碰巧知道它也是由Haskell名称id :: a -> a
引用的。总而言之,我们可以使用它们来说明endofunctor一致性条件:
对于Hask a
中的所有对象,我们有可能[id [a
]] = id [Maybe a
]。对于Hask f
和g
中的任意两个箭头,我们认为可能[f . g
] =也许[f
]。也许[g
]。
最后一步是注意到Maybe [_]碰巧可以实现为Haskell函数本身作为Hask对象forall a b . (a -> b) -> (Maybe a -> Maybe b)
的值。这给了我们Functor
。
虽然上述内容相当技术性和复杂性,但是将Hask和分类endofunctors的概念保持为直接并与Haskell实例化分离是一个重要的观点。特别是,我们可以发现所有这些结构,而不需要fmap
作为真正的Haskell函数存在。 Hask是一个类别,在价值层面根本没有引入任何东西。
这就是将Hask视为一个类别的真正跳动的核心所在。使用Functor
标识Hask上的endofunctors的符号需要更多的线条模糊。
此行模糊是合理的,因为Hask
具有指数。这是一种狡猾的方式,说明整个分类箭头和Hask中的特殊对象之间存在统一。
为了更明确,我们知道对于Hask的任何两个对象,比如a
和b
,我们可以讨论这两个对象之间的箭头,通常表示为Hask({{1 },a
)。这只是一个数学集合,但我们知道Hask中的另一种类型与Hask密切相关(b
,a
):b
!
所以这很奇怪。
我最初声明一般的Haskell值在Hask的分类表示中绝对没有表示。然后我继续演示我们可以通过使用仅其分类概念来完成Hask的很多操作,而不会将这些部分作为值实际粘贴在Haskell中。
但是现在我注意到像(a -> b)
这样的类型的值确实存在,因为金属语言集Hask(a -> b
,a
)中的所有箭头都存在。这真是一个诡计,正是这种金属语言模糊使得指数类别如此有趣。
b
。如果我们查看任何Hask对象()
,我们知道Hask中有一整套分类箭头(a
,()
)。此外,我们知道这些对应于a
类型的值。最后,既然我们知道给定任何函数() -> a
我们可以通过应用f :: () -> a
立即得到a
,可能想要说出Hask中的分类箭头(()
,{ {1}})完全类型为()
的Haskell 值。
这应该是完全令人困惑或令人难以置信的令人兴奋。
通过坚持我的初步陈述,我将在某种程度上哲学地结束这一点:Hask根本没有谈论Haskell的价值观。它实际上并不是一个纯粹的类别 - 类别很有意思,因为它们非常简单,因此不需要所有类型和价值的超分类概念a
包容等。
但我也许,很糟糕,表明即使作为一个严格的只是一个类别,Hask也有一些看起来非常非常类似于Haskell的所有值的东西:Hask的箭头({每个Hask对象a
都有{1}},typeOf
。
哲学上我们可能会争辩说,这些箭头并不是真正我们正在寻找的Haskell值 - 它们只是站立,分类的模拟。你可能会认为它们是不同的东西,但恰好恰好与Haskell值一一对应。
我实际上认为这是一个非常重要的想法。这两件事情是不同的,他们只是表现得相似。
非常相似。任何类别都可以让你组成箭头,所以我们假设我们在Hask(()
,a
)和Hask中的一些箭头(a
,{{1} })。如果我们将这些箭头与类别组合结合起来,我们会在Hask中得到一个箭头(a
,b
)。稍微改变一下我们可能会说我刚刚做的是找到()
类型的值,类型为a
的值,然后将它们组合起来以生成类型为{{{ 1}}。
换句话说,如果我们侧身看事物,我们可以看到分类箭头组合作为功能应用的一般形式。
这就是像Hask这样的类别如此有趣的原因。从广义上讲,这些类别被称为笛卡尔封闭类别或CCC。由于同时具有初始对象和指数(也需要产品),因此它们具有完全模拟类型化lambda演算的结构。
但他们仍然没有值。
[1]如果您在阅读其余答案之前阅读此内容,请继续阅读。事实证明,虽然期望发生这种情况是荒谬的,但实际上确实如此。如果您在阅读完整个答案之后阅读此,那么让我们反思一下CCC的酷炫程度。
答案 3 :(得分:6)
有几种方法可以根据类别进行设置。特别是编程语言,结果非常丰富。
如果我们选择Hask类别,我们只是设置一个抽象级别。谈论价值观不太舒服的水平。
但是,常量可以在Hask中建模为从终端对象()到相应类型的箭头。 然后,例如:
您可以查看:Barr,Wells - 计算类别理论,第2.2节。
答案 4 :(得分:4)
具有终端对象(或终端对象* s *)的任何类别都有所谓的global elements(或点或常量,也是{ {3}},例如,在Awoday的on Wikipedia类别理论中,可以找到更多内容,参见 2.3广义元素)我们可以称之为 values 这里的对象,将全球元素作为“价值”的自然和普遍的分类概念。
例如,Set
具有通常的元素(集合,Set
的对象)作为全局元素,这意味着任何集合A
的元素都可以被视为{{3} } {function {Set
{⋆} → A
的态射从book {⋆}
到此集合A
。对于包含A
的有限集|A| = n
,有n
个这样的态射,对于空集{}
,{⋆} → {}
中没有这样的态射Set
,以便{}
“没有元素”和|{}| = 0
,单独设置{⋆} ≊ {+}
唯一,以便|{⋆}| = |{+}| = 1
,等等。集合的元素或“值”实际上只是单个集合中的函数(1
,Set
中的终端对象),因为A ≊ Hom(1, A)
中存在同构Set
(是CCC
,因此Hom
在此处为内部,Hom(1, A)
为对象。
因此,全局元素是Set
中具有终端对象的任何类别的元素概念的概括。它可以用different进一步推广(在一组集合,偏移或空间中,态射由点上的动作决定,但在一般类别中并非总是如此)。一般来说,一旦我们将“值”(元素,点,常数,术语)转换为相关类别的箭头,我们就可以使用此特定类别的unit set来推理它们。
类似地,我们在generalized elements中有true
为⊤ → Bool
,false
为⊤ → Bool
:
true :: () -> Bool
true = const True
false :: () -> Bool
false = const Frue
the language中的{p> true ≠ false
我们还有一个⊥
家庭⊤ → Bool
undefined
,error "..."
,{ {1}},一般递归等等):
fix
bottom1 :: () -> Bool
bottom1 = const undefined
bottom2 :: () -> Bool
bottom2 = const $ error "..."
bottom3 :: () -> Bool
bottom3 = const $ fix id
bottom4 :: () -> Bool
bottom4 = bottom4
bottom5 :: () -> Bool
bottom5 = const b where b = b
...
就是这样,我们找不到⊥ ≠ false ≠ true
形式的任何其他态射,因此⊤ → Bool
,⊥
和false
是true
的唯一值可以在扩展上区分。请注意,在Bool
中,任何对象都有值,即有人居住,因为对于任何类型Hask
,始终存在态射⊤ → A
,它会使A
与Hask
不同或任何其他非平凡Set
(其内部逻辑有点无聊,这就是快速和宽松推理是道德正确的论文,我们需要寻找Haskell的一个子集,它有一个好的CCC
有一个理智的逻辑)。
此外,在类型理论中,值在语法上表示为再次Hask
类似分类语义的术语。
如果我们谈论“柏拉图式”(即总数,CCC
)BiCCC
,那么这里是Agda中Hask
的一个简单证明(很好地捕捉了柏拉图式的特征):
A ≊ Hom(1, A)