I'd like some way to define related records. For example,
type Thing = { field1: string; field2: float }
type ThingRecord = { field1: string; field2: float; id: int; created: DateTime }
or
type UserProfile = { username: string; name: string; address: string }
type NewUserReq = { username: string; name: string; address: string; password: string }
type UserRecord = { username: string; name: string; address: string; encryptedPwd: string; salt: string }
along with a way to convert from one to the other, without needing to write so much boilerplate. Even the first example in full would be:
type Thing =
{ field1: string
field2: float }
with
member this.toThingRecord(id, created) =
{ field1 = this.field1
field2 = this.field2
id = id
created = created } : ThingRecord
and ThingRecord =
{ field1: string
field2: float
id: int
created: DateTime }
with
member this.toThing() =
{ field1 = this.field1
field2 = this.field2 } : Thing
As you get up to field10 etc, it gets to be a liability.
I currently do this in an unsafe (and slow) manner using reflection.
I added a request for with syntax to be extended to record definitions on uservoice, which would fill this need.
But is there perhaps an typesafe way to do this already? Maybe with type providers?
Yes, that's a chink in F#'s otherwise shiny armor. I don't feel there's a universal solution there for easily inheriting or extending a record. No doubt there is an appetite for one - I've counted over a dozen uservoice submissions advocating improvements along these lines - here are a few leading ones, feel free to vote up: 1, 2, 3, 4, 5.
For sure, there are things you can do to work around the problem, and depending on your scenario they might work great for you. But ultimately - they're workarounds and there's something you have to sacrifice:
Speed and type safety when using reflection,
Brevity when you go the type safe way and have full-fledged records with conversion functions between them,
All the syntactic and semantic goodness that records give you for free when you decide to fall back to plain .NET classes and inheritance.
Type providers won't cut it because they're not really a good tool for metaprogramming. That's not what they were designed for. If you try to use them that way, you're bound to hit some limitation.
For one, you can only provide types based on external information. This means that while you could have a type provider that would pull in types from a .NET assembly via reflection and provide some derived types based on that, you can't "introspect" into the assembly you're building. So no way of deriving from a type defined earlier in the same assembly.
I guess you could work around that by structuring your projects around the type provider, but that sounds clunky. And even then, you can't provide record types anyway yet, so best you could do are plain .NET classes.
For a more specific use case of providing some kind of ORM mapping for a database - I imagine you could use type providers just fine. Just not as a generic metaprogramming facility.
Why don't you make them more nested, like the following?
type Thing = { Field1: string; Field2: float }
type ThingRecord = { Thing : Thing; Id: int; Created: DateTime }
or
type UserProfile = { Username: string; Name: string; Address: string }
type NewUserReq = { UserProfile: UserProfile; Password: string }
type UserRecord = { UserProfile: UserProfile; EncryptedPwd: string; Salt: string }
Conversion functions are trivial:
let toThingRecord id created thing = { Thing = thing; Id = id; Created = created }
let toThing thingRecord = thingRecord.Thing
Usage:
> let tr = { Field1 = "Foo"; Field2 = 42. } |> toThingRecord 1337 (DateTime (2016, 6, 24));;
val tr : ThingRecord = {Thing = {Field1 = "Foo";
Field2 = 42.0;};
Id = 1337;
Created = 24.06.2016 00:00:00;}
> tr |> toThing;;
val it : Thing = {Field1 = "Foo";
Field2 = 42.0;}
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"]}
My intent is to define a module with functions which can operate on all records types which comply with certain assumptions about the keys.
To illustrate, let us have the following code:
> type DBRow = { id: string ; createdAt: System.DateTime } ;;
type DBRow =
{id: string;
createdAt: System.DateTime;}
> let logCreationInfo row = printf "Record %s created at %s " row.id (row.createdAt.ToString()) ;;
val logCreationInfo : row:DBRow -> unit
I would like to change the above logCreationInfo to be able to operate on all records which have id: string and createdAt: System.DateTime (and maybe other things).
Coming from typescript's structural typing, I'd have expected this to be trivial, but I am exploring the possibility that there is a more idiomatic way to handle this in F#.
I had attempted to handle this using interfaces, but even if that could work, since F# supports only explicit interfaces, this will not be suitable for types I don't define myself.
You could use statically resolved type constraints.
let inline logCreationInfo (x : ^t) =
printfn "Record %s created at %s"
(^t : (member id : string) (x))
((^t : (member createdAt : System.DateTime) (x)).ToString())
F# largely uses nominative typing - this is a natural choice in its runtime environment, as this is what Common Type System specification prescribes. Adherence to that set of rules allows F# code to near-seamlessly interoperate with other .NET languages.
It's worth noting that this follows the same reasoning as to why TypeScript uses structural typing. Since that language builds up on top of dynamically typed JavaScript, it's more natural to express object relationships in terms of their structure rather than nominal types - which are a foreign concept in JS.
F# does have a "backdoor" for structural typing through already mentioned SRTPs, but I would suggest using it very sparingly. SRTPs are resolved and the code using them is inlined by the compiler, making for longer compilation times and reduced interoperability with other languages and the .NET platform in general (simply put, you can't refer to that code from other languages or using reflection API, because it's "compiled away").
Usually there are other solutions available. Interfaces were already mentioned, though the example used was a bit contrived - this is simpler:
type IDBRow =
abstract Id: string
abstract CreatedAt: System.DateTime
type Person =
{
id: string
name: string
age: int
createdAt: System.DateTime
}
interface IDBRow with
member this.Id = this.id
member this.CreatedAt = this.createdAt
let logCreationInfo (row: #IDBRow) =
printf "Record %s created at %s" row.Id (string row.CreatedAt)
let x = { id = "1"; name = "Bob"; age = 32; createdAt = DateTime.Now }
logCreationInfo x
Or using composition and a generic type to capture the generic part of what it means to be a DBRow:
type DBRow<'data> =
{
id: string
data: 'data
createdAt: System.DateTime
}
type Person =
{
name: string
age: int
}
let logCreationInfo (row: DBRow<_>) =
printf "Record %s created at %s" row.id (string row.createdAt)
let x = { id = "1"; data = { name = "Bob"; age = 32 }; createdAt = DateTime.Now }
logCreationInfo x
Here's a version with interfaces:
open System
type DBRow1 = {
id: string
createdAt: DateTime
}
type DBRow2 = {
id: string
createdAt: DateTime
address: string
}
/// The types are defined above without an interface
let row1 = {id = "Row1"; createdAt = DateTime.Now}
let row2 = {id = "Row2"; createdAt = DateTime.Now; address = "NYC"}
type IDBRow<'A> =
abstract member Data:(string * DateTime)
// Object expression implements the interface
let Data1 (x:DBRow1) = {
new IDBRow<_> with
member __.Data = (x.id, x.createdAt)
}
let Data2 (x: DBRow2) = {
new IDBRow<_> with
member __.Data = (x.id, x.createdAt)
}
//pass in both the object expression and the record
let getData (ifun: 'a -> IDBRow<'b>) xrec =
(ifun xrec).Data
// You could partially apply the functions: `getData1 = getData Data1`
getData Data1 row1 //("Row1", 2018/02/05 9:24:17)
getData Data2 row2 //("Row2", 2018/02/05 9:24:17)
You can certainly use an interface (an object expression in this case) to tack on another member, .Data, even if you don'T have access to the original type. You would still need to put together one object expression for each type though, so SRTP might be a more "elegant" solution.
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