如何在F#中定义非零整数,在分配零值时会出现编译时错误?
我的问题是在16:00分钟观看 Scott Wlaschin 视频https://www.youtube.com/watch?v=E8I19uA-wGY&t=960s
我在SO中找到了这个问题的另一个答案,但都指的是动态检查(在创建时抛出异常),但这种方法并不是什么大问题,可以在任何OO而不是OO语言中完成。我所期待的是:type NonZeroInteger = int except 0
或类似的东西。
答案 0 :(得分:5)
在F#中,没有真正为你想做的事编译时间合同。处理此问题的F#方法是使用具有私有构造函数的类型NonZeroInteger
和返回Option<NonZeroInteger>
的函数。这将确保您永远不会有Some(0)
。
这样做基本上迫使使用您的代码的开发人员在给定错误的整数值时,在构造一个NonZeroInteger
之后可能没有open Option
module A =
type NonZeroInteger =
private | NonZeroInteger of int
static member Create (v : int) : Option<NonZeroInteger> =
if v = 0 then
None
else
Some(NonZeroInteger(v))
member this.Get : int =
this |> fun (NonZeroInteger v) -> v
member this.Print =
this |> fun (NonZeroInteger v) -> printfn "%i" v
printfn "%A" (A.NonZeroInteger(0)) // error FS1093: The union cases or fields of the type 'NonZeroInteger' are not accessible from this code location
let wrong = A.NonZeroInteger.Create(0) // None
let right = A.NonZeroInteger.Create(-1) // Some
wrong |> Option.iter (fun x -> x.Print) // Doesn't print anything
right |> Option.iter (fun x -> x.Print) // Prints -1
。
在您的代码中,您可以随时安全地假设NonZeroIntegers实际上是非零。
private
NonZeroInteger
构造函数可以阻止模块外的任何人构建<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height= "wrap_content"
android:gravity="center_horizontal"
android:background="@drawable/bg_image_login"
android:orientation="vertical"
android:windowSoftInputMode="stateVisible|adjustResize"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.fania.suara_project.LoginActivity">
<ImageView
android:id="@+id/haha"
android:background="drawable/bg_image_login"
android:scaleType="fitXY"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
,而无需通过Create函数。
这使得代码非常冗长和缓慢,但却很安全。所以这里肯定是一种权衡。
答案 1 :(得分:0)
您无法直接执行此操作,例如Scala的require
。
这是一种方式,有点DDD-ish:
type NonZeroInteger = private NonZeroInteger of int
let createNZI i =
if i = 0 then
Error "NonZeroInteger can't be Zero"
else
Ok (NonZeroInteger i)
构造函数是私有的,因此您必须通过createNZI函数(如果更自然,可以切换名称)。比处理Result
类型。如果更简单,可以使用Some / None。最后,如果需要,使用模式匹配或活动模式打开NonzeroInteger。