I have the following code in F#
live version at https://repl.it/repls/CarefulGiganticExtraction
let inline tryParse text =
let mutable r = Unchecked.defaultof<_>
(^a : (static member TryParse: string * ^a byref -> bool) (text, &r)), r
let inline tryParseWithDefault (defaultVal:'a) text =
match tryParse text with
| true, v -> v
| _ -> defaultVal
type TextBox(text:string) =
member this.Text = text
type View() =
member this.Name = "View"
type State = { count: int }
module Binder =
let inline oneWay defaultValue update dispatch (sender:obj) _ =
let value =
match sender with
| :? TextBox as textBox -> tryParseWithDefault defaultValue (textBox.Text)
| _ -> defaultValue
dispatch (fun state -> update state value )
let view (state: State) (dispatch): View =
// Create a partially applied oneWay binding function
// This fails to compile due to
// "The type of a first class function cannot contain byrefs"
let inline oneWay defaultValue update =
Binder.oneWay defaultValue update dispatch
// Return the mock view
View()
This is a cut down mock of some real code I'm trying to prototype to work with FuncUI an Elmish dialect for Avalonia.
The problem is that I get the compile error when I try to create the partial application of Binder.oneWay
let inline oneWay defaultValue update =
Binder.oneWay defaultValue update dispatch
error FS0425: The type of a first-class function cannot contain byrefs
Is it possible to create a partial application of the Binder.oneWay function at the top of the view function or have I coded myself into a corner?
Again the live version is at
https://repl.it/repls/CarefulGiganticExtraction
Related
I have some code which has a lot of repetition.
type RecordA = {
Name: string
// ...
}
type RecordB = {
Name: string
// ...
}
val getTheHandler: (name: string) -> (() -> ())
let handleA (record: RecordA) =
(getTheHandler record.Name) ()
let handleB (record: RecordB) =
(getTheHandler record.Name) ()
I'm wondering if it is possible to write some generic function that would let me simplify/refactor the getTheHandler record.Name. In trying to refactor that snippet the compiler wants to choose one record type of the other.
So trying this, I get a compiler error:
let shorter (record: 'T) =
(getTheHandler record.Name) ()
// later:
shorter myRecordA // FS0001: This expression was expected to have type RecordB but here has type RecordA
Is this possible? Is the only way to make this work to add a member function to each record type?
Yes, it is possible with SRTP - see here: Partial anonymous record in F#
Here's an example using your use case:
let getTheHandler (name: string) () = printfn $"{name}"
let inline handle (r: ^T) =
(^T : (member Name: string) r)
let ra: RecordA = { Name = "hello" }
let rb: RecordB = { Name = "world" }
handle ra // "hello"
handle rb // "world"
I'd also say though, a little repetition isn't that bad. SRTPs can be wonderful, but can lead down a path of getting way too happy with abstraction and sometimes compile-time slow downs. Using it judiciously like this isn't that bad though.
Just for the record, you can also solve this problem by using ordinary object-oriented interfaces. This is something that works quite well with functional design in F# and it is quite clean. There is some more work involved in explicitly implementing the interfaces, but the up side is that you end up with more clear explicit code (and the interface can model the intention better than just a member name):
To define and implement an interface:
type INamed =
abstract Name : string
type RecordA =
{ Name: string }
interface INamed with
member x.Name = x.Name
type RecordB =
{ Name: string }
interface INamed with
member x.Name = x.Name
To use this:
let getTheHandler (name:string) =
fun () -> printfn "Hi %s" name
let handle (record: INamed) =
(getTheHandler record.Name) ()
You also can solve this by using a higher-order function. By doing the selection through a function. It will become generic.
type RecordA = {
Name: string
}
type RecordB = {
Name: string
}
let getTheHandler name = printfn $"{name}"
let handle chooser record =
fun () -> getTheHandler (chooser record)
let ra : RecordA = {Name="Hello"}
let rb : RecordB = {Name="World"}
let name1 = ra |> handle (fun r -> r.Name)
let name2 = rb |> handle (fun r -> r.Name)
name1 ()
name2 ()
I would like to extend F# Arrays such that I can use arrays without converting to the finite int. Instead I want to work with bigint directly.
I was able to add a second length method to the array type as follows:
type 'T ``[]`` with
member this.LengthI: bigint =
bigint this.Length
member this.Item(index: bigint): 'T =
this.[int index]
However the Item method cannot be called with the .[ ] syntax.
Any ideas how this could be achieved? I this possible at all?
I strongly suspect this isn't possible for native arrays. You can verify yourself that you can overload indexed access just fine for other collections.
If you compile the following code:
let myArray = [| "a" |]
let myList = [ "a" ]
let arrayElement = myArray.[11111]
let listElement = myList.[22222]
and inspect the resulting IL, you'll see that while accessing the list element compiles to a regular virtual call, there is a special CIL instruction for accessing a native array element, ldelem.
//000004: let arrayElement = myArray.[11111]
IL_002c: call string[] Fuduoqv1565::get_myArray()
IL_0031: ldc.i4 0x2b67
IL_0036: ldelem [mscorlib]System.String
IL_003b: stsfld string '<StartupCode$51dff40d-e00b-40e4-b9cc-15309089d437>'.$Fuduoqv1565::arrayElement#4
.line 5,5 : 1,33 ''
//000005: let listElement = myList.[22222]
IL_0040: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<string> Fuduoqv1565::get_myList()
IL_0045: ldc.i4 0x56ce
IL_004a: callvirt instance !0 class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<string>::get_Item(int32)
IL_004f: stsfld string '<StartupCode$51dff40d-e00b-40e4-b9cc-15309089d437>'.$Fuduoqv1565::listElement#5
IL_0054: ret
I would guess that the same compiler logic that special-case array access to that single instruction also bypass any overload resolution involving extension methods and the like.
One way to circumvent this is to wrap the array in a custom type, where overloaded indexers will work as you expect. Making the wrapper type a struct should reduce the performance loss in most cases:
type [<Struct>] BigArray<'T>(array : 'T[]) =
member this.LengthI: bigint =
bigint array.Length
member this.Item
with get(index : int) = array.[index]
and set (index : int) value = array.[index] <- value
member this.Item
with get(index : bigint) = array.[int index]
and set (index : bigint) value = array.[int index] <- value
let bigArray = BigArray myArray
let bigArrayElement = bigArray.[0]
let bigArrayElement2 = bigArray.[bigint 0]
Another one is to upcast the array to the base System.Array class, on which you can then define the same overloaded operator. This removes the need to create a wrapper type and duplicate all members of 'T[], as you can just upcast/downcast the same array object as necessary. However, since the base class is untyped, you will lose type safety and have to box/unbox the elements when using the indexed access, which is quite ugly:
type System.Array with
member this.Item
with get (index : int) = (this :?> 'T[]).[index]
and set (index : int) (value : 'T) = (this :?> 'T[]).[index] <- value
member this.Item
with get(index : bigint) : 'T = (this :?> 'T[]).[int index]
and set(index : bigint) (value : 'T) = (this :?> 'T[]).[int index] <- value
let untypedArray = myArray :> System.Array
let untypedArrayElement = box untypedArray.[0] :?> string
let untypedArrayElement2 = box untypedArray.[bigint 0] :?> string
Using F#, I had the following (simplified), which works fine:
type MyInt =
struct
val Value: int
new v = { Value = v }
end
static member inline name() = "my_int" // relevant line
let inline getName (tp: ^a): string = (^a: (static member name : unit -> string) ())
It seems to me that the statically resolved member signature from the explicit member constraint requires a function. I was wondering if it can also be used with a field instead. I tried a few things:
The following will compile, but won't work and will fail with the error
error FS0001: The type 'MyInt' does not support the operator 'get_name'
type MyInt =
//...
static member inline name = "my_int"
let inline getName (tp: ^a): string = (^a: (static member name : string) ())
Removing the () to prevent it from trying to call the gettor is a syntax error. If I change it to actually implement the gettor, it works (but that's essentially the same as the original code).
type MyInt =
// ...
static member inline name with get() = "my_int"
let inline getName (tp: ^a): string = (^a: (static member name : string) ())
Is there a way to get, using explicit member constraints or similar means, the compiler to find the static field? Or is this simply a limitation of the syntax of constraints?
Update:
Surprisingly, it does work with instance fields, or as in this case, struct fields: using (^a: (member Value: 'b) ()), which will call the member Value.
I define a property in a class like:
type Customer() =
let mutable _lastName = String.Empty
member val LastName = _lastName with get
And in the method QueryData I assign a value to _lastName:
member self.QueryData () =
// CODE
let addressData = bp.GetStructure("PE_PERSONALDATA")
_lastName <- addressData.GetString("LASTNAME")
null
| RfcCommunication ex ->
let basedComm = ex :> Exception
basedComm
| RfcLogon ex ->
let basedLogon = ex :> Exception
basedLogon
| RfcAbapRuntime ex ->
let basedAbap = ex :> Exception
basedAbap
In the main function I created an instance of Customer() and read the LastName property:
[<EntryPoint>]
let main argv =
let customer = CustomerBapi.Customer()
let ex = customer.QueryData()
if ex <> null then printfn "%s" ex.Message
printfn "%s" customer.LastName
The result is empty string. I tried to debug the program and see that _lastName is not empty
Why is the LastName property is empty?
Your property should look like this:
member this.LastName with get () = _lastName
The type of property you are using is passing _lastName as the value to initialise it with.
Explanation from MSDN:
Private values that hold the data for properties are called backing
stores. To have the compiler create the backing store automatically,
use the keywords member val, omit the self-identifier, then provide an
expression to initialize the property. If the property is to be
mutable, include with get, set. For example, the following class type
includes two automatically implemented properties. Property1 is
read-only and is initialized to the argument provided to the primary
constructor, and Property2 is a settable property initialized to an
empty string:
type MyClass(property1 : int) =
member val Property1 = property1
member val Property2 = "" with get, set
Using Newtonsoft.Json, latest version (=6.0.6) I get the following error:
Cannot create and populate list type Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers+EmptyEnumerable`1[System.String]
However in a post it was announced that Newtonsoft.Json would fully support Fsharp types?
When I change the offending type to a regular array, everything works fine.
The code:
type Prescription () =
member val Id = "" with get, set
member val Status = new PrescriptionStatus() with get, set
member val Prescriber = new Prescriber() with get, set
member val Indications = [||] : string[] with get, set
When I change Indications to be:
member val Indications = Seq.empty : string seq with get, set
I run into the error.
Also, when I initialise what is in fact an enumerable as an array, it cannot be constructed:
member val Indications : string seq = [||] |> Array.toSeq with get, set
I guess the answer is, Newtonsoft.Json doesn't fully support F# types.
But F# doesn't make supporting them particularly easy. For instance, an empty seq defined with Seq.empty is not just an IEnumerable<T>, it's a particular enumerable implementation EmptyEnumerable<T>, and this seems to throw off serialization - most likely because there's no appropriate constructor on it. From the post you linked to:
To all future creators of immutable .NET collections: If your collection of T has a constructor that takes IEnumerable then Json.NET will automatically work when deserializing to your collection, otherwise you're all out of luck.
If you initialize your seq like this instead, perhaps the behaviour will be different:
member val Indications = Seq.ofArray [||] : string seq with get, set
But that's splitting hairs, the actual answer here is simple - don't serialize seqs. Just use concrete, well-behaved types like arrays. The simpler the type, the less likely it is to give you headaches when doing serialization or interop.
Setting JsonSerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace will fix this error.
I liked the notion of using simple types like arrays, only I wanted to use the same DTOs also for mapping to IQueryable in Linq queries. So, in that respect, arrays were not an option.
Luckily with some testing, it was simple:
#load ".\Scripts\load-project.fsx"
#time
open System
open System.Collections.Generic
open Newtonsoft.Json
[<CLIMutable>]
type Test1 =
{
Strings : string seq
}
type Test2 () =
member val Strings = Seq.empty : IEnumerable<string> with get, set
type Test3 () =
member val Strings = Seq.empty : String seq with get, set
type Test4 () =
member val Strings : IEnumerable<string> = Seq.empty : IEnumerable<string> with get, set
type Test5 () =
member val Strings : IEnumerable<string> = [] |> List.toSeq : IEnumerable<string> with get, set
type Test6 () =
member val Strings = [] |> List.toSeq : string seq with get, set
let test1 = { Strings = Seq.empty }
let test2 = new Test2 ()
let test3 = new Test3 ()
let test4 = new Test4 ()
let test5 = new Test5 ()
let test6 = new Test6 ()
let json1 = JsonConvert.SerializeObject(test1)
let json2 = JsonConvert.SerializeObject(test2)
let json3 = JsonConvert.SerializeObject(test3)
let json4 = JsonConvert.SerializeObject(test4)
let json5 = JsonConvert.SerializeObject(test5)
let json6 = JsonConvert.SerializeObject(test6)
let deserialized1 = JsonConvert.DeserializeObject<Test1>(json1) // Fails
let deserialized2 = JsonConvert.DeserializeObject<Test2>(json2) // Fails
let deserialized3 = JsonConvert.DeserializeObject<Test3>(json3) // Fails
let deserialized4 = JsonConvert.DeserializeObject<Test4>(json4) // Fails
let deserialized5 = JsonConvert.DeserializeObject<Test5>(json5) // Passes
let deserialized6 = JsonConvert.DeserializeObject<Test5>(json6) // Passes
So, as long as you construct your sequence using a type that has a recognisable constructor, for example a list, the object can be deserialised. Strangely enough, initializing the sequence as an array and then converting it to a sequence, like with the list to sequence example (which passes), fails.