The attribute and C# examples are noted here but it doesn't look to be possible for FSharp.
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-2
// using System.Runtime.CompilerServices
// using System.Diagnostics;
public void DoProcessing()
{
TraceMessage("Something happened.");
}
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}
Sample Output:
message: Something happened.
member name: DoProcessing
source file path: c:\Users\username\Documents\Visual Studio 2012\Projects\CallerInfoCS\CallerInfoCS\Form1.cs
source line number: 31
Is it possible to do the above in F# and if so what is the notation?
A quick search through the compiler source code shows that the name CallerMemberName does not appear anywhere in the code, so I think this feature is not supported. (You can certainly mark a parameter with the attribute, but these attributes are special - they instruct the compiler instead of being discovered and used in some way at runtime.)
Update July 2016: As of late June, F# now supports CallerLineNumber and CallerFilePath, but CallerMemberName is still absent. It seems like that one in particular is more difficult to implement, unfortunately.
On a related note, F# has a few special identifiers that let you get the current source file name and line number, so you might be able to get similar information with __SOURCE_DIRECTORY__ and __LINE__
(but not from the caller as in C#).
Here's a quick 'n' dirty hack, which abuses inline to get this info:
module Tracing =
open System
open System.Text.RegularExpressions
let (|TraceInfo|_|) (s:string) =
let m = Regex.Match(s, "at (?<mem>.+?) in (?<file>.+?\.[a-zA-Z]+):line (?<line>\d+)")
if m.Success then
Some(m.Groups.["mem"].Value, m.Groups.["file"].Value, int m.Groups.["line"].Value)
else None
let inline trace s =
printfn "%s" s
match Environment.StackTrace with
| TraceInfo(m, f, l) ->
printfn " Member: %s" m
printfn " File : %s" f
printfn " Line : %d" l
| _ -> ()
It actually does work, more or less:
Related
I'm trying to go from:
sprintf "%3.1f" myNumber
to:
sprintf myFormatter myNumber
which is not possible
I have a situation where number precision depends on some settings, so I would like to be able to create my own formatter string.
I know it can be done with String.Format, but I am curious if there is a F# way with sprintf, or ksprinf; can it be done?
Simple answer
EDIT: Diego Esmerio on F# Slack showed me a simpler way that I honestly never thought of while working out the answer below. The trick is to use PrintfFormat directly, like as follows.
// Credit: Diego. This
let formatPrec precision =
PrintfFormat<float -> string,unit,string,string>(sprintf "%%1.%if" precision)
let x = 15.234
let a = sprintf (formatPrec 0) x
let b = sprintf (formatPrec 1) x
let c = sprintf (formatPrec 3) x
Output:
val formatPrec : precision:int -> PrintfFormat<(float -> string),unit,string,string>
val x : float = 15.234
val a : string = "15"
val b : string = "15.2"
val c : string = "15.234"
This approach is arguably much simpler than the Expr-based approach below. For both approaches, be careful with the formatting string, as it will compile just fine, but break at runtime if it is invalid.
Original answer (complex)
This isn't trivial to do, because functions like sprintf and printfn are compile-time special-case functions that turn your string-argument into a function (in this case of type float -> string).
There are some things you can do with kprintf, but it won't allow the formatting-argument to become a dynamic value, since the compiler still wants to type-check that.
However, using quotations we can build such function ourselves. The easy way is to create quotation from your expression and to change the parts we need to change.
The starting point is this:
> <# sprintf "%3.1f" #>
val it : Expr<(float -> string)> =
Let (clo1,
Call (None, PrintFormatToString,
[Coerce (NewObject (PrintfFormat`5, Value ("%3.1f")), PrintfFormat`4)]),
Lambda (arg10, Application (clo1, arg10)))
...
That may look like a whole lot of mess, but since we only need to change one tiny bit, we can do this rather simply:
open Microsoft.FSharp.Quotations // part of F#
open Microsoft.FSharp.Quotations.Patterns // part of F#
open FSharp.Quotations.Evaluator // NuGet package (with same name)
// this is the function that in turn will create a function dynamically
let withFormat format =
let expr =
match <# sprintf "%3.1f" #> with
| Let(var, expr1, expr2) ->
match expr1 with
| Call(None, methodInfo, [Coerce(NewObject(ctor, [Value _]), mprintFormat)]) ->
Expr.Let(var, Expr.Call(methodInfo, [Expr.Coerce(Expr.NewObject(ctor, [Expr.Value format]), mprintFormat)]), expr2)
| _ -> failwith "oops" // won't happen
| _ -> failwith "oops" // won't happen
expr.CompileUntyped() :?> (float -> string)
To use this, we can now simply do this:
> withFormat "%1.2f" 123.4567899112233445566;;
val it : string = "123.46"
> withFormat "%1.5f" 123.4567899112233445566;;
val it : string = "123.45679"
> withFormat "%1.12f" 123.4567899112233445566;;
val it : string = "123.456789911223"
Or like this:
> let format = "%0.4ef";;
val format : string = "%0.4ef"
> withFormat format 123.4567899112233445566;;
val it : string = "1.2346e+002f"
It doesn't matter whether the format string is now a fixed string during compile time. However, if this is used in performance sensitive area, you may want to cache the resulting functions, as recompiling an expression tree is moderately expensive.
Can somebody help me with article of Tomas Petricek: http://tomasp.net/blog/fsharp-dynamic-lookup.aspx/#dynfslinks?
The problem is that it is severely outdated. I understand that namespaces
open Microsoft.FSharp.Quotations.Typed
open Microsoft.FSharp.Quotations.Raw
are gone. So I removed the openings. But there are still errors. "Typed" is not defined. "RecdGet" is not defined. And I suspect they are not the last. I'm trying to prove to my boss that F# is good to use for database normalization. Dynamic lookup of fields would really helped me to deal with similarly named fields having different prefixes.
There is also post of Tomas on fpish: https://fpish.net/topic/None/57493, which I understand predates the article
Here's a rough equivalent:
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
type DynamicMember<'t,'u> = Expr<'t -> 'u>
let getValueReader (expr:DynamicMember<'recdT, 'fieldT>) =
// Match the quotation representing the symbol
match expr with
| Lambda(v, PropertyGet (Some (Var v'), pi, [])) when v = v' ->
// It represents reading of the F# record field..
// .. get a function that reads the record field using F# reflection
let rdr = Reflection.FSharpValue.PreComputeRecordFieldReader pi
// we're not adding any additional processing, so we just
// simply add type conversion to the correct types & return it
((box >> rdr >> unbox) : 'recdT -> 'fieldT)
| _ ->
// Quotation doesn't represent symbol - this is an error
failwith "Invalid expression - not reading record field!"
type SampleRec = { Str : string; Num : int }
let readStrField = getValueReader <# fun (r : SampleRec) -> r.Str #>
let readNumField = getValueReader <# fun (r : SampleRec) -> r.Num #>
let rc = { Str = "Hello world!"; Num = 42 }
let s, n = readStrField rc, readNumField rc
printfn "Extracted: %s, %d" s n
I wrote this script from some resources I found. It's working but I some files I have problem. I am new in F# so how can I change line with FileHelpersException to get exact line where is the problem? Thanks
// Learn more about F# at http://fsharp.net
// See the 'F# Tutorial' project for more help.
open FileHelpers
open System
[<DelimitedRecord(",")>]
type CsvRecord =
class
val field1 : string
val field2 : string
val field3 : int
new () = {
field1 = ""
field2 = ""
field3 = 0
}
end
[<EntryPoint>]
let main argv =
use file = System.IO.File.CreateText("result.txt")
let engine = new FileHelperEngine<CsvRecord>()
engine.Encoding <- new Text.UTF8Encoding()
let res =
try
engine.ReadFile("test.csv")
with
| :? FileHelpersException -> Array.empty<CsvRecord>
for record in res do
fprintfn file "%s" record.field1
printf "DONE!"
let s = Console.ReadLine()
0 // return an integer exit code
I suggest that you use CsvTypeProvider instead. When there's a mismatch the error message states the line which has the error
open FSharp.Data
[<EntryPoint>]
let main argv =
use file = System.IO.File.CreateText("result.txt")
let csv = new CsvProvider<"test.csv">()
for record in csv.Data do
fprintfn file "%s" record.field1
If you want to ignore the lines with errors, just pass IgnoreErrors=true as an extra parameter to CsvProvider
This question is about the FileHelpers library you are using, not F#, so looking at the docs for that might help. In this case you can check for ConvertException instead of FileHelpersException, which contains members that give you more details about the member.
try
engine.ReadFile("test.csv")
with
| :? ConvertException as ex ->
printfn "ERROR: Line %d Col %d" ex.LineNumber ex.ColumnNumber
Array.empty<CsvRecord>
I agree with Gustavo though, you might find it easier to use the CsvTypeProvider.
This question already has answers here:
FirstOrDefault In F#
(4 answers)
Closed 9 years ago.
The code below throws a NullReferenceException within the FirstOrDefault() method:
open System
open System.Collections.Generic
open System.Linq
[<EntryPoint>]
let main argv =
let suspects = seq {
yield ("Frank", 1.0)
yield ("Suzie", 0.9)
yield ("John", 0.5)
// yield ("Keyser Soze", 0.3)
}
let likely = suspects.FirstOrDefault(fun (name, confidence) -> name = "Keyser Soze")
printfn "Name: %s" (fst likely)
Console.ReadLine() |> ignore
0
What's the best way to work around that? Catching it seems wrong. I could grab the iterator manually and put it in a while loop, but that's - well, wrong on so many levels.
[Edit]
I can't even do what I would do in C#, namely, check to see if the result is null or default, for two reasons: (1) The error is thrown in the FirstOrDefault() method, not when I reference the result; and (2) if I try to check for null, the compiler complains that `The type '(string * float)' does not have 'null' as a proper value':
if likely = null then
printfn "Nothing to see here"
Any suggestions?
As noted above, Seq.tryFind is the idiomatic way of achieving that. If you really must use FirstOrDefault() you could do something like this:
open System.Collections.Generic
open System.Linq
let suspects = seq {
yield Some("Frank", 1.0)
yield Some("Suzie", 0.9)
yield Some("John", 0.5)
// yield ("Keyser Soze", 0.3)
}
let likely = suspects.FirstOrDefault(fun x -> let name, confidence = x.Value
name = "Keyser Soze")
match likely with
| Some(x) -> printfn "Name: %s" (fst x)
| None -> printfn "Not Found"
You can follow hardcode null checks-polluted C# way if you want to:
...
let likely = suspects.FirstOrDefault(fun x -> x.Equals(null) || (fst x) = "Keyser Soze")
if obj.ReferenceEquals(likely, null) then
printfn "Nothing to print"
else
printfn "Name: %s" (fst x)
...
but this is against the major F# idiom upon avoiding null-checks altogether.
EDIT: It seems the alleged NullReferenceException within FirstOrDefault actively referred in comments simply does not happen! With first line of code above changed back to the original
let likely = suspects.FirstOrDefault(fun (name, confidence) -> name = "Keyser Soze")
the snippet works for the sequence of three first tuples without a problem.
How do i setup a printf-style logger for f# using logging library similar to log4net.
i have Log.Debug, Info, Warn, etc. functions that are similar to DebugFormat or InfoFormat in log4net. i tried to setup type extensions for my Log class that i could call in printf style like Log.Debugf "%s" "foo". my generic log function looks like this:
let log format = Printf.kprintf (sprintf "%s") format
i am having trouble with the extension function signature to log to my Debug function...
i tried using Debugf format and Debug
I'm not familiar with log4net, but assuming you're logging to a MessageBox (like the pros do), you can do the following:
let log format = Printf.kprintf (fun msg -> System.Windows.Forms.MessageBox.Show(msg)) format
In this case, since Show takes a string, it can be shortened to:
let log format = Printf.kprintf System.Windows.Forms.MessageBox.Show format
you mean something like this ?
open System
type SomeLogger() =
member this.Error(format : string, [<ParamArray>]args : obj[] ) = ()
member this.Info(format : string, [<ParamArray>]args : obj[] ) = ()
module Extensions =
type SomeLogger with
member this.FInfo format = Printf.ksprintf (this.Info) format
member this.FError format = Printf.ksprintf (this.Error) format
open Extensions
let l = new SomeLogger()
l.FInfo "%d%s" 10 "123"
You can use standard logging subsystem that defined in System.Diagnostic namespace.
You shall be sure that your logging enviromnet correctly initialized. For example something like this (part of example in C#) but it easy is linked with f# code.
Trace.Listeners.Clear();
try {
TextWriterTraceListener infoTextLogger = new AlignedTextWriterTraceListener(#"c:\temp\log.log");
infoTextLogger.Filter = new EventTypeFilter(SourceLevels.All);
infoTextLogger.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.ProcessId | TraceOptions.ThreadId;
Trace.Listeners.Add(infoTextLogger);
TextWriterTraceListener consoleWriter = new AlignedTextWriterTraceListener(System.Console.Out);
consoleWriter.Filter = new EventTypeFilter(SourceLevels.Information);
Trace.Listeners.Add(consoleWriter);
} catch (Exception exp) {
throw exp;
}
AlignedTextWriterTraceListener.TraceSourceNameLength = SOURCE_NAME_FIELD_LENGTH;
Trace.AutoFlush = true;
Trace.TraceInformation("Logging subsystem has been initiated");
so in f#
open System
open System.Diagnostics
module ClientConsole =
let Run _ =
Trace.TraceInformation("Client started");
For more convenient you can use another trace listener that definded by third party programmer.
For example lool at : AlignedTextWriterTraceListener