I'm wondering why the |, "or" pattern in a SRTP context in my function doThingsWithOrProps isn't working as expected below, aka it should be able accept a type that has either a property PropA or another property PropB, but it is interpreted as an AND & pattern instead like for the function doThingsWithAndProps which doesn't really make sense since the pattern expected for the input of the respective two functions is clearly different.
let inline (|PropA|) source =
(^Source: (member PropA: 'PropA) source)
let inline (|PropB|) source =
(^Source: (member PropB: 'PropB) source)
let inline (|PropAAndB|) (PropA (propA: 'PropA) & PropB (propB: 'PropB)) = (propA, propB)
let inline (|PropAOrB|) (PropA p | PropB p) = p
let inline doThingsWithAndProps (PropAAndB (propA: 'PropA, propB: 'PropB)) =
printfn $"({nameof propA} = %A{propA}, {nameof propB} = %A{propB})"
let inline doThingsWithOrProps (PropAOrB propAOrB) =
printfn $"{nameof propAOrB} = %A{propAOrB}"
// Compiles just fine
doThingsWithAndProps {| PropA = "hello"; PropB = "world" |}
// The type '{| PropA: 'a |}' does not support the operator 'get_PropB'
doThingsWithOrProps {| PropA = "wer" |}
// The type '{| PropB: 'a |}' does not support the operator 'get_PropA'
doThingsWithOrProps {| PropB = "wer" |}
===
Additional bits using https://sharplab.io to convert valid parts of the F# code above to C# (skimming some part parts about equality, hashcode, to making a little terser)
let inline (|PropA|) source =
(^Source: (member PropA: 'PropA) source)
let inline (|PropB|) source =
(^Source: (member PropB: 'PropB) source)
let inline (|PropAAndB|) (PropA (propA: 'PropA) & PropB (propB: 'PropB)) = (propA, propB)
let inline (|PropAOrB|) (PropA p | PropB p) = p
let inline doThingsWithAndProps (PropAAndB (propA: 'PropA, propB: 'PropB)) =
printfn $"({nameof propA} = %A{propA}, {nameof propB} = %A{propB})"
let inline doThingsWithOrProps (PropAOrB propAOrB) =
printfn $"{nameof propAOrB} = %A{propAOrB}"
// Compiles just fine
doThingsWithAndProps {| PropA = "hello"; PropB = "world" |}
[assembly: FSharpInterfaceDataVersion(2, 0, 0)]
[assembly: AssemblyVersion("0.0.0.0")]
[CompilationMapping(SourceConstructFlags.Module)]
public static class #_
{
[SpecialName]
public static Tuple<PropA, PropB> |PropAAndB|$W<a, PropB, PropA>(FSharpFunc<a, PropA> get_PropA, FSharpFunc<a, PropB> get_PropB, a _arg1)
{
PropA item = get_PropA.Invoke(_arg1);
PropB item2 = get_PropB.Invoke(_arg1);
return new Tuple<PropA, PropB>(item, item2);
}
[SpecialName]
public static b |PropAOrB|<a, b>(a _arg1)
{
if (false)
{
return (b)(object)null;
}
throw new NotSupportedException("Dynamic invocation of get_PropA is not supported");
}
[SpecialName]
public static b |PropAOrB|$W<a, b>(FSharpFunc<a, b> get_PropA, FSharpFunc<a, b> get_PropB, a _arg1)
{
return get_PropA.Invoke(_arg1);
}
public static void doThingsWithAndProps<a, PropB, PropA>(a _arg1)
{
if (0 == 0)
{
throw new NotSupportedException("Dynamic invocation of get_PropA is not supported");
}
PropA val = (PropA)(object)null;
if (0 == 0)
{
throw new NotSupportedException("Dynamic invocation of get_PropB is not supported");
}
PropB val2 = (PropB)(object)null;
object[] array = new object[4];
array[0] = "propA";
array[1] = val;
array[2] = "propB";
array[3] = val2;
Type[] array2 = new Type[2];
array2[0] = typeof(PropA);
array2[1] = typeof(PropB);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, PropA, string, PropB>>("(%P() = %A%P(), %P() = %A%P())", array, array2));
}
public static void doThingsWithAndProps$W<a, PropB, PropA>(FSharpFunc<a, PropA> get_PropA, FSharpFunc<a, PropB> get_PropB, a _arg1)
{
PropA val = get_PropA.Invoke(_arg1);
PropB val2 = get_PropB.Invoke(_arg1);
object[] array = new object[4];
array[0] = "propA";
array[1] = val;
array[2] = "propB";
array[3] = val2;
Type[] array2 = new Type[2];
array2[0] = typeof(PropA);
array2[1] = typeof(PropB);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, PropA, string, PropB>>("(%P() = %A%P(), %P() = %A%P())", array, array2));
}
public static void doThingsWithOrProps<a, b>(a _arg1)
{
if (0 == 0)
{
throw new NotSupportedException("Dynamic invocation of get_PropA is not supported");
}
b val = (b)(object)null;
object[] array = new object[2];
array[0] = "propAOrB";
array[1] = val;
Type[] array2 = new Type[1];
array2[0] = typeof(b);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, b>>("%P() = %A%P()", array, array2));
}
public static void doThingsWithOrProps$W<a, b>(FSharpFunc<a, b> get_PropA, FSharpFunc<a, b> get_PropB, a _arg1)
{
b val = get_PropA.Invoke(_arg1);
object[] array = new object[2];
array[0] = "propAOrB";
array[1] = val;
Type[] array2 = new Type[1];
array2[0] = typeof(b);
ExtraTopLevelOperators.PrintFormatLine(new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, b>>("%P() = %A%P()", array, array2));
}
}
namespace <StartupCode$_>
{
internal static class $_
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly <>f__AnonymousType1562431155<string, string> _arg1#11;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly string activePatternResult1923786_0#18;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly string activePatternResult1923786_1#18;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal static readonly PrintfFormat<Unit, TextWriter, Unit, Unit> format#1;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
[DebuggerNonUserCode]
internal static int init#;
static $_()
{
_arg1#11 = new <>f__AnonymousType1562431155<string, string>("hello", "world");
activePatternResult1923786_0#18 = #_._arg1#11.PropA;
activePatternResult1923786_1#18 = #_._arg1#11.PropB;
object[] array = new object[4];
array[0] = "propA";
array[1] = #_.activePatternResult1923786_0#18;
array[2] = "propB";
array[3] = #_.activePatternResult1923786_1#18;
Type[] array2 = new Type[2];
array2[0] = typeof(string);
array2[1] = typeof(string);
format#1 = new PrintfFormat<Unit, TextWriter, Unit, Unit, Tuple<string, string, string, string>>("(%P() = %A%P(), %P() = %A%P())", array, array2);
PrintfModule.PrintFormatLineToTextWriter(Console.Out, #_.format#1);
}
}
}
You're confusing runtime with compile time.
The SRTP constraints work at compile time. The compiler has to make sure that any values that could possibly be passed to your function when runtime comes around will definitely satisfy the SRTP constraint.
Active matchers, on the other hand, work at runtime. It's not known ahead of time what the value passed to the active matcher will be. The active matcher gets the value, looks at it, and determines how it should be classified. That's the whole point of an active matcher: it classifies a value by some classification that's not known in advance.
So when you make an active matcher like PropAOrB, the compiler sees that, when a value is passed to it at runtime, your matcher will have to first call PropA to see if it matches the value, and if it doesn't - call PropB and see if it matches. Which means that, potentially, both PropA and PropB will be called, and therefore, the static (i.e. known in advance) type of the values that can be passed to PropAOrB must satisfy both PropA's and PropB's STRP constraints.
If you wanted to let PropAOrB accept either values that have only PropA or values that have only PropB or values that have both, one option would be to match at runtime using reflection:
let inline (|PropA|_|) source =
let prop = source.GetType().GetProperty("PropA")
if prop = null then None else Some (PropA (prop.GetValue(source)))
let inline (|PropB|_|) source =
let prop = source.GetType().GetProperty("PropB")
if prop = null then None else Some (PropB (prop.GetValue(source)))
let inline (|PropAOrB|) (PropA p | PropB p) = p
let inline doThingsWithOrProps (PropAOrB propAOrB) =
printfn $"{nameof propAOrB} = %A{propAOrB}"
doThingsWithOrProps {| PropA = "wer" |}
doThingsWithOrProps {| PropB = "wes" |}
But of course, now both PropA and PropB return object (because that's what PropertyInfo.GetValue returns), so you'd have to know the type and cast it I guess.
Plus, you'd have to deal with incomplete pattern matches, because there is a third unhandled possibility: the value has neither PropA nor PropB.
Related
How do I type let's say a function that takes a record that has a field a and any other field?
Is some equivalent of this Standard ML function possible in F#?
fun f {a: string, ...} = "Hello" ^ a;
Tried the following but these don't seem to be syntactically valid:
let f (r: {|a: string; _|}) = impl;
let g (r: {|a: string; ...|}) = impl;
You can achieve this kind of constraint with Statically Resolved Type Parameters like so:
let inline f< ^T when ^T: (member a: string)> (r: ^T) = ()
f {| a = "yeet" |} // compiles
f {| a = "yeet"; b = "yote" |} // compiles
f {| b = "yote" |} // error
Note that this isn't just for anonymous records. It will hold true for any type that has a member with the specified signature.
I also like to hide these things behind a module and extract the nasty SRTP stuff into an active pattern like so:
module M =
let inline private (|HasName|) x = (^a : (member Name: string) x)
let inline printName (HasName name) = printfn $"{name}"
type Person1 = { Name: string; Age: int }
type Person2 = { Name: string; Age: int; IsFunny: bool }
type Name(name) =
member _.Name = name
let p1 = { Name = "Phillip"; Age = 30 }
let p2 = { Name = "Phillip"; Age = 30; IsFunny = false }
let nm = Name "Phillip"
M.printName p1
M.printName p2
M.printName nm
This has the benefit of hiding the details of how you get the constraint "lined up correctly" and lets you easily re-use the signature in other things you want to publicly expose.
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'm a newbie F# programmer, and I'm having some trouble getting the syntax for my F# program correct.
Essentially, I want to turn this C# code into F#:
class MyRiskyObject : BaseObject
{
private string field;
public MyRiskyObject(object foo, string data)
: base(foo)
{
try
{
this.data = RiskyOperation(data);
}
catch (ArgumentException)
{
DoSomethingElse();
}
}
}
I, so far, have something like
type MyRiskyObject
inherit BaseObject
val field : string
new (foo:object, data:string) = {
inherit BaseObject(foo)
try
field = RiskyOperation()
????????
}
I just can't get the syntax right...
Edit:
here is the actual code I'm working on:
type RegExpObject =
inherit CommonObject
val RegExp : Regex
val Global : bool
member x.IgnoreCase:bool =
(x.RegExp.Options &&& RegexOptions.IgnoreCase) = RegexOptions.IgnoreCase
member x.MultiLine:bool =
(x.RegExp.Options &&& RegexOptions.Multiline) = RegexOptions.Multiline
new (env, pattern, options, global') =
{
inherit CommonObject(env, env.Maps.RegExp, env.Prototypes.RegExp)
// Here, I need to catch the exception, and instead call RaiseSyntaxError.
RegExp = new Regex(pattern, options ||| RegexOptions.ECMAScript ||| RegexOptions.Compiled)
Global = global'
}
then RegExp = new Regex(pattern, options ||| RegexOptions.ECMAScript ||| RegexOptions.Compiled)
new (env, pattern) =
RegExpObject(env, pattern, RegexOptions.None, false)
Why not just delegate the real work to a separate free function, so the work doesn't have to be done directly in your class' constructor?
let createRegex pattern options =
try
Regex(pattern, options ||| RegexOptions.ECMAScript ||| RegexOptions.Compiled)
with
| :? System.ArgumentException -> RaiseSynaxError ()
Then your class (as demonstrated in your edit) would be:
type RegExpObject(env, pattern, options, global') //'
inherit CommonObject(env, env.Maps.RegExp, env.Prototypes.RegExp)
let RegExp = createRegex pattern options
let Global = global' //'
member x.IgnoreCase =
(x.RegExp.Options &&& RegexOptions.IgnoreCase) = RegexOptions.IgnoreCase
member x.MultiLine =
(x.RegExp.Options &&& RegexOptions.Multiline) = RegexOptions.Multiline
new (env, pattern) = RegExpObject(env, pattern, RegexOptions.None, false)
An advantage here is that, as #Brian indicated, no use of val would be necessary, making the class definition much cleaner.
Try the following
type MyRiskyObject(foo : obj, data : string) as this =
inherit BaseObject(foo)
let mutable data = data;
do
try
data <- this.RiskyOperation data
with
| :? System.ArgumentException -> this.DoSomethingElse()
Alternate example with non-mutable let binding where RiskOperation and DoSomethingElse are not members of MyRiskObject
type MyRiskyObject(foo : obj, data : string) =
inherit BaseObject(foo)
let data =
try
OtherModule.RiskyOperation data
with
| :? System.ArgumentException ->
OtherModule.DoSomethingElse()
data