What is the appropriate way to create generic SELECT * FROM table WHERE id = #id; with type constraint using Dapper.FSharp?
Here is my snippet. With (p.Id = id) it won't compile because of type constraint mismatch and with (checkId id p) it obviously fails
#r "nuget: Dapper.Fsharp"
#r "nuget: Npgsql.Fsharp"
open Npgsql
open Npgsql.FSharp
open Dapper.FSharp.PostgreSQL
module Example =
type Id = Guid
type A = {
Id : Id
DoubleValue : double
}
type B = {
Id : Id
IntValue: int
}
let inline checkId (id: ^id when ^id : equality ) a =
( ^a:( member Id : ^id ) a) = id
let inline getById< ^a,^id when ^a:( member Id : ^id ) and ^id : equality > (con:NpgsqlConnection) (table: QuerySource< ^a > ) (id: ^id ) =
select {
for p in table do
//where (checkId id p) // <- this will compile, but raises NotImplementedException
where (p.Id = id) // <- this won't compile with "Type constraint mismatch. The type ''a' is not compatible with type 'B'"
take 1
}
|> con.SelectAsync< ^a >
|> Async.AwaitTask
I ended up using this as a workaround
#r "nuget: Dapper.Fsharp"
#r "nuget: Npgsql.Fsharp"
open Npgsql
open Dapper
type Id = Guid
module Example =
[<CLIMutable>]
type A = {
Id : Id
DoubleValue : double
}
[<CLIMutable>]
type B = {
Id : Id
IntValue: int
}
let toTableName<'a> =
failwith "todo"
let getById<'a> (con:NpgsqlConnection) (id: Id ) =
con.QuerySingleAsync<'a>($"""SELECT * FROM "{toTableName<'a>}" WHERE "Id" = #id;""", {| id = id |})
|> Async.AwaitTask
Related
I am trying to convert a C# class into F#:
type Aggregator<'T when 'T : (new : unit -> 'T)>()=
static let ApplyMethod = "Apply"
member val private _aggregations : IDictionary<Type, obj> = new Dictionary<Type, obj>()
member val AggregateType = typeof<'T> with get
member val Alias = Unchecked.defaultof<string> with get
However it seems that even this simple code cannot compile:
Program.fs:1189 This expression was expected to have type
'IDictionary<Type,obj>'
but here has type
'Dictionary<Type,obj>'
Does it mean that a field declared with an interface IDictionary<Type, obj> type cannot infer the value passed knowing that this particular value implements that interface Dictionary<Type, obj>?
Actually if I am explicitely upcasting to IDictionary<Type, obj>:
member val private _aggregations : IDictionary<Type, obj> =
(new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
This works, does it mean that F# is stricter than C# in that regard?
As pointed in the comment, F# does require an explicit:
type Aggregator<'T when 'T : (new : unit -> 'T)>()=
static let ApplyMethod = "Apply"
member val private _aggregations : IDictionary<Type, obj> = new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
member val AggregateType = typeof<'T> with get
member val Alias = Unchecked.defaultof<string> with get
Side notes:
Btw, not everything what you can do in C# is possible in F# (no protected access modifier for example).
Result of the conversion:
type Aggregator<'T when 'T : (new : unit -> 'T) and 'T : not struct> (overrideMethodLookup : IEnumerable<MethodInfo>)=
let aggregations : IDictionary<Type, obj> = (new Dictionary<Type, obj>() :> IDictionary<Type, obj>)
let aggregateType = typeof<'T>
let mutable alias = Unchecked.defaultof<string>
do
alias <- typeof<'T>.Name.ToTableAlias();
overrideMethodLookup.Each(fun (method : MethodInfo) ->
let mutable step = Unchecked.defaultof<obj>
let mutable eventType = method.GetParameters().Single<ParameterInfo>().ParameterType;
if eventType.Closes(typedefof<Event<_>>) then
eventType <- eventType.GetGenericArguments().Single();
step <- typedefof<EventAggregationStep<_,_>>.CloseAndBuildAs<obj>(method, [| typeof<'T>; eventType |]);
else
step <- typedefof<AggregationStep<_,_>>.CloseAndBuildAs<obj>(method, [| typeof<'T>; eventType |]);
aggregations.Add(eventType, step)
) |> ignore
static let ApplyMethod = "Apply"
new() = new Aggregator<'T>(typeof<'T>.GetMethods()
|> Seq.where (fun x -> x.Name = ApplyMethod &&
x.GetParameters().Length = 1))
member this.Add<'TEvent>(aggregation: IAggregation<'T, 'TEvent>) =
if aggregations.ContainsKey(typeof<'TEvent>) then
aggregations.[typeof<'TEvent>] <- aggregation
else
aggregations.Add(typeof<'TEvent>, aggregation)
this
member this.Add<'TEvent>(application: Action<'T, 'TEvent>) =
this.Add(new AggregationStep<'T, 'TEvent>(application));
interface IAggregator<'T> with
member this.AggregatorFor<'TEvent>() =
if aggregations.ContainsKey(typeof<'TEvent>) then
aggregations.[typeof<'TEvent>].As<IAggregation<'T, 'TEvent>>()
else
null
member this.Build(events, session, state) =
events.Each(fun (x : IEvent) -> x.Apply(state, this)) |> ignore
state
member this.Build(events, session) =
(this :> IAggregator<'T>).Build(events, session, new 'T());
member this.EventTypes =
aggregations.Keys.ToArray();
member this.AggregateType =
aggregateType
member this.Alias =
alias
member this.AppliesTo(stream) =
stream.Events.Any(fun x -> aggregations.ContainsKey(x.Data.GetType()));
The following code is not casting my return value of SqlDataReader from getReader correctly to IDataReaderin the call to Seq.unfold. What am I doing wrong?
open System.Data
open System.Data.SqlClient
open System.Configuration
type Foo = { id:int; name:string }
let populateFoo (r:IDataReader) =
let o = r.GetOrdinal
{ id = o "id" |> r.GetInt32; name = o "name" |> r.GetString; }
let iter populateObject (r:IDataReader) =
match r.Read() with
| true -> Some(populateObject r, r)
| _ -> None
let iterFoo = iter populateFoo
let getReader : IDataReader =
let cnstr = ConfigurationManager.ConnectionStrings.["db"].ConnectionString
let cn = new SqlConnection(cnstr)
let cmd = new SqlCommand("select * from Foo", cn)
cmd.ExecuteReader()
let foos = Seq.unfold iterFoo getReader
F# does not automatic upcasting like C#, except in some specific scenarios (see the spec, section 14.4.2).
You have to explicitly cast the expression: cmd.ExecuteReader() :> IDataReader then you can remove the type annotation after getReader.
Alternatively you may leave that function returning an SqlDataReader and upcast at the call site:
let foos = getReader :> IDataReader |> Seq.unfold iterFoo
If unfold was a static member of a type with a signature like this one:
type T() =
static member unfold(a, b:IDataReader) = Seq.unfold a b
you would be able to do directly T.unfold(iterFoo, getReader) and it will automatically upcast. That's one of the cases mentioned in the spec.
I see that you can enforce constructor usage of single-case discriminated unions, can you do the same with multi-case?
for example
type MemberId =
| MemberId of int
| MemberGuid of Guid
I'm currently trying in the fsi like this
val create : int -> T option
val create : Guid -> T option
but I'm guessing like C#, F# won't allow you to overload based on return type for the unwrap:
val value : T -> string
Edit ---------------
MemberId.fsi =
module MemberId
open System
type _T
val createId : int -> _T option
val createGuid : Guid -> _T option
val value : _T -> 'a
MemberId.fs =
module MemberId
open System
type _T =
| Id of int
| MemberGuid of Guid
let createId id = match id with
| x when x>0 -> Some(Id(id))
| _ -> None
let createGuid guid = Some(MemberGuid( guid))
let value (e:_T):int = e
Appears to be pretty close, but the unwrapper doesn't compile and I can't seem to figure out how to write it
TestConsumer MemberIdClient.fs =
module MemberIdClient
open System
open MemberId
let address1 = MemberId.create(-1)
let address2 = MemberId.create(Guid.Empty)
let unwrapped1 =
match address1 with
| MemberId x -> () // compilation error on 'MemberId x'
| _ -> ()
Functions cannot be overloaded, but methods can:
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
static member create id = MemberId id
static member create guid = MemberGuid guid
Indeed there is a way to overload with the output parameter, using some inline tricks:
open System
type MemberId =
private
| MemberId of int
| MemberGuid of Guid
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
type Value = Value with
static member ($) (Value, d:int ) = function MemberId id -> id | _ -> failwith "Wrong case"
static member ($) (Value, d:Guid) = function MemberGuid guid -> guid | _ -> failwith "Wrong case"
let inline create x : MemberId = Create $ x
let inline value x : 'IntOrGuid = (Value $ Unchecked.defaultof<'IntOrGuid>) x
let a = create 1
let b = create (Guid.NewGuid())
let c:int = value a
let d:Guid = value b
By doing this you can 'overload' functions, even on output parameters.
Anyway the big difference with the single case DU is that now the unwrapper is not 'safe', that's why the unwrapper makes little sense, except in some specif scenarios.
In these cases you may consider other mechanisms to unwrap the values, like exposing functions isX or returning options which may be complemented with an active pattern to unwrap.
Having said that, if you are only interested in 'hiding' the constructors to do some validations, but not hiding the DU you can simply shadow the constructors, here's an example:
open System
type T =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)
let a = MemberId 1
let b = MemberGuid (Guid.NewGuid())
let c = MemberId -1
// but you can still pattern match
let printValue = function
| Some (MemberId x) -> sprintf "case 1, value is %A" x
| Some (MemberGuid x) -> sprintf "case 2, value is %A" x
| None -> "No value"
let ra = printValue a // "case 1, value is 1"
let rb = printValue b // "case 2, value is 67b36c20-2..."
let rc = printValue c // "No value"
// and if you want to use an overloaded constructor
type T with
static member Create id = MemberId id
static member Create guid = MemberGuid guid
let d = T.Create 1
let e = T.Create (Guid.NewGuid())
// or using the inline trick
type Create = Create with
static member ($) (Create, id ) = MemberId id
static member ($) (Create, guid) = MemberGuid guid
let inline create x : T option = Create $ x
let d' = create 1
let e' = create (Guid.NewGuid())
Here's the little bit of code from Gustavo's answer I needed that appears to work all by itself
module MemberId
open System
type MemberId =
| MemberId of int
| MemberGuid of Guid
// Shadow constructors
let MemberId x = if x > 0 then Some (MemberId x) else None
let MemberGuid x = Some (MemberGuid x)
I have the following ViewModelBase in F# which I'm trying to build to learn F# with WPF.
module MVVM
open System
open System.Collections.ObjectModel
open System.ComponentModel
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open System.Reactive.Linq
module Property =
let ToName(query : Expr) =
match query with
| PropertyGet(a, b, list) ->
b.Name
| _ -> ""
let SetValue<'t>(obj, query : Expr<'t>, value : 't) =
match query with
| PropertyGet(a, b, list) ->
b.SetValue(obj, value)
| _ -> ()
let GetValue<'o, 't>(obj : 'o , query : Expr<'t>) : option<'t> =
match query with
| PropertyGet(a, b, list) ->
option.Some(b.GetValue(obj) :?> 't )
| _ -> option.None
let Observe<'t>(x: INotifyPropertyChanged) (p : Expr<'t>) =
let name = ToName(p)
x.PropertyChanged.
Where(fun (v:PropertyChangedEventArgs) -> v.PropertyName = name).
Select(fun v -> GetValue(x, p).Value)
type ViewModelBase() =
let propertyChanged = new Event<_, _>()
interface INotifyPropertyChanged with
[<CLIEvent>]
member x.PropertyChanged = propertyChanged.Publish
abstract member OnPropertyChanged: string -> unit
default x.OnPropertyChanged(propertyName : string) =
propertyChanged.Trigger(x, new PropertyChangedEventArgs(propertyName))
member x.SetValue<'t>(expr : Expr<'t>, v : 't) =
Property.SetValue(x, expr, v)
x.OnPropertyChanged(expr)
member x.OnPropertyChanged<'t>(expr : Expr<'t>) =
let propName = Property.ToName(expr)
x.OnPropertyChanged(propName)
However I get an error from the compiler
Error 1 The type 'ViewModelBase' is used in an invalid way.
A value prior to 'ViewModelBase' has an inferred type involving
'ViewModelBase', which is an invalid forward reference.
However the compiler doesn't tell me what value prior is the offending part of the problem. As I'm pretty new to the type inference as used by F# I'm probably missing an obvious problem.
FYI the code is meant to be used like the below but at the moment this code is commented out and the error is only pertaining to the core code above
type TestModel() as this =
inherit MVVM.ViewModelBase()
let mutable name = "hello"
let subscription = (Property.Observe this <# this.SelectedItem #>).
Subscribe(fun v -> Console.WriteLine "Yo")
member x.SelectedItem
with get() = name
and set(v) =
x.SetValue(<# x.SelectedItem #>, v)
I found it.
let SetValue<'t>(obj, query : Expr<'t>, value : 't) =
match query with
| PropertyGet(a, b, list) ->
b.SetValue(obj, value)
| _ -> ()
was under constrained. Should be
let SetValue<'t>(obj : Object, query : Expr<'t>, value : 't) =
match query with
| PropertyGet(a, b, list) ->
b.SetValue(obj, value)
| _ -> ()
I need a function to convert types from a third-party library to IDictionarys so they can be easily serialized (to JSON). There are dependencies between the types so the dictionaries are sometimes nested.
Right now I have something hideous like this:
//Example type
type A(name) =
member __.Name = name
//Example type
type B(name, alist) =
member __.Name = name
member __.AList : A list = alist
let rec ToSerializable x =
match box x with
| :? A as a -> dict ["Name", box a.Name]
| :? B as b -> dict ["Name", box b.Name; "AList", box (List.map ToSerializable b.AList)]
| _ -> failwith "wrong type"
This would convert everything to a primitive type, an IEnumerable of such a type, or a dictionary.
This function will keep growing as types are added (ugh). It's not type-safe (requiring the catch-all pattern). Figuring out which types are supported requires perusing the monolithic pattern match.
I'd love to be able to do this:
type ThirdPartyType with
member x.ToSerializable() = ...
let inline toSerializable x =
(^T : (member ToSerializable : unit -> IDictionary<string,obj>) x)
let x = ThirdPartyType() |> toSerializable //type extensions don't satisfy static member constraints
So, I'm looking for creativity here. Is there a better way to write this that addresses my complaints?
Here's one possible solution that addresses the type-safety question, though not necessarily your extensibility question:
// these types can appear in any assemblies
type A = { Name : string }
type B = { Name : string; AList : A list }
type C(name:string) =
member x.Name = name
static member Serialize(c:C) = dict ["Name", box c.Name]
// all of the following code goes in one place
open System.Collections.Generic
type SerializationFunctions = class end
let inline serializationHelper< ^s, ^t when (^s or ^t) : (static member Serialize : ^t -> IDictionary<string,obj>)> t =
((^s or ^t) : (static member Serialize : ^t -> IDictionary<string,obj>) t)
let inline serialize t = serializationHelper<SerializationFunctions,_> t
// overloads for each type that doesn't define its own Serialize method
type SerializationFunctions with
static member Serialize (a:A) = dict ["Name", box a.Name]
static member Serialize (b:B) = dict ["Name", box b.Name; "AList", box (List.map serialize b.AList)]
let d1 = serialize { A.Name = "a" }
let d2 = serialize { B.Name = "b"; AList = [{ A.Name = "a" }]}
let d3 = serialize (C "test")
as quick-n-obvious idea: use overloads
//Example type
type A(name) =
member __.Name = name
//Example type
type B(name, alist) =
member __.Name = name
member __.AList : A list = alist
type Converter =
static member ToSerializable(a : A) = dict ["Name", box a.Name]
static member ToSerializable(b : B) = dict ["Name", box b.Name; "AList", box (b.AList |> List.map Converter.ToSerializable)]