Amongst several other State-related types, I have the following records types in my code:
type SubmittedSuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
}
type ApprovedSuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
ApprovalDate : DateTime
}
These then feed into the following:
type Suggestion =
| SubmittedSuggestion of SubmittedSuggestionData
| ApprovedSuggestion of ApprovedSuggestionData
This gives me the ability to work with a State machine style pattern to carry out specific business logic dependent upon State. (This approach was taken from: http://fsharpforfunandprofit.com/posts/designing-with-types-representing-states/)
I have a function that in it's simplest form, changes a SubmittedSuggestion to an ApprovedSuggestion:
let ApproveSuggestion suggestion =
match suggestion with
| SubmittedSuggestion suggestion -> ApprovedSuggestion {}
This function is incomplete at the moment as what I'm struggling to comprehend is when a Suggestion changes from Submitted to Approved, how do you copy the properties from the passed in suggestion into the newly created ApprovedSuggestion whilst also populating the new property of ApprovalDate?
I guess it would work if I did something like:
let ApproveSuggestion suggestion =
match suggestion with
| SubmittedSuggestion {SuggestionId = suggestionId; SuggestionText = suggestionText; Originator = originator; ParentCategory = category; SubmissionDate = submissionDate} ->
ApprovedSuggestion {SuggestionId = suggestionId; SuggestionText = suggestionText; Originator = originator; ParentCategory = category; SubmissionDate = submissionDate; ApprovalDate = DateTime.UtcNow}
but that looks pretty horrific to me.
Is there a cleaner, more succinct way of getting the same outcome? I've tried using the with keyword but it didn't compile.
Thanks
If there is a large overlap between types, it is often a good idea to think about decomposition. For example, the types could look like this:
type SuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
}
type ApprovedSuggestionData = {
Suggestion : SuggestionData
ApprovalDate : DateTime
}
Depending on the usage and differences between the types, one could also consider to have the approved type only within the discriminated union, skipping the second type altogether:
type Suggestion =
| SubmittedSuggestion of SuggestionData
| ApprovedSuggestion of SuggestionData * approvalDate : DateTime
A common argument against such decomposition is that access to members of types deeper down the hierarchy becomes more verbose, e.g. approvedSuggestionData.Suggestion.Originator. While this is true, properties can be used to forward commonly used members of constituents if verbosity becomes annoying, and any downside should be weighed against the advantages: there is less code duplication in the types, and any operation that the more granular types offer can be made available from the composed type.
The ability to easily construct an approved suggestion from an unapproved suggestion and the approval date is one case in which this is useful. But there could be more: say, for example, there is an operation that validates the users and categories of all suggestions, approved or not. If the types holding the Originator and ParentCategory members for approved and unapproved suggestions are unrelated, the code that gets these needs to be duplicated. (Or a common interface would need to be created.)
I would change your suggestion to
type SubmittedSuggestionData = {
SuggestionId : Guid
SuggestionText : string
Originator : User
ParentCategory : Category
SubmissionDate : DateTime
ApprovalDate : DateTime option
}
and then approval becomes
let approve t = {t with AprovalDate =Some(System.DateTime.Now)}
Both the suggestions offered by #Vandroiy and #JohnPalmer are good, but for completeness' sake, I'd like to offer a third perspective as well.
As far as I know, there's no language construct that will make copying of values between 'similar' types succinct. The reason for that is that these types are different. Their similarity is incidental, but seen from the type system, they are completely different types. If you look at them 'mathematically', they are simply types A and B.
In such cases, I'd often simply bite the bullet and add a translation function that translates values of one type to values of the other type:
let approve (suggestion : SubmittedSuggestionData) = {
SuggestionId = suggestion.SuggestionId
SuggestionText = suggestion.SuggestionText
Originator = suggestion.Originator
ParentCategory = suggestion.ParentCategory
SubmissionDate = suggestion.SubmissionDate
ApprovalDate = DateTime.UtcNow }
While it seems a little verbose, you're still keeping it DRY because such copying of values is constrained to that single function.
Furthermore, such a function can often be given a good name, which means that the function becomes part of your Domain Model.
Related
I have 3 state machines that follow the similar model:
- same states
- same calls for each state
only the implementation of these calls change.
All the data is carried in a single record that also contains a DU that has the state and there are also a few other common parameters. Each implementation also adds their own fields to that record.
So I would like to figure out if there is a way to make a record I can extend but that contains a number of common fields that the code is expecting to find. Something similar to an interface for a type, but for a record.
For example:
type State =
| a
| b
type StateMachine1StateObject =
{
State: State
SomeData: ...
}
type StateMachine2StateObject =
{
State: State
SomeOtherKindOfData: ...
}
and I would like to have a single state machine loop (which is really a mailbox processor loop) take all the same messages, and be able to rely on some fields that will always be there in that type, but the records themselves may be different as they contain their own fields.
How can this be structured? It looks like I can't use interfaces with records nor combine multiple records into one.
If you have a lot of data in common then can create for example a record, with a field, that has a generic type.
type State<'a> = {
Name: string
Id: int
Extended: 'a
}
type Person = {
FirstName: string
LastName: string
}
type Computer = {
CPU: string
Ghz: float
}
This way, you can create a state that have different Extended data
// State<Person>
let x = {
Name = "foo"
Id = 1
Extended = {
FirstName = "David"
LastName = "Raab"
}
}
// State<Computer>
let y = {
Name = "bar"
Id = 2
Extended = {
CPU = "AMD"
Ghz = 3.6
}
}
You also could convert from one record to another. Your state machine, only accepts a record with those data it needs. For exmaple, you have a state machine that only needs Name and Id. You create.
type StateA = {
Name: string
Id: int
}
Assume you now have a different state with shared keys.
type StateB = {
Name: string
Id: int
Extra: float
}
Instead of type hackery, generics and so on, the easiest way is just to convert from B to A.
let stateBtoA (record:StateB) : StateA = {
Name = record.Name
Id = record.Id
}
So your state machine only accepts StateA, and you have a bunch of function, and other types must be converted to StateA before passing it to the state machine.
If for some reason your state machine, needs access to the orignal data, you should be able to pass it as a generic paramter to a function call. Like
message (x:StateA) (y:'a) : 'a =
// do something with x
y
message (stateBtoA data) data
So your function could work on the first argument, that is of StateA, and for example returns the second argument y that could be StateB. As long your function does nothing with y. It should be generic.
I have two record types:
type Employee = {
Id: string
Name: string
Phone: string
}
type AuditLog = {
PerformedBy: string
PerformedOn: string
}
Following are instances of the record types:
let emp = {
Id = "123"
Name = "Abc"
Phone = "999"
}
let log = {
PerformedBy = "234"
PerformedOn = "1/1/1"
}
Is there any way to combine the fields of these two instances to create a new record/anonymous record type like the following?
let combined = {
Id = "123"
Name = "Abc"
Phone = "999"
PerformedBy = "234"
PerformedOn = "1/1/1"
}
In F# record types are not inheritable or combineable in other ways.
The only thing you can do to get a type with the combined fields is
to explicitly create a new record that has no relation to the existing 2 types
create such type anonymously, #JL0PD pointed to the docs for anonmyous types. Anonymous types can be very helpful in some situations, but explicit types are the better choice - make code more readable - in most situations.
create a record that has 2 fields with the 2 types, which is not really what you are looking for.
Some languages like Typescript have intersection types where you can define a type as having the fields of a set of other types (since the fields of the created type are the union of the combined types, "intersection" sounds strange for my taste). I guess you are looking for that, but that is not available in F# and most languages.
A very basic example:
type private test =
{
a : string
b : string list
}
let t = { a = "hello"; b = ["1"; "2"] }
let s = JsonConvert.SerializeObject(t)
This will produce an empty string.
I have seen that json.net supports F# and that there are a lot of posts related to enum types, etc but I'm not there yet: I'm trying to serialize something very simple.
Many posts point toward another json serializer project, called Chiron, but it was updated a year ago and they're still like:
We’re working on Guides and reference content for working with Chiron, so keep an eye on the Updates
Is there something obvious I haven't seen?
So ideally, working with json.net would be better, especially since I'm used to it in C#
The issue seems to be that Json.Net only serializes public fields of F# records. When you mark the record as private, all its fields also become private and those are ignored. The following works as expected for me:
type test =
{
a : string
b : string list
}
let t = { a = "hello"; b = ["1"; "2"] }
let s = JsonConvert.SerializeObject(t)
This produces the expected JSON:
{"a":"hello","b":["1","2"]}
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! :-)
Lets begin with the Class approach:
class LoginCredentials {
var id : String
init(userID:String) {
self.id = userID
}
}
then we will have the following:
class FacebookLoginCredentials : LoginCredentials {
var token : String
init(userID:String,userToken:String) {
self.token = userToken
super.init(userID: userID)
}}
And
class TwitterLoginCredentials : LoginCredentials {
var token : String
var secret : String
init(userID:String,userToken:String,secret : String) {
self.token = userToken
self.secret = secret
super.init(userID: userID)
}
}
The second Approach is the Protocol Oriented if I am not wrong
protocol LoginCredentials {
var id : String { get }
}
then we will have :
struct FacebookLoginCredentials : LoginCredentials {
var id: String
var token : String
init(userID:String,userToken:String) {
self.id = userID
self.token = userToken
}
}
And
struct TwitterLoginProfile : LoginCredentials {
var id: String
var token : String
var secret : String
init(userID:String,userToken:String,secret : String) {
self.token = userToken
self.secret = secret
self.id = userID
}
}
I just need to know which one is more Swift ?
Ultimately, neither of these approaches is "more Swift". In Swift, you will sometimes want to use inheritance and other times you will want to use protocols. The real decision point for these two approaches is:
Do you want value type semantics (structs and protocols) or do you want reference type semantics (classes and protocols). I usually default to value type semantics because they are safer but there are definitely circumstances where reference type semantics are important. You can read more about that here: Why Choose Struct over Class.
Either or is acceptable in swift.
Here is how you want to distinguish from the two.
When working with Protocols, you want to treat these as if they were blue prints for your objects.
Ballplayers must know what a ball is, so any person that wants to be a Ballplayer must know what a ball is.
You have a set of rules you want certain objects to follow, make a protocol.
If you want to make objects that have functionality, and you want the children to inherent this functionality, and then have more functionality, then do the inheret class structure.
Dad can throw a ball at 100MPH
Junior can throw a ball at 100MPH and throw a curve ball.
This would be done with a class, not protocol
Structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure.
As a general guideline, consider creating a structure when one or more of these conditions apply:
The structure’s primary purpose is to encapsulate a few relatively simple data values.
It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.
Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
The structure does not need to inherit properties or behavior from another existing type.
Examples of good candidates for structures include:
The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.
In all other cases, define a class, and create instances of that class
to be managed and passed by reference. In practice, this means that
most custom data constructs should be classes, not structures.
Why Choose Struct Over Class?