Haskell 如何创建具有限制的类型

Haskell 如何创建具有限制的类型,haskell,dependent-type,Haskell,Dependent Type,例如,我想将一个类型MyType设置为整数三元组。但不仅仅是三个整数的笛卡尔积,我希望类型代表所有(x,y,z),这样x+y+z=5 我该怎么做?除了使用(x,y)之外,因为z=5-x-y 同样的问题,如果我有三个构造函数A,B,C,类型应该都是(A x,B y,C z),这样x+y+z=5我认为这里的诀窍是,你不在类型级别上强制它,你使用“智能构造函数”:也就是说,只允许通过生成这样的值的函数创建这样的“元组”: module Test(MyType,x,y,z,createMyType) w

例如,我想将一个类型MyType设置为整数三元组。但不仅仅是三个整数的笛卡尔积,我希望类型代表所有(x,y,z),这样x+y+z=5

我该怎么做?除了使用(x,y)之外,因为z=5-x-y


同样的问题,如果我有三个构造函数A,B,C,类型应该都是(A x,B y,C z),这样x+y+z=5

我认为这里的诀窍是,你不在类型级别上强制它,你使用“智能构造函数”:也就是说,只允许通过生成这样的值的函数创建这样的“元组”:

module Test(MyType,x,y,z,createMyType) where

data MyType = MT { x :: Int, y :: Int, z :: Int }

createMyType :: Int -> Int -> MyType
createMyType myX myY = MT { x = myX, y = myY, z = 5 - myX - myY }
如果您想生成所有可能的值,那么您可以编写一个函数来生成这些值,可以使用提供的边界,也可以使用指定的边界

很有可能使用类型级别的教堂数字或类似的数字来强制创建这些数字,但对于您可能想要/需要的内容来说,这几乎肯定是太多的工作了

这可能不是您想要的(即“除了使用(x,y),因为z=5-x-y”),但这比试图在类型级别上强制限制允许有效值更有意义


类型可以确保值的“类型”正确(没有双关语);为了确保值的有效性,您隐藏了构造函数,只允许通过批准的函数创建,以保证您需要的任何不变量。

我不是这方面的专家,但我认为您不能在Haskell的类型级别上实现这一点,因为Haskell不支持依赖类型。您可能想看看Agda。

是的,智能构造函数或Agda是一个不错的选择,但是如果您真的想疯狂使用“依赖”方法,在Haskell中:

