The following snippet illustrates the error I'm getting. Even though both match branches return the same thing; I get error, "This expression was expected to have type unit but here has type 'a -> unit" I have no clue what the compiler wants here...
open System.IO
let FileContent contents =
match contents with
| "" -> None
| c -> Some(c)
let WriteSomething (contents:string) =
let writer = new StreamWriter("")
writer.Write( contents ) |> ignore
let DoStuffWithFileContents =
let reader = new StreamReader( "" )
let stuff = reader.ReadToEnd()
match stuff |> FileContent with
| Some(c) -> WriteSomething c
|> ignore
| None -> ignore // <- error on "ignore"
The ignore operator is actually a function that takes a single input and returns the unit type (F#'s equivalent of void). So when you have -> ignore you're returning the ignore function.
Instead, use () to represent the value of the unit type:
| Some(c) -> WriteSomething c
|> ignore
| None -> ()
But actually, since StreamWriter.Write returns void, all these ignore's are unnecessary. You could just as easily write this as:
let WriteSomething (contents:string) =
let writer = new StreamWriter("")
writer.Write(contents)
let DoStuffWithFileContents =
let reader = new StreamReader("")
let stuff = reader.ReadToEnd()
match stuff |> FileContent with
| Some(c) -> WriteSomething c
| None -> ()
Or even better, use Option.iter:
let WriteSomething (contents:string) =
let writer = new StreamWriter("")
writer.Write(contents)
let DoStuffWithFileContents =
let reader = new StreamReader("")
let stuff = reader.ReadToEnd()
stuff |> FileContent |> Option.iter(WriteSomething)
By returning ignore on your last line you are returning a function, not a simple value.
The point of ignore is to convert things to (). Your last line can simply return () directly.
Related
This is not for a practical need, but rather to try to learn something.
I am using FSToolKit's asyncResult expression which is very handy and I would like to know if there is a way to 'combine' expressions, such as async and result here, or does a custom expression have to be written?
Here is an example of my function to set the ip to a subdomain, with CloudFlare:
let setSubdomainToIpAsync zoneName url ip =
let decodeResult (r: CloudFlareResult<'a>) =
match r.Success with
| true -> Ok r.Result
| false -> Error r.Errors.[0].Message
let getZoneAsync (client: CloudFlareClient) =
asyncResult {
let! r = client.Zones.GetAsync()
let! d = decodeResult r
return!
match d |> Seq.filter (fun x -> x.Name = zoneName) |> Seq.toList with
| z::_ -> Ok z // take the first one
| _ -> Error $"zone '{zoneName}' not found"
}
let getRecordsAsync (client: CloudFlareClient) zoneId =
asyncResult {
let! r = client.Zones.DnsRecords.GetAsync(zoneId)
return! decodeResult r
}
let updateRecordAsync (client: CloudFlareClient) zoneId (records: DnsRecord seq) =
asyncResult {
return!
match records |> Seq.filter (fun x -> x.Name = url) |> Seq.toList with
| r::_ -> client.Zones.DnsRecords.UpdateAsync(zoneId, r.Id, ModifiedDnsRecord(Name = url, Content = ip, Type = DnsRecordType.A, Proxied = true))
| [] -> client.Zones.DnsRecords.AddAsync(zoneId, NewDnsRecord(Name = url, Content = ip, Proxied = true))
}
asyncResult {
use client = new CloudFlareClient(Credentials.CloudFlare.Email, Credentials.CloudFlare.Key)
let! zone = getZoneAsync client
let! records = getRecordsAsync client zone.Id
let! update = updateRecordAsync client zone.Id records
return! decodeResult update
}
It is interfacing with a C# lib that handles all the calls to the CloudFlare API and returns a CloudFlareResult object which has a success flag, a result and an error.
I remapped that type to a Result<'a, string> type:
let decodeResult (r: CloudFlareResult<'a>) =
match r.Success with
| true -> Ok r.Result
| false -> Error r.Errors.[0].Message
And I could write an expression for it (hypothetically since I've been using them but haven't written my own yet), but then I would be happy to have an asyncCloudFlareResult expression, or even an asyncCloudFlareResultOrResult expression, if that makes sense.
I am wondering if there is a mechanism to combine expressions together, the same way FSToolKit does (although I suspect it's just custom code there).
Again, this is a question to learn something, not about the practicality since it would probably add more code than it's worth.
Following Gus' comment, I realized it would be good to illustrate the point with some simpler code:
function DoA : int -> Async<AWSCallResult<int, string>>
function DoB : int -> Async<Result<int, string>>
AWSCallResultAndResult {
let! a = DoA 3
let! b = DoB a
return b
}
in this example I would end up with two types that can take an int and return an error string, but they are different. Both have their expressions so I can chain them as needed.
And the original question is about how these can be combined together.
It's possible to extend CEs with overloads.
The example below makes it possible to use the CustomResult type with a usual result builder.
open FsToolkit.ErrorHandling
type CustomResult<'T, 'TError> =
{ IsError: bool
Error: 'TError
Value: 'T }
type ResultBuilder with
member inline _.Source(result : CustomResult<'T, 'TError>) =
if result.IsError then
Error result.Error
else
Ok result.Value
let computeA () = Ok 42
let computeB () = Ok 23
let computeC () =
{ CustomResult.Error = "oops. This went wrong"
CustomResult.IsError = true
CustomResult.Value = 64 }
let computedResult =
result {
let! a = computeA ()
let! b = computeB ()
let! c = computeC ()
return a + b + c
}
I have the following f# code
product.code <- productPage.Html
.Descendants["li"]
.Select(fun node -> node.InnerText())
.Where(fun link -> (Regex.Match(link,#"code:").Success))
.FirstOrDefault()
.Replace("code:", "")
.Trim()
I'm having some trouble with nulls.
In c# I would do something like this.
product.code = productPage?.Html
?.Descendants["li"]
?.Select(node => node.InnerText())
?.Where(link => Regex.Match(link,#"code:").Success)
?.FirstOrDefault()
?.Replace("code:", "")
?.Trim() ?? "Not Found"
Is this possible?
In the second example, it looks to me like "?." has to be carried through the whole call chain due to its initial use. Rather than try to recreate this operator and preserve how this looks in C#, I suggest you go for more idiomatic F#. For example:
module String =
let replace (oldValue: string) (newValue: string) (s: string) =
s.Replace (oldValue, newValue)
let trim (s: string) =
s.Trim()
let result =
match isNull productPage with
| true -> None
| false ->
productPage.Html.Descendants.["li"]
|> Seq.map (fun node -> node.InnerText())
|> Seq.tryPick (fun link -> (Regex.Match (link, "code:").Success))
let code =
match result with
| Some html ->
html
|> String.replace "code:" ""
|> String.trim
| None -> "Not Found"
product.code <- code
Say I have a a function
let GetDataFromWebsite (url:string) =
let webpage = HtmlDocument.Load(url)
let html = webpage.TryGetHtml
html
(note that this will become a longer function once I work out how to use the TryGetHtml function)
This tells me that it has a return string -> unit -> HtmlNode option. What is this exactly returning and how do I use it? I have tried
match GetDataFromWebsite(#"...") with
| None -> "None"
| _ -> (fun a -> a.ToString())
|> printfn "%s"
but visual studio states that:
This expresion was expected to have type
'unit -> FSharp.Data.HtmlNode option'
but here has type
''a option'
Nearly there :)
TryGetHtml is a function, not a property, and you likely want to evaluate it instead of assigning it:
let GetDataFromWebsite (url:string) =
let webpage = HtmlDocument.Load(url)
let html = webpage.TryGetHtml() // note braces
html
Now it returns HtmlNode option you can pattern match on:
match GetDataFromWebsite(#"...") with
| None -> "None"
| Some x -> x.ToString()
|> printfn "%s"
This should compile without errors.
Given the following contrived active pattern:
let (|TypeDef|_|) (typeDef:Type) (value:obj) =
if obj.ReferenceEquals(value, null) then None
else
let typ = value.GetType()
if typ.IsGenericType && typ.GetGenericTypeDefinition() = typeDef then Some(typ.GetGenericArguments())
else None
The following:
let dict = System.Collections.Generic.Dictionary<string,obj>()
match dict with
| TypeDef typedefof<Dictionary<_,_>> typeArgs -> printfn "%A" typeArgs
| _ -> ()
gives the error:
Unexpected type application in pattern matching. Expected '->' or other token.
But this works:
let typ = typedefof<Dictionary<_,_>>
match dict with
| TypeDef typ typeArgs -> printfn "%A" typeArgs
| _ -> ()
Why is typedefof (or typeof) not allowed here?
Even if you're using a parameterized active pattern (where the argument is some expression), the compiler parses the argument as a pattern (as opposed to an expression), so the syntax is more restricted.
I think this is essentially the same problem as the one discussed here: How can I pass complex expression to parametrized active pattern? (I'm not sure about the actual compiler implementation, but the F# specification says that it should parse as a pattern).
As a workaround, you can write any expression inside a quotation, so you could do this:
let undef<'T> : 'T = Unchecked.defaultof<_>
let (|TypeDef|) (typeExpr:Expr) (value:obj) =
let typeDef = typeExpr.Type.GetGenericTypeDefinition()
// ...
let dict = System.Collections.Generic.Dictionary<string,obj>()
match dict with
| TypeDef <# undef<Dictionary<_,_>> #> typeArgs -> printfn "%A" typeArgs
| _ -> ()
Adding to Tomas' answer, the troublesome syntax in this case appears to be with the explicit type arguments. Another workaround is to use a dummy parameter to transmit the type information
let (|TypeDef|_|) (_:'a) (value:obj) =
let typeDef = typedefof<'a>
if obj.ReferenceEquals(value, null) then None
else
let typ = value.GetType()
if typ.IsGenericType && typ.GetGenericTypeDefinition() = typeDef then Some(typ.GetGenericArguments())
else None
let z =
let dict = System.Collections.Generic.Dictionary<string,obj>()
match dict with
| TypeDef (null:Dictionary<_,_>) typeArgs -> printfn "%A" typeArgs
| _ -> ()
I continue to work on a printer for F# quoted expressions, it doesn't have to be perfect, but I'd like to see what is possible. The active patterns in Microsoft.FSharp.Quotations.Patterns and Microsoft.FSharp.Quotations.DerivedPatterns used for decomposing quoted expressions will typically provide MemberInfo instances when appropriate, these can be used to obtain the name of a property, function, etc. and their "declaring" type, such as a module or static class. The problem is, I only know how to obtain the CompiledName from these instances but I'd like the F# name. For example,
> <# List.mapi (fun i j -> i+j) [1;2;3] #> |> (function Call(_,mi,_) -> mi.DeclaringType.Name, mi.Name);;
val it : string * string = ("ListModule", "MapIndexed")
How can this match be rewritten to return ("List", "mapi")? Is it possible?
FYI, here is my final polished solution from Stringer Bell and pblasucci's help:
let moduleSourceName (declaringType:Type) =
FSharpEntity.FromType(declaringType).DisplayName
let methodSourceName (mi:MemberInfo) =
mi.GetCustomAttributes(true)
|> Array.tryPick
(function
| :? CompilationSourceNameAttribute as csna -> Some(csna)
| _ -> None)
|> (function | Some(csna) -> csna.SourceName | None -> mi.Name)
//usage:
let sourceNames =
<# List.mapi (fun i j -> i+j) [1;2;3] #>
|> (function Call(_,mi,_) -> mi.DeclaringType |> moduleSourceName, mi |> methodSourceName);
You can use F# powerpack for that purpose:
open Microsoft.FSharp.Metadata
...
| Call(_, mi, _) ->
let ty = Microsoft.FSharp.Metadata.FSharpEntity.FromType(mi.DeclaringType)
let name = ty.DisplayName // name is List
However, I don't think if it's possible to retrieve function name with powerpack.
Edit:
As hinted by pblasucci, you can use CompilationSourceName attribute for retrieving source name:
let infos = mi.DeclaringType.GetMember(mi.Name)
let att = infos.[0].GetCustomAttributes(true)
let fName =
(att.[1] :?> CompilationSourceNameAttribute).SourceName // fName is mapi