I would like to build a custom printing function which I use to determine what to print for a debug logger.
The function I am looking for is something like:
let debugLog severity ... =
if severity < logLevel then
printfn ...
// otherwise just ignore
Unfortunately I have no idea how to fill in the ... I would also like to avoid passing a string directly as a second parameter, I would really like my function to behave like an extended printfn.
You can use the kprintf function.
let maxLevel = 3
let log severityLevel str =
if severityLevel < maxLevel then
printfn "%s" str
// or you can add more formatting
// printfn "Level: %i -> %s" severityLevel str
let logf sevLevel str = Printf.kprintf (log sevLevel) str
logf 1 "Message: %i" 1 // prints 'Message: 1'
logf 4 "Message: %i" 2 // doesn't print
logf 2 "Message: %s %i" "this is test" 3 // prints Message: this is test 3
The best way I can think of would be to use fprintf with stdout when you do want to print, and TextWriter.Null when you don't.
let logLevel = 3
let debugLog severity format =
let out =
if severity < logLevel then
stdout
else
System.IO.TextWriter.Null
fprintfn out format
debugLog 2 "test: %i" 42 // prints 'test: 42'
debugLog 4 "test: %i" 42 // prints nothing
Related
I've been trying to create a code in F# that can read text via another file, however I keep seeing a
The block following this 'let' is unfinished. Every code block is an expression and must have a result
on the
let a = line.Split delim
,I looked it up and thought it was an indentation error but that didn't fix that. Any advice?
[<EntryPoint>]
let main argv =
let delim = ','
use stream = new StreamReader #"final.txt"
let line = stream.Readline()
let a = line.Split delim
|>Seq.map System.Int32.Parse
|>Seq.toArray
printfn "Orignal numbers: %A" a
printfn "Ordered numbers: %A" (oddEven a)
0 // return an integer exit code
You're missing indentation (and misspelled a thing or two). Anything that you want to be part of a let block has to be indented one level. Here's your code properly formatted:
[<EntryPoint>]
let main argv =
let delim = ','
use stream = new StreamReader #"final.txt"
let line = stream.ReadLine()
let a =
line.Split delim
|> Seq.map System.Int32.Parse
|> Seq.toArray
printfn "Orignal numbers: %A" a
printfn "Ordered numbers: %A" (oddEven a)
0 // return an integer exit code
/// Colored printf
let cprintf c fmt =
Printf.kprintf
(fun s ->
let old = System.Console.ForegroundColor
try
System.Console.ForegroundColor <- c;
System.Console.Write s
finally
System.Console.ForegroundColor <- old)
fmt
// Colored printfn
let cprintfn c fmt =
cprintf c fmt
printfn ""
With the above stolen code,
cprintfn ConsoleColor.Yellow "This works"
but
cprintfn ConsoleColor.Yellow "This %s" "doesn't"
(cprintfn ConsoleColor.Yellow) "still %s" "doesn't"
(cprintfn ConsoleColor.Yellow "still %s") "doesn't"
cprintfn ConsoleColor.Yellow ("still %s" "doesn't")
I know this has to do with Printf.TextWriterFormat<_> vs simple strings but even specifying a type for fmt as in cprintf c (fmt:Printf.TextWriterFormat<_>), I can't get the kprintf part to work. I read a bunch of answers related to logging, but I still can't figure it out. Is a cprintf that takes format parameters possible?
First of all, your cprintf function is perfectly fine and you can use it with variable number of arguments. The problem is only with the cprintfn function:
cprintf System.ConsoleColor.Red "%s %d" "hi" 42
The problem is that when you're defining a function that uses formatting string, you always need to use partial application. That is your function has to be of the form:
let myprintf fmt =
<whatever>
otherprintf <whatever> fmt
The important thing is that otherprintf <whatever> fmt has to be the last part of the body and it has to take fmt as the last argument. This way, when otherprintf needs more arguments (as specified by fmt) these are automatically propagated and become arguments of myprintf.
This means that defining cprintfn in terms of cprintf is actually quite tricky. But you can always define a helper function that is a bit more powerful and lets you do both:
let cprintfWith endl c fmt =
Printf.kprintf
(fun s ->
let old = System.Console.ForegroundColor
try
System.Console.ForegroundColor <- c;
System.Console.Write (s + endl)
finally
System.Console.ForegroundColor <- old)
fmt
let cprintf c fmt = cprintfWith "" c fmt
let cprintfn c fmt = cprintfWith "\n" c fmt
I am trying to wrap a call to sprintf function. Here's my attempt:
let p format args = "That was: " + (sprintf format args)
let a = "a"
let b = "b"
let z1 = p "A %s has invalid b" a
This seems to work, output is
val p : format:Printf.StringFormat<('a -> string)> -> args:'a -> string
val a : string = "a"
val b : string = "b"
val z1 : string = "That was: A a has invalid b"
But it wouldn't work with more than one arg:
let z2 = p "A %s has invalid b %A" a b
I get compile-time error:
let z2 = p "A %s has invalid b %A" a b;;
---------^^^^^^^^^^^^^^^^^^^^^^^^^^^
stdin(7,10): error FS0003: This value is not a function and cannot be applied
How can I create a single function which would work with any number of args?
UPD. Tomas has suggested to use
let p format = Printf.kprintf (fun s -> "This was: " + s) format
It works indeed. Here's an example
let p format = Printf.kprintf (fun s -> "This was: " + s) format
let a = p "something like %d" 123
// val p : format:Printf.StringFormat<'a,string> -> 'a
// val a : string = "This was: something like 123"
But the thing is that main purpose of my function is to do some work except for formatring, so I tried to use the suggested code as follows
let q format =
let z = p format // p is defined as suggested
printf z // Some work with formatted string
let z = q "something like %d" 123
And it doesn't work again:
let z = q "something like %d" 123;;
----------^^^^^^^^^^^^^^^^^^^
stdin(30,15): error FS0001: The type ''c -> string' is not compatible with the type 'Printf.TextWriterFormat<('a -> 'b)>'
How could I fix it?
For this to work, you need to use currying - your function p needs to take the format and return a function returned by one of the printf functions (which can then be a function taking one or more arguments).
This cannot be done using sprintf (because then you would have to propagate the arguments explicitly. However, you can use kprintf which takes a continuation as the first argument::
let p format = Printf.kprintf (fun s -> "This was: " + s) format
The continuation is called with the formatted string and so you can do whatever you need with the resulting string before returning.
EDIT: To answer your extended question, the trick is to put all the additional work into the continuation:
let q format =
let cont z =
// Some work with formatted string
printf "%s" z
Printf.kprintf cont format
After trying this simple console input with 5, the result is shown as 53
printfn "Enter no. of blocks: "
let nBlock = System.Console.Read()
printfn "entered value is %O" nBlock
Tried it on the interactive, still getting wrong results. Any solutions please?
You should try something like:
printfn "Enter no. of blocks: "
let nBlock = System.Console.ReadLine() |> System.Int32.Parse
printfn "entered value is %d" nBlock
Explanation:
you code only reads one character - as Lee mentioned
with this you will read a line (ending after you press return) and parse that string into a int.
Remark: maybe you will want to check for a number, you can do this with TryParse:
printfn "Enter no. of blocks: "
let nBlock =
match System.Console.ReadLine() |> System.Int32.TryParse with
| true, n -> n
| false, _ -> -1
printfn "entered value is %d" nBlock
Of course you will have to check for the error case (-1) or change it into a option or something.
System.Console.Read() returns an int, therefore nBlock contains the int representation of the input character '5', which is 53. You can convert it back to a char using Convert.ToChar. Since each character is returned individually, you will need wrapping it into a loop to process the entire input, as described in this article.
A better approach is probably to use Console.ReadLine() to read then entire line and parse that to an int.
Hi everbody I am doing a project with F# but I get this error when ı use let num= line for the following code . I'm new at F# so I can not solve the problem. My code should do this things. User enter a number and calculate the fibonacci but if user enter not a number throw exception
open System
let rec fib n =
match n with
|0->0
|1->1
|2->1
|n->fib(n-1)+fib(n-2);;
let printFibonacci list =
for i=0 to (List.length list)-1 do
printf "%d " (list.Item(i));;
let control = true
while control do
try
printfn "Enter a Number:"
let num:int = Convert.ToInt32(stdin.ReadLine())
with
| :? System.FormatException->printfn "Number Format Exception";
let listFibonacci = [for i in 0 .. num-1->fib(i)]
printFibonacci(listFibonacci)
printfn "\n%A"(listFibonacci)
control<-false
Console.ReadKey(true)
exit 0;;
I'm not an F# expert but I can see 3 problems with the code you posted.
1) As Lasse V Karlsen commented - f# uses the 'offside' rule so your 'fib' expression needs the body indented in. If you are running this in the Visual Studio Shell it should warn you of this by putting a blue squiggly line under the appropriate code.
2) Both 'control' and 'num' are mutable values so need to be declared explicitly as such.
f# is a functional language so by default any expressions are immutable i.e they are not allowed to change state after they have been declared.
In f#, saying 'let n = expr' does not mean 'assign the value of expr to n' like you would in say c# or c++. Instead it means 'n fundamentally is expr' and will be forever much like a mathematical equation.
So if you want to update the value of a variable you use the special '<-' notation which is the equivalent of 'assign the value on rhs to the lhs' and you need to declare that variable as mutable i.e 'this value can be changed later'
So I think both num and control need to be declared at the top of the loop as
let mutable control = false
let mutable num = 0 // or whatever you want the initial value of num to be
As a side note you don't have to explicitly declare num as an int ( you can if you want ) but f# will infer the type for you
If I understand your code correctly, you want to keep asking for input number n until a valid number is given and print fibonacci numbers up to n. In this case, you'd better move the calculation and printing inside the try block. Here's an updated version with formatting.
open System
let rec fib n =
match n with
|0->0
|1->1
|2->1
|n->fib(n-1)+fib(n-2);;
let printFibonacci list =
for i=0 to (List.length list)-1 do
printf "%d " (list.Item(i))
let mutable control = true //you forgot to add the 'mutable' keyword
while control do
try
printfn "Enter a Number:"
let num:int = Convert.ToInt32(stdin.ReadLine())
let listFibonacci = [for i in 0 .. num-1 -> fib(i)]
printFibonacci(listFibonacci)
printfn "\n%A"(listFibonacci)
control <- false
with
| :? System.FormatException -> printfn "Number Format Exception"
//add the ignore statement to drop the resulting ConsoleKeyInfo struct
//or the compiler will complain about an unused value floating around.
Console.ReadKey(true) |> ignore
// exit 0 (* Exit isn't necessary *)
Instead of using an imperative style number entry routine and relying on exceptions for control flow, here's a recursive getNumberFromConsole function you could use as well:
open System
let rec fib n =
match n with
| 0 -> 0
| 1 | 2 -> 1
| n -> fib(n-1) + fib(n-2);;
let printFibonacci list =
for i=0 to (List.length list)-1 do
printf "%d " (list.Item(i))
//alternative number input, using recursion
let rec getNumberFromConsole() =
match Int32.TryParse(stdin.ReadLine()) with
| (true, value) -> value
| (false, _) -> printfn "Please enter a valid number"
getNumberFromConsole()
printfn "Enter a Number:"
let num = getNumberFromConsole()
let listFibonacci = [for i in 0 .. num-1 -> fib(i)]
printFibonacci(listFibonacci)
printfn "\n%A"(listFibonacci)
Console.ReadKey(true) |> ignore
P.S. Thanks for showing me stdin. I never knew it existed. Now I can write some interactive scripts.