{-# LANGUAGE GADTs, TypeFamilies, RankNTypes, StandaloneDeriving, UndecidableInstances, TypeOperators #-}

data Z = Z
data S n = S n

data Nat n where
  Zero :: Nat Z
  Suc  :: Nat n -> Nat (S n)

deriving instance Show (Nat n)

type family (:+) a b :: *
type instance (:+) Z b = b
type instance (:+) (S a) b = S (a :+ b)

plus :: Nat x -> Nat y -> Nat (x :+ y)
plus Zero y = y
plus (Suc x) y = Suc (x `plus` y)

type family (:*) a b :: *
type instance (:*) Z b = Z
type instance (:*) (S a) b = b :+ (a :* b)

times :: Nat x -> Nat y -> Nat (x :* y)
times Zero y = Zero
times (Suc x) y = y `plus` (x `times` y)

data (:==) a b where
  Refl :: a :== a

deriving instance Show (a :== b)

cong :: a :== b -> f a :== f b
cong Refl = Refl

data Triple where
  Triple :: Nat x -> Nat y -> Nat z -> (z :== (x :+ y)) -> Triple

deriving instance Show Triple

-- Half a decision procedure
equal :: Nat x -> Nat y -> Maybe (x :== y)
equal Zero Zero = Just Refl
equal (Suc x) Zero = Nothing
equal Zero (Suc y) = Nothing
equal (Suc x) (Suc y) = cong `fmap` equal x y

triple' :: Nat x -> Nat y -> Nat z -> Maybe Triple
triple' x y z = fmap (Triple x y z) $ equal z (x `plus` y)

toNat :: (forall n. Nat n -> r) -> Integer -> r
toNat f n | n < 0 = error "why can't we have a natural type?"
toNat f 0 = f Zero
toNat f n = toNat (f . Suc) (n - 1)

triple :: Integer -> Integer -> Integer -> Maybe Triple
triple x y z = toNat (\x' -> toNat (\y' -> toNat (\z' -> triple' x' y' z') z) y) x

data Yatima where
  Yatima :: Nat x -> Nat y -> Nat z -> ((x :* x) :+ (y :* y) :+ (z :* z) :== S (S (S (S (S Z))))) -> Yatima

deriving instance Show Yatima

yatima' :: Nat x -> Nat y -> Nat z -> Maybe Yatima
yatima' x y z = 
  fmap (Yatima x y z) $ equal ((x `times` x) `plus` (y `times` y) `plus` (z `times` z)) (Suc (Suc (Suc (Suc (Suc Zero)))))

yatima :: Integer -> Integer -> Integer -> Maybe Yatima
yatima x y z = toNat (\x' -> toNat (\y' -> toNat (\z' -> yatima' x' y' z') z) y) x


{-
λ> triple 3 4 5
Nothing
λ> triple 3 4 7
Just (Triple (Suc (Suc (Suc Zero))) (Suc (Suc (Suc (Suc Zero)))) Refl (Suc (Suc (Suc (Suc (Suc (Suc (Suc Zero))))))))

λ> yatima 0 1 2 
Just (Yatima Zero (Suc Zero) (Suc (Suc Zero)) Refl)
λ> yatima 1 1 2 
Nothing
-}
{-#语言GADT、类型族、RankNTypes、独立派生、不可判定实例、类型运算符#-}
数据Z=Z
数据sn=sn
数据Nat n在哪里
零::Nat Z
Suc::Nat n->Nat(sn)
派生实例显示(Nat n)
类型族(+)a b::*
类型实例(:+)Z b=b
类型实例(+:+)(SA)b=S(a:+b)
加:Nat x->Nat y->Nat(x:+y)
加零y=y
正(Suc x)y=Suc(x`plus`y)
类型族(:*)a b::*
类型实例(:*)Z b=Z
类型实例(:*)(sa)b=b:+(a:*b)
时间:Nat x->Nat y->Nat(x:*y)
乘以0 y=0
次数(Suc x)y=y`plus`(x`times`y)
数据(:==)a b其中
Refl::a:==a
派生实例显示(a:==b)
丛::a:==b->f a:==f b
cong Refl=Refl
三重数据在哪里
三重:Nat x->Nat y->Nat z->(z:==(x:+y))->三重
派生实例显示三元组
--半个决策程序
相等:Nat x->Nat y->Maybe(x:==y)
等于零=仅反射
相等(Suc x)零=零
等于零(Suc y)=零
相等(Suc x)(Suc y)=cong`fmap`equal x y
三重“::Nat x->Nat y->Nat z->可能是三重
三重x y z=fmap(三重x y z)$等于z(x`plus`y)
音调::(对于所有n.natn->r)->Integer->r
toNat f n | n<0=错误“为什么我们不能有一个自然类型?”
音调f0=f0
toNat f n=toNat(f.Suc)(n-1)
三重:整数->整数->整数->可能是三重
三重x y z=toNat(\x'->toNat(\y'->toNat(\z'->triple'x'y'z')y)x
数据Yatima在哪里
Yatima::Nat x->Nat y->Nat z->((x:*x):+(y:*y):+(z:*z):==S(S(S(S(S(S z)))))->Yatima
派生实例Show Yatima
yatima'::Nat x->Nat y->Nat z->可能是yatima
yatima'x y z=
fmap(Yatima x y z)$相等((x`times`x)`plus`(y`times`y)`plus`(z`times`z))(Suc(Suc(Suc(Suc(Suc)Zeroщ)'))
整型->整型->整型->可能是整型
yatima x y z=toNat(\x'->toNat(\y'->toNat(\z'->yatima'x'y'z')y)x
{-
λ> 三重3 4 5
没有什么
λ> 三重347
正(三重(Suc(Suc(Suc零)))(Suc(Suc(Suc(Suc零)))Refl(Suc(Suc)(Suc(Suc(Suc零))))))
λ> 亚田0 1 2
正(亚田零点(Suc零点)(Suc零点))反射)
λ> 雅蒂玛1 2
没有什么
-}

bam,代码中有一个静态检查的不变量!除了你可以说谎之外…

正常的依赖类型的方法是使用sigma(依赖产品)类型,例如在Agda中:

open import Relation.Binary.PropositionalEquality (_≡_)
open import Data.Nat (ℕ; _+_)
open import Data.Product (Σ; ×; _,_)

FiveTriple : Set
FiveTriple = Σ (ℕ × ℕ × ℕ) (λ{ (x , y , z) → x + y + z ≡ 5 })

someFiveTriple : FiveTriple
someFiveTriple = (0 , 2 , 5) , refl

这就是为什么∑通常被称为“存在”类型:它允许您指定某些数据和有关该数据的某些属性。

仅详细说明ivanm的:


也许你应该看看!这是Haskell没有的依赖类型的情况。你想要Agda或Coq来做这件事。智能构造函数是在Haskell中实现这一点的标准方法。然后确保隐藏真正的构造函数,并且公开的任何操作都保留需求。如何处理条件
x*x+y*y+z*z==5
?现在z作为x和y的函数在某些地方是未定义的,在另一些地方是多值的…
mkXYZ::Int->Int->Int->Maybe MyType
@yatima2975我在下面修改了我的代码以静态地检查该条件:)当然,这在Haskell中毫无意义,但至少是可能的!请注意,对于其当前类型,
equal
函数可能是mean,并且总是吐出
Nothing
。解决方法是使用
或者(x:==y)(不是(x:==y))
,或者如果你不相信空类型是真正空的(但出于某种原因,你不担心人们伪造证据),为自然不等式定义一个归纳类型,然后制作
或者(x:==y)(x:==y)
。发布这个答案是值得的,只是为了说服永远不要尝试这种方法:-)是的:)这种方法在为这类东西设计的语言中,比如Agda,要愉快得多
data MyType = MT {x :: Int, y :: Int, z :: Int } deriving Show

createMyType :: Int -> Int -> Int -> Maybe MyType
createMyType a b c
    | a + b + c == 5 = Just MT { x = a, y = b, z = c }
    | otherwise      = Nothing