我一直在考虑使用Option类型。
这意味着转换如下函数:
Customer GetCustomerById(Int32 customerID) {...}
Customer c = GetCustomerById(619);
DoStuff(c.FirstName, c.LastName);
返回选项Maybe
类型:
Maybe<Customer> GetCustomerById(Int32 customerID) {...}
在我的非功能语言中,我必须检查返回值是否存在:
Maybe<Customer> c = GetCustomerById(619);
if (c.HasValue)
DoStuff(c.Value.FirstName, c.Value.LastName);
这很有效:
null
(而不是引发异常)但我没有使用其RAII的C#,Java或C ++。我在德尔福;具有手动内存管理的本机语言。我将继续以类似 C#的语言显示代码示例。
使用手动内存管理,我的原始代码:
Customer c = GetCustomerById(619);
if (c != nil)
{
try
{
DoStuff(c.FirstName, c.LastName);
}
finally
{
c.Free();
}
}
转换为:
Maybe<Customer> c = GetCustomerById(619);
if (c.HasValue)
{
try
{
DoStuff(c.Value.FirstName, c.Value.LastName);
}
finally
{
c.Value.Free();
}
}
我现在有一个Maybe<>
持有 无效 的引用;它的更糟比null更好,因为现在Maybe
认为它有有效内容,并且内容确实有一个指向内存的指针,但该内存不是& #39; t有效。
我已将NullReferenceException
交易到随机数据损坏崩溃错误。
有没有人想过这个问题,以及解决它的技巧?
我考虑过在名为Free
的结构中添加一个方法:
void Free()
{
if (this.HasValue())
{
_hasValue = false;
T oldValue = _value;
_value = null;
oldValue.Free();
}
}
如果人们称之为,哪个有效;并知道称之为;知道为什么叫它;并知道他们不应该打电话给他们。
许多微妙的知识,以避免我尝试使用选项类型时引入的危险错误。
当被Maybe<T>
包裹的对象实际上通过一个名为canonical Free
的方法间接销毁时,它也会崩溃:
Maybe<ListItem> item = GetTheListItem();
if item.HasValue then
begin
DoStuffWithItem(item.Value);
item.Value.Delete;
//item still thinks it's valid, but is not
item.Value.Selected := False;
end;
Nullable
/ Maybe
/ Option
类型在处理没有内置非值的类型时具有优势(例如记录,整数) ,字符串,其中没有内置的非值)。
如果某个函数返回一个不可为空的值,那么在没有使用某些特殊的 sentinal 值的情况下,无法传达不存在的返回结果。
function GetBirthDate(): TDateTime; //returns 0 if there is no birth date
function GetAge(): Cardinal; //returns 4294967295 if there is no age
function GetSpouseName: string; //returns empty string if there is no spouse name
选项用于避免特殊的标记值,并与调用者进行实际通信。
function GetBirthDate(): Maybe<TDateTime>;
function GetAge(): Maybe<Integer>;
function GetSpouseName: Maybe<string>;
Option
类型通过将事物与无关的东西分开来避免NullReferenceExceptions
(或EAccessViolation
地址$ 00000000)的流行即可。
函数返回特殊的,有时是危险的sentinal值
function GetBirthDate(): TDateTime; //returns 0 if there is no birth date
function GetAge(): Cardinal; //returns 4294967295 if there is no age
function GetSpouseName: string; //returns empty string if there is no spouse name
function GetCustomer: TCustomer; //returns nil if there is no customer
转换为不可能存在特殊危险的特殊价值的表格:
function GetBirthDate(): Maybe<TDateTime>;
function GetAge(): Maybe<Integer>;
function GetSpouseName: Maybe<string>;
function GetCustomer: Maybe<TCustomer>;
调用者意识到函数可以返回没有东西,并且他们必须通过检查存在的箍。对于已支持 null 的类型,Option
使我们有机会尝试阻止人们导致NullReference异常。
在函数式编程语言中,它更加健壮;可以构造返回类型,以便不可能返回nil
- 编译器只是不允许它。
在过程式编程语言中,我们所能做的最好就是锁定nil
,并使其无法触及。在此过程中,调用者拥有更强大的代码。
有人可能会争论&#34;为什么不告诉开发人员永远不要犯错误&#34; :
为:
customer = GetCustomer();
Print(customer.FirstName);
不可:
customer = GetCustomer();
if Assigned(customer)
Print(customer.FirstName);
得到好的。
问题是我希望编译器能够捕获这些错误。我希望错误首先更难发生。我想要一个成功的坑。它强制调用者理解该函数可能会失败。 签名本身解释了该做什么,并使处理它变得容易。
在这种情况下,我们隐式返回两个值:
函数式编程语言中的人已经采用了这个概念,并且这是一个人们试图重新使用过程语言的概念,即如果值存在或不存在,你就会传达一种新的类型。试图盲目使用它会产生编译时错误:
customer = GetCustomer();
Print(customer.FirstName); //syntax error: Unknown property or method "FirstName"
如果您想了解有关尝试在过程语言中使用功能Maybe
monad的更多信息,可以参考更多有关该主题的想法:
答案 0 :(得分:5)
唯一可以保护您免除Value
Maybe
Maybe
中包含的任何内容的行为,就是从清除Maybe
内容的 TMaybe<T> = record
strict private
FValue: T;
public
...
function ExtractValue: T;
end;
function TMaybe<T>.ExtractValue: T;
begin
if not _hasValue then raise Exception.Create('Invalid operation, Maybe type has no value');
Result := FValue;
_hasValue = false;
FValue := Default(T);
end;
变量中提取值并使用结果就像你通常会使用任何对象引用一样。
类似的东西:
Maybe<ListItem> item = GetTheListItem();
if item.HasValue then
begin
Value := item.ExtractValue;
DoStuffWithItem(Value);
Value.Free;
end;
然后你将被迫提取价值以便使用它。
1) yum install mysql-devel
2) gem install mysql2
答案 1 :(得分:3)
Delphi中不需要Maybe
,因为对象是引用类型,因此您可以使用nil
对象指针,例如:
type
TCustomer = class
public
customerID: Int32;
FirstName, LastName: string;
end;
function GetCustomerById(customerID: Int32): TCustomer;
begin
...
if (customerID is found) then
Result := ...
else
Result := nil;
end;
var
c: TCustomer;
begin
c := GetCustomerById(619);
if c <> nil then
DoStuff(c.FirstName, c.LastName);
end;
如果函数需要为返回分配新对象,例如:
function GetCustomerById(customerID: Int32): TCustomer;
begin
...
if (customerID is found) then
begin
Result := TCustomer.Create;
...
end else
Result := nil;
...
end;
然后你有两个终身管理选择(假设调用者需要获得对象的所有权,因为它不属于其他地方)。
1)完成使用对象后,您可以调用Free
:
var
c: TCustomer;
begin
c := GetCustomerById(619);
if c <> nil then
try
DoStuff(c.FirstName, c.LastName);
finally
c.Free;
end;
end;
2)您可以使用引用计数界面:
type
ICustomer = interface
['{2FBD7349-340C-4A4E-AA72-F4AD964A35D2}']
function getCustomerID: Int32;
function getFirstName: string;
function getLastName: string;
property CustomerID: Int32 read getCustomerID;
property FirstName: string read getFirstName;
property LastName: string read getLastName;
end;
TCustomer = class(TInterfacedObject, ICustomer)
public
fCustomerID: Int32;
fFirstName, fLastName: string;
function getCustomerID: Int32;
function getFirstName: string;
function getLastName: string;
end;
function TCustomer.getCustomerID: Int32;
begin
Result := fCustomerID;
end;
function TCustomer.getFirstName: string;
begin
Result := fFirstName;
end;
function TCustomer.getLastName: string;
begin
Result := fLastName;
end;
function GetCustomerById(customerID: Int32): ICustomer;
begin
...
if (customerID is found) then
begin
Result := TCustomer.Create as ICustomer;
...
end else
Result := nil;
end;
var
c: ICustomer;
begin
c := GetCustomerById(619);
if c <> nil then
DoStuff(c.FirstName, c.LastName);
end;