How about when the object inside the option type is destroyed?

I have considered using Option .

This means function conversion, for example:

Customer GetCustomerById(Int32 customerID) {...} Customer c = GetCustomerById(619); DoStuff(c.FirstName, c.LastName); 

to return the Maybe parameter:

 Maybe<Customer> GetCustomerById(Int32 customerID) {...} 

in my non-functional language, then I need to check if the return value is present:

 Maybe<Customer> c = GetCustomerById(619); if (c.HasValue) DoStuff(c.Value.FirstName, c.Value.LastName); 

And this works quite well:

  • you know from the function signature whether it can return null (and not to throw an exception)
  • you are trying to check the return value before using it blindly

But no junk collection

But I'm not in C #, Java or C ++ with my RAII. I am in Delphi; native language with manual memory management. I will continue to show code examples in C #.

With manual memory management, my original code is:

 Customer c = GetCustomerById(619); if (c != nil) { try { DoStuff(c.FirstName, c.LastName); } finally { c.Free(); } } 

converts to something like:

 Maybe<Customer> c = GetCustomerById(619); if (c.HasValue) { try { DoStuff(c.Value.FirstName, c.Value.LastName); } finally { c.Value.Free(); } } 

Now I have Maybe<> containing the link is invalid ; it's worse than zero because Maybe now thinks it has valid content and the contents has a pointer to memory, but that memory is not valid.

I exchanged a possible NullReferenceException for a random data failure error.

Has anyone thought about this problem and how it works?

Add. Free for Maybe

I thought about adding a method to a structure called Free :

 void Free() { if (this.HasValue()) { _hasValue = false; T oldValue = _value; _value = null; oldValue.Free(); } } 

What works if people call him; and know to call it; Know why to call it; and know that they should not call.

A lot of subtle knowledge to avoid the dangerous mistake that I just introduced when trying to use the type of option.

It also falls apart when an object wrapped in Maybe<T> is actually destroyed indirectly by a method not called canonical Free :

 Maybe<ListItem> item = GetTheListItem(); if item.HasValue then begin DoStuffWithItem(item.Value); item.Value.Delete; //item still thinks it valid, but is not item.Value.Selected := False; end; 

Bonus Chat

The Nullable / Maybe / Option has the advantage of working with types that do not have a built-in non-value (for example, records, integers, strings where there is no built-in non-value).

If the function returns a value other than zero, then there is no way to report the non-existence of the return result without using any special sentinal values.

 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 

This option is used to prevent special values ​​of the sentinel value and tells the caller what is really happening.

 function GetBirthDate(): Maybe<TDateTime>; function GetAge(): Maybe<Integer>; function GetSpouseName: Maybe<string>; 

Not only for types with zero value

The Option type also gained popularity in order to avoid NullReferenceExceptions (or EAccessViolation at $ 00000000), dividing the thing into nothing .

Functions that return special, sometimes dangerous, issued values

 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 

They are converted into forms where special, sometimes dangerous, brave meanings are impossible:

 function GetBirthDate(): Maybe<TDateTime>; function GetAge(): Maybe<Integer>; function GetSpouseName: Maybe<string>; function GetCustomer: Maybe<TCustomer>; 

Subscribers are created in order to implement a function that may not be returned by a thing , and they must go through the existence test hoop. In the case of types that already support null , Option gives us a chance to try to stop people from throwing NullReference exceptions.

In functional programming languages, it is much more durable; the type of the return value can be constructed so that it is impossible to return nil - the compiler simply does not allow it.

In procedural programming languages, the best we can do is block nil and make achievement impossible. And in this process, the caller has more reliable code.

You can argue "why not tell the developer that he never made mistakes":

Bad

 customer = GetCustomer(); Print(customer.FirstName); 

Good

 customer = GetCustomer(); if Assigned(customer) Print(customer.FirstName); 

Just get it.

The problem is that I want the compiler discovered these errors. I want errors to happen first. I want success. This makes the caller realize that the function may fail. The signature itself explains what to do, and makes it easier to deal with it.

In this case, we implicitly return two values:

  • customer
  • a flag indicating whether the client is really there

People in functional programming languages ​​have adopted a concept, and this is a concept that people are trying to return to procedural languages, that you have a new type that conveys if the value is there or not. And attempts to blindly use it will give a compile-time error:

 customer = GetCustomer(); Print(customer.FirstName); //syntax error: Unknown property or method "FirstName" 

Reading bonuses

If you want to know more about trying to use the Maybe functional monad in procedural languages, you can consult other thoughts on this:

+5
source share
2 answers

The only thing that can protect you from calling anything on your Value wrapped in Maybe is to extract the value from the Maybe variable by clearing the Maybe contents and using the result, as you usually use any object reference.

Sort of:

  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; 

And then you will be forced to extract the value in order to use it.

 Maybe<ListItem> item = GetTheListItem(); if item.HasValue then begin Value := item.ExtractValue; DoStuffWithItem(Value); Value.Free; end; 
+4
source

You don't need Maybe in Delphi, because objects are reference types, so you can use nil object pointers, for example:

 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; 

If the function needs to allocate a new object to return, for example:

 function GetCustomerById(customerID: Int32): TCustomer; begin ... if (customerID is found) then begin Result := TCustomer.Create; ... end else Result := nil; ... end; 

Then you have two options for managing the life cycle (assuming that the caller should take responsibility for the object because it does not belong to anyone else).

1) you can call Free when you are done using the object:

 var c: TCustomer; begin c := GetCustomerById(619); if c <> nil then try DoStuff(c.FirstName, c.LastName); finally c.Free; end; end; 

2) you can use the link counting interface:

 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; 
+3
source

Source: https://habr.com/ru/post/1271208/


All Articles