我是Haskell的新手。对不起,如果这个问题有明显的答案。
我有
data Tmp = Foo Int
| Bar Int
| Baz Int
和
data Test = A Tmp Tmp
构造函数A Tmp Tmp
可以使用Tmp
的任何构造函数,除了
A (Baz i) (Baz j)
其中i
和j
是Int
个Tmp
。我有什么办法吗?
如果第一个A Tmp Tmp
已经Baz
,则限制Tmp
中的第二个Baz
免于package com.example.malik.shoutout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.google.android.gms.drive.Drive;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
public abstract class MainActivity extends AppCompatActivity
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
setContentView(R.layout.activity_main);
}
}
?
答案 0 :(得分:13)
答案取决于您希望如何强制执行限制:在运行时或编译时。
要在运行时强制执行限制,您可以添加一个检查限制的函数(例如makeA
),然后调用构造函数。这样一个函数可以执行一些操作,然后调用构造函数也称为智能构造函数。如果您只从模块中导出智能构造函数makeA
而不是真正的构造函数A
,则可以确保其他模块使用智能构造函数,因此始终会检查限制。
示例:
module Test (Tmp (Foo, Bar, Baz), Test (), makeA) where
data Tmp
= Foo Int
| Bar Int
| Baz Int
data Test = A Tmp Tmp
makeA :: Tmp -> Tmp -> Tmp
makeA (Baz _) (Baz _) = error "makeA: two baz problem"
makeA tmp1 tmp2 = A tmp1 tmp2
此技术的好处是您根本不必更改数据类型。缺点是限制仅在运行时强制执行。
要在编译时强制执行限制,您需要以某种方式更改数据类型。当前数据类型的问题在于类型检查器无法区分由Foo
和Bar
构造的值以及由Baz
构造的值。对于类型检查器,这些都是Tmp
值,因此类型检查器不能强制某些Tmp
值正常而其他值不正确。所以我们必须更改数据类型以编码类型中Tmp
值的“Bazness”。
在类型中编码Bazness的一个选项是重构Tmp
,如下所示:
data TmpNotBaz
= Foo Int
| Bar Int
data Tmp
= NotBaz TmpNotBaz
| Baz Int
现在很明显,TmpNotBaz
类型的值不能是Baz
,但类型Tmp
的值可以是Baz
。这个想法的好处是它只使用基本的Haskell功能。一个小缺点是您需要将NotBaz
的调用放入您的代码中。一个主要的缺点是,我们仍然不能直接表达“A
如果另一个不是”Baz
可以A
的一个论点的想法。我们必须编写data Test
= A1 TmpNotBaz Tmp
| A2 Tmp TmpNotBaz
的多个版本:
A1
现在,我们可以根据需要选择A2
或A (Baz ...) (Baz ...)
来表达我们想要的所有值,并且我们无法再根据需要表达A (Foo 1) (Foo 2)
。此解决方案的一个问题是,过去有多种表示形式,例如A1 (Foo 1) (NotBaz (Foo 2))
:A2 (NotBaz (Foo 1)) (Foo 2)
和Tmp
都代表此值。
您可以尝试使用此类数据类型的结构,并创建适合您情况的版本。
在类型中对Bazness进行编码的另一个选择是将一些类型级别的信息注释到{-# LANGUAGE GADTs, TypeFamilies, DataKinds #-}
data Bazness = IsBaz | NotBaz
data BothBazOrNot = BothBaz | NotBothBaz
type family AreBothBaz (b1 :: Bazness) (b2 :: Bazness) :: BothBazOrNot where
AreBothBaz 'IsBaz 'IsBaz = 'BothBaz
AreBothBaz _ _ = 'NotBothBaz
data Tmp (b :: Bazness) :: * where
Foo :: Int -> Tmp 'NotBaz
Bar :: Int -> Tmp 'NotBaz
Baz :: Int -> Tmp 'IsBaz
data Test where
A :: AreBothBaz b1 b2 ~ 'NotBothBaz => Tmp b1 -> Tmp b2 -> Test
类型,并使用类型级编程来推断这种类型 - 级别信息。这个想法的缺点是它使用更高级的Haskell功能。实际上,有许多新兴方法可以做这种事情,并且不清楚哪些最终将被视为“标准”高级Haskell。也就是说,这是一种方法:
Foo
请注意构造函数Bar
,Baz
和IsBaz
的类型签名是如何讨论构造函数是否创建了NotBaz
或A
的内容。以及b1
的类型签名如何讨论某些b2
和NotBothBaz
选项,以便A (Foo 1) (Bar 2)
。
使用此代码,我们可以编写以下表达式:
A (Foo 1) (Baz 2)
A (Baz 1) (Bar 2)
A (Baz 1) (Baz 2)
但如果我们尝试编写Couldn't match type 'BothBaz with 'NotBothBaz
arising from a use of A
In the expression: A (Baz 1) (Baz 2)
,则类型检查器会抱怨:
A
因此类型检查器发现在这种情况下,BothBaz
的参数是A
,但我们注释NotBothBaz
的类型只接受{{1}的参数},因此类型检查器会抱怨BothBaz
与NotBothBaz
不同。