Writing to mutable property for a struct record is not allowed in F#. Why? - f#

When I have the following code:
[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}
let john = { FirstName = "John"; LastName = "Connor"}
john.FirstName <- "Sarah";
The compiler complains that "A value must be mutable in order to mutate the contents". However when I remove the Struct attribute it works fine. Why is that so ?

This protects you from a gotcha that used to plague the C# world a few years back: structs are passed by value.
Note that the red squiggly (if you're in IDE) appears not under FirstName, but under john. The compiler complains not about changing the value of john.FirstName, but about changing the value of john itself.
With non-structs, there is an important distinction between the reference and the referenced object:
Both the reference and the object itself can be mutable. So that you can either mutate the reference (i.e. make it point to a different object), or you can mutate the object (i.e. change the contents of its fields).
With structs, however, this distinction does not exist, because there is no reference:
This means that when you mutate john.FirstName, you also mutate john itself. They are one and the same.
Therefore, in order to perform this mutation, you need to declare john itself as mutable too:
[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}
let mutable john = { FirstName = "John"; LastName = "Connor"}
john.FirstName <- "Sarah" // <-- works fine now
For further illustration, try this in C#:
struct Person
{
public string FirstName;
public string LastName;
}
class SomeClass
{
public Person Person { get; } = new Person { FirstName = "John", LastName = "Smith" };
}
class Program
{
static void Main( string[] args )
{
var c = new SomeClass();
c.Person.FirstName = "Jack";
}
}
The IDE will helpfully underline c.Person and tell you that you "Cannot modify the return value of 'SomeClass.Person' because it is not a variable".
Why is that? Every time you write c.Person, that is translated into calling the property getter, which is just like another method that returns you a Person. But because Person is passed by value, that returned Person is going to be a different Person every time. The getter cannot return you references to the same object, because there can be no references to a struct. And therefore, any changes you make to this returned value will not be reflected in the original Person that lives inside SomeClass.
Before this helpful compiler error existed, a lot of people would do this:
c.Person.FirstName = "Jack"; // Why the F doesn't it change? Must be compiler bug!
I clearly remember answering this question almost daily. Those were the days! :-)

Related

Difference between "toString" and "as String" in Dart?

Is there any difference between .toString and as String in Dart?
toString() is a method on Object and is therefore available on every object. The method is used to get a string representation of the object:
A string representation of this object.
Some classes have a default textual representation, often paired with a static parse function (like int.parse). These classes will provide the textual representation as their string represetion.
Other classes have no meaningful textual representation that a program will care about. Such classes will typically override toString to provide useful information when inspecting the object, mainly for debugging or logging.
https://api.dart.dev/stable/2.13.4/dart-core/Object/toString.html
as String is a typecast in Dart and is used to tell the analyzer/compiler that whatever it assumes, you are now going to tell it that your object is in fact a String at runtime. You can hereafter use the object like a String.
But the compiler will add a check at runtime and if the object is not compatible with the interface of String, your application will crash because you have lied to the compiler.
It is therefore two entire different things and is used for different purposes. You can e.g. not use as String on an object which is not already a String.
The safest you can do is just call toString() since toString() on String will just return itself.
They are completely different!
.toString() is a method to represent data of a object but as String is a type cast which tries to convert the object itself to a String .
Imagine you have a class named Person
class Person {
String firstName;
String lastName;
Person(
this.firstName,
this.lastName,
);
}
now casting person to a String will lead to an _CastError error since Person is not a String or a subtype of String(inherited classes from String class)
final person = Person('sajad', 'abd');
final personAsString = person as String;
Meanwhile, the method .toString() will represent you object in a String.
final person = Person('sajad', 'abd');
final personToString = person.toString();
print(personToString); // result: Instance of 'Person'
.toString() is defined for every class in dart and you can override it in you custom classes
for example you can override it in Person class to represent firstname and lastname of person
class Person {
String firstName;
String lastName;
Person(
this.firstName,
this.lastName,
);
#override
String toString() => 'Person(firstName: $firstName, lastName: $lastName)';
}
And now the result of
final person = Person('sajad', 'abd');
final personToString = person.toString();
print(personToString);
would be
Person(firstName: sajad, lastName: abd)

Some mistakes in the language

