Handy F# snippets [closed]

2020-01-24 06:18发布

There are already two questions about F#/functional snippets.

However what I'm looking for here are useful snippets, little 'helper' functions that are reusable. Or obscure but nifty patterns that you can never quite remember.

Something like:

open System.IO

let rec visitor dir filter= 
    seq { yield! Directory.GetFiles(dir, filter)
          for subdir in Directory.GetDirectories(dir) do 
              yield! visitor subdir filter} 

I'd like to make this a kind of handy reference page. As such there will be no right answer, but hopefully lots of good ones.

EDIT Tomas Petricek has created a site specifically for F# snippets http://fssnip.net/.

30条回答
Summer. ? 凉城
2楼-- · 2020-01-24 06:31

toggle code to sql

More trivial than most on this list, but handy nonetheless:

I'm always taking sql in and out of code to move it to a sql environment during development. Example:

let sql = "select a,b,c "
    + "from table "
    + "where a = 1"

needs to be 'stripped' to:

select a,b,c
from table
where a = 1

keeping the formatting. It's a pain to strip out the code symbols for the sql editor, then put them back again by hand when I've got the sql worked out. These two functions toggle the sql back and forth from code to stripped:

// reads the file with the code quoted sql, strips code symbols, dumps to FSI
let stripForSql fileName = 
    File.ReadAllText(fileName)
    |> (fun s -> Regex.Replace(s, "\+(\s*)\"", "")) 
    |> (fun s -> s.Replace("\"", ""))
    |> (fun s -> Regex.Replace(s, ";$", "")) // end of line semicolons
    |> (fun s -> Regex.Replace(s, "//.+", "")) // get rid of any comments
    |> (fun s -> printfn "%s" s)

then when you are ready to put it back into your code source file:

let prepFromSql fileName = 
    File.ReadAllText(fileName)
    |> (fun s -> Regex.Replace(s, @"\r\n", " \"\r\n+\"")) // matches newline 
    |> (fun s -> Regex.Replace(s, @"\A", " \"")) 
    |> (fun s -> Regex.Replace(s, @"\z", " \"")) 
    |> (fun s -> printfn "%s" s)

I'd love to get rid of the input file but can't even begin to grok how to make that happen. anyone?

edit:

I figured out how to eliminate the requirement of a file for these functions by adding a windows forms dialog input/output. Too much code to show, but for those who would like to do such a thing, that's how I solved it.

查看更多
甜甜的少女心
3楼-- · 2020-01-24 06:31

List comprehensions for float

This [23.0 .. 1.0 .. 40.0] was marked as deprecated a few versions backed.

But apparently, this works:

let dl = 9.5 / 11.
let min = 21.5 + dl
let max = 40.5 - dl

let a = [ for z in min .. dl .. max -> z ]
let b = a.Length

(BTW, there's a floating point gotcha in there. Discovered at fssnip - the other place for F# snippets)

查看更多
做个烂人
4楼-- · 2020-01-24 06:32

Generic memoization, courtesy of the man himself

let memoize f = 
  let cache = System.Collections.Generic.Dictionary<_,_>(HashIdentity.Structural)
  fun x ->
    let ok, res = cache.TryGetValue(x)
    if ok then res
    else let res = f x
         cache.[x] <- res
         res

Using this, you could do a cached reader like so:

let cachedReader = memoize reader
查看更多
放我归山
5楼-- · 2020-01-24 06:32

F# Map <-> C# Dictionary

(I know, I know, System.Collections.Generic.Dictionary isn't really a 'C#' dictionary)

C# to F#

(dic :> seq<_>)                        //cast to seq of KeyValuePair
    |> Seq.map (|KeyValue|)            //convert KeyValuePairs to tuples
    |> Map.ofSeq                       //convert to Map

(From Brian, here, with improvement proposed by Mauricio in comment below. (|KeyValue|) is an active pattern for matching KeyValuePair - from FSharp.Core - equivalent to (fun kvp -> kvp.Key, kvp.Value))

Interesting alternative

To get all of the immutable goodness, but with the O(1) lookup speed of Dictionary, you can use the dict operator, which returns an immutable IDictionary (see this question).

I currently can't see a way to directly convert a Dictionary using this method, other than

(dic :> seq<_>)                        //cast to seq of KeyValuePair
    |> (fun kvp -> kvp.Key, kvp.Value) //convert KeyValuePairs to tuples
    |> dict                            //convert to immutable IDictionary

F# to C#

let dic = Dictionary()
map |> Map.iter (fun k t -> dic.Add(k, t))
dic

What is weird here is that FSI will report the type as (for example):

val it : Dictionary<string,int> = dict [("a",1);("b",2)]

but if you feed dict [("a",1);("b",2)] back in, FSI reports

IDictionary<string,int> = seq[[a,1] {Key = "a"; Value = 1; } ...
查看更多
倾城 Initia
6楼-- · 2020-01-24 06:32

Pairwise and pairs

I always expect Seq.pairwise to give me [(1,2);(3;4)] and not [(1,2);(2,3);(3,4)]. Given that neither exist in List, and that I needed both, here's the code for future reference. I think they're tail recursive.

//converts to 'windowed tuples' ([1;2;3;4;5] -> [(1,2);(2,3);(3,4);(4,5)])
let pairwise lst = 
    let rec loop prev rem acc = 
       match rem with
       | hd::tl -> loop hd tl ((prev,hd)::acc)
       | _ -> List.rev acc
    loop (List.head lst) (List.tail lst) []

//converts to 'paged tuples' ([1;2;3;4;5;6] -> [(1,2);(3,4);(5,6)])    
let pairs lst = 
    let rec loop rem acc = 
       match rem with
       | l::r::tl -> loop tl ((l,r)::acc)
       | l::[] -> failwith "odd-numbered list" 
       | _ -> List.rev acc
    loop lst []
查看更多
beautiful°
7楼-- · 2020-01-24 06:34

Simple read-write to text files

These are trivial, but make file access pipeable:

open System.IO
let fileread f = File.ReadAllText(f)
let filewrite f s = File.WriteAllText(f, s)
let filereadlines f = File.ReadAllLines(f)
let filewritelines f ar = File.WriteAllLines(f, ar)

So

let replace f (r:string) (s:string) = s.Replace(f, r)

"C:\\Test.txt" |>
    fileread |>
    replace "teh" "the" |>
    filewrite "C:\\Test.txt"

And combining that with the visitor quoted in the question:

let filereplace find repl path = 
    path |> fileread |> replace find repl |> filewrite path

let recurseReplace root filter find repl = 
    visitor root filter |> Seq.iter (filereplace find repl)

Update Slight improvement if you want to be able to read 'locked' files (e.g. csv files which are already open in Excel...):

let safereadall f = 
   use fs = new FileStream(f, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
   use sr = new StreamReader(fs, System.Text.Encoding.Default)
   sr.ReadToEnd()

let split sep (s:string) = System.Text.RegularExpressions.Regex.Split(s, sep)

let fileread f = safereadall f
let filereadlines f = f |> safereadall |> split System.Environment.NewLine  
查看更多
登录 后发表回答