I'm new in Dart, I want to precise this. I create two classes in Dart, one is 'Person' and the other one is a child of the first one, and it's named 'Employee'.
I create an object of Person. When I change this object to an Instance of class Employee, nothing wrong. But at the time that I'm asking to a parameter that is inside the Employee, I raised an error.
So why Dart allow me to the class of the object, but not allowed me to access to a parameter inside the new class?
The code below :
void main {
var person = Person(name: "Zozor");
print(person.describe());
person = Employee(taxCode: 'AAB');
person.sayName();
print(person.taxCode);
}
class Person {
Person({this.name, this.age, this.height});
String name;
final int age;
final double height;
String describe() => "Hello, I'm ${this.name}. I'm ${this.age} and I'm ${this.height} meter${this.height == 1 ? '':'s'} tall";
void sayName()=> print("Hello, I'm ${this.name}.");
}
class Employee extends Person {
Employee({String name, int age, double height, this.taxCode, this.salary}) : super(name:name, age: age, height: height);
final String taxCode;
final int salary;
}
Variables in Dart must be declared before being used. They have a type and store a reference to the value (see https://www.tutorialspoint.com/dart_programming/dart_programming_variables.htm).
var person = Person(name: "Zozor");
Declares the variable person of type Person (type is derived from the type of class it is initialized to).
When you assign:
person = Employee(taxCode: 'AAB');
The type is unchanged with the assignment (i.e. remains Person), only the reference changes to the result of downcasting the Employee to a Person (the downcasting is done implicitly as described https://news.dartlang.org/2012/05/types-and-casting-in-dart.html).
The above is due to var creating static type variables.
An alternative would be to use dynamic typing as in:
dynamic person = Person(name: "Zozor");
This declares a person variables whose type is dynamic. Now when the assignment is made to Employee:
person = Employee(taxCode: 'AAB');
The type of the person variable is now Employee rather than Person. Furthermore, there is no downcasting of Employee and no error message related to taxCode.
A simple way to stay with static (rather than using dynamic) is to use an explicit recasting of person to Employee:
print((person as Employee).taxCode);
This casts a person to Employee then gets the taxCode.

Checking a nullable property after a null-conditional check of parent object

Appreciate there have been some question close to what I am asking but not quite like here. I have been checking the ?. operator and I came upon the following scenario. Situation is as follows:
internal class Dog
{
public int? Age { get; set; }
}
Checks in main code were as follows:
Dog d2 = new Dog() { Age = 10 };
int age1 = d2.Age.Value; // compiles okay
int age2 = d2?.Age.Value; // CS0266
I would like to know why the code line with age3 is requesting an explicit cast. d2.Age being type int? and Age.Value being type int doesn't vary between the two usages.
Once you use the null-condicional operator, the resulting value can be null. that's why it can never be int.
What you need is:
int age2 = (d2?.Age).Value;

Dart: How to bind to variables annotated with int via Web UI?

What is the best practice in Dart when dealing with classes as data records?
To Elaborate: When writing an app, it is likely that a class for a table row will be created. As in
class Item { int itemid, String itemName, double score }
Item item = new Item();
This allows compile time catching of any typos etc. in Dart. (Unlike using a class that relies on NoSuchMethod.)
It will also need a corresponding string structure to bind to the HTML such as
<input id="itemname" type="text" bind-value="itemEdit.itemName">
So the Dart would be:
class ItemEdit { String itemId, String itemName, String score }
ItemEdit itemEdit = new ItemEdit();
Next we need a way to get from one to the other, so we add a method to Item
fromStrings(ItemEdit ie) {
itemid = ie.itemId == null ? null : int.parse(ie.itemId);
itemName = ie.ItemName;
score = ie.score == null ? null : double.parse(ie.score);
}
And the other way around:
toStrings(ItemEdit ie) {
ie.itemid = itemId == null ? '' : ie.itemId.toString();
ie. itemName = itemName == null ? '' : itemname; // Web_ui objects to nulls
ie.score = score == null ? null : score.toStringAsFixed(2);
}
Also, we get jason data from a database, so we need to add another method to Item:
fromJson(final String j) {
Map v = JSON.parse(j);
itemid = v['itemid'];
itemname = v['itemname'];
score = v['score'];
}
And we need to be able to revert to default values:
setDefaults() { itemId = 0; itemName = "New item"; score = 0; }
This verbosity gets me feeling like I am writing COBOL again!
There is something fundamental missing here - either in my understanding, or in the Dart/WebUI libraries.
What I would like to write is something like
class Item extends DataRecord {
int itemid = 0,
String itemName = 'New item',
double score = 0.0;
}
Then, without further coding, to be able to write code such as
item.toStrings();
...
item.fromStrings();
...
item.fromJson(json);
...
item.setDefaults(); // results in {0,'New item',0.0}
And to be able to write in the HTML:
value="{{item.strings.score}}"
If this was possible, it would be quicker, simpler, clearer, and less error prone than the code I am writing at the moment.
(Full disclosure, this answer is written with the assumption that at least one bug will be fixed. See below)
Three suggestions that might help.
Use named constructors to parse and create objects.
Take advantage of toJson() when encoding to JSON.
Use bind-value-as-number from Web UI
1) Named constructors
import 'dart:json' as json;
class Item {
int itemid;
String itemName;
double score;
Item.fromJson(String json) {
Map data = json.parse(json);
itemid = data['itemid'];
itemName = data['itemName'];
score = data['score'];
}
}
2) Encoding to JSON
The dart:json library has a stringify function to turn an object into a JSON string. If the algorithm encounters an object that is not a string, null, number, boolean, or collection of those, it will call toJson() on that object and expect something that is JSON-encodable.
class Item {
int itemid;
String itemName;
double score;
Map toJson() {
return {'itemid':itemid, 'itemName':itemName, 'score':score};
}
}
3) Now, having said that, sounds like you want to easily bind to HTML fields and get primitive values back, not just strings. Try this:
<input type="number" min="1" bind-value-as-number="myInt" />
(Note, there seems to be a bug with this functionality. See https://github.com/dart-lang/web-ui/issues/317)
(from https://groups.google.com/a/dartlang.org/forum/#!topic/web-ui/8JEAA5OxJOc)
Just found a way to perhaps help a little in the this situation:
class obj {
int gapX;
String get gapXStr => gapX.toString();
set gapXStr(String s) => gapX = int.Parse(s);
...
Now, from the HTML you can use, for example
bind-value="gapXStr"
and in code you can use
x += ob.gapX;

Using NoRM to access MongoDB from F#

Testing out NoRM https://github.com/atheken/NoRM from F# and trying to find a nice way to use it. Here is the basic C#:
class products
{
public ObjectId _id { get; set; }
public string name { get; set; }
}
using (var c = Mongo.Create("mongodb://127.0.0.1:27017/test"))
{
var col = c.GetCollection<products>();
var res = col.Find();
Console.WriteLine(res.Count().ToString());
}
This works OK but here is how I access it from F#:
type products() =
inherit System.Object()
let mutable id = new ObjectId()
let mutable _name = ""
member x._id with get() = id and set(v) = id <- v
member x.name with get() = _name and set(v) = _name <- v
Is there an easier way to create a class or type to pass to a generic method?
Here is how it is called:
use db = Mongo.Create("mongodb://127.0.0.1:27017/test")
let col = db.GetCollection<products>()
let count = col.Find() |> Seq.length
printfn "%d" count
Have you tried a record type?
type products = {
mutable _id : ObjectId
mutable name : string
}
I don't know if it works, but records are often good when you just need a class that is basically 'a set of fields'.
Just out of curiosity, you can try adding a parameter-less constructor to a record. This is definitely a hack - in fact, it is using a bug in the F# compiler - but it may work:
type Products =
{ mutable _id : ObjectId
mutable name : string }
// Horrible hack: Add member that looks like constructor
member x.``.ctor``() = ()
The member declaration adds a member with a special .NET name that is used for constructors, so .NET thinks it is a constructor. I'd be very careful about using this, but it may work in your scenario, because the member appears as a constructor via Reflection.
If this is the only way to get succinct type declaration that works with libraries like MongoDB, then it will hopefuly motivate the F# team to solve the problem in the future version of the language (e.g. I could easily imagine some special attribute that would force F# compiler to add parameterless constructor).
Here is a pretty light way to define a class close to your C# definition: it has a default constructor but uses public fields instead of getters and setters which might be a problem (I don't know).
type products =
val mutable _id: ObjectId
val mutable name: string
new() = {_id = ObjectId() ; name = ""}
or, if you can use default values for your fields (in this case, all null):
type products() =
[<DefaultValue>] val mutable _id: ObjectId
[<DefaultValue>] val mutable name: string

Resources