关于F#/功能片段已经存在两个 问题.
然而,我在这里寻找的是有用的片段,可重复使用的小"帮助"功能.或者模糊但又漂亮的模式,你永远不会记得.
就像是:
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}
我想把它作为一个方便的参考页面.因此,没有正确的答案,但希望有很多好的答案.
EDIT Tomas Petricek专门为F#片段创建了一个网站http://fssnip.net/.
Perl风格的正则表达式匹配
let (=~) input pattern = System.Text.RegularExpressions.Regex.IsMatch(input, pattern)
它允许您使用let test = "monkey" =~ "monk.+"
符号匹配文本.
中缀运营商
我从http://sandersn.com/blog//index.php/2009/10/22/infix-function-trick-for-f获得了这一点,请访问该页面了解更多详情.
如果您了解Haskell,您可能会发现自己在F#中缺少中缀糖:
// standard Haskell call has function first, then args just like F#. So obviously // here there is a function that takes two strings: string -> string -> string startsWith "kevin" "k" //Haskell infix operator via backQuotes. Sometimes makes a function read better. "kevin" `startsWith` "K"
虽然F#没有真正的"中缀"运算符,但同样的事情可以通过管道和"反管道"(谁知道这样的事情)几乎同样优雅地完成.
// F# 'infix' trick via pipelines "kevin" |> startsWith <| "K"
多行字符串
这非常简单,但它似乎是F#字符串的一个特征,并不为人所知.
let sql = "select a,b,c \ from table \ where a = 1"
这会产生:
val sql : string = "select a,b,c from table where a = 1"
当F#编译器看到一个反斜杠后跟一个字符串文字中的回车符时,它将删除从反斜杠到下一行的第一个非空格字符的所有内容.这允许您使用排成一行的多行字符串文字,而不使用一串字符串连接.
通用备忘录,礼貌的男人自己
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
使用这个,你可以这样做一个缓存的阅读器:
let cachedReader = memoize reader
对文本文件的简单读写
这些都是微不足道的,但是make file accessable:
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)
所以
let replace f (r:string) (s:string) = s.Replace(f, r) "C:\\Test.txt" |> fileread |> replace "teh" "the" |> filewrite "C:\\Test.txt"
并将其与问题中引用的访问者相结合:
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)
如果您希望能够读取"锁定"文件(例如已在Excel中打开的csv文件...),则更新稍有改进:
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
对于性能密集型的东西,你需要检查null
let inline isNull o = System.Object.ReferenceEquals(o, null) if isNull o then ... else ...
比那时快20倍
if o = null then ... else ...
Active Patterns,又名"Banana Splits",是一个非常方便的结构,让人们可以匹配多个正则表达式模式.这很像AWK,但没有DFA的高性能,因为模式按顺序匹配,直到成功.
#light open System open System.Text.RegularExpressions let (|Test|_|) pat s = if (new Regex(pat)).IsMatch(s) then Some() else None let (|Match|_|) pat s = let opt = RegexOptions.None let re = new Regex(pat,opt) let m = re.Match(s) if m.Success then Some(m.Groups) else None
一些使用示例:
let HasIndefiniteArticle = function | Test "(?: |^)(a|an)(?: |$)" _ -> true | _ -> false type Ast = | IntVal of string * int | StringVal of string * string | LineNo of int | Goto of int let Parse = function | Match "^LET\s+([A-Z])\s*=\s*(\d+)$" g -> IntVal( g.[1].Value, Int32.Parse(g.[2].Value) ) | Match "^LET\s+([A-Z]\$)\s*=\s*(.*)$" g -> StringVal( g.[1].Value, g.[2].Value ) | Match "^(\d+)\s*:$" g -> LineNo( Int32.Parse(g.[1].Value) ) | Match "^GOTO \s*(\d+)$" g -> Goto( Int32.Parse(g.[1].Value) ) | s -> failwithf "Unexpected statement: %s" s
'Unitize'一个不处理单位
的FloatWithMeasure
函数使用函数http://msdn.microsoft.com/en-us/library/ee806527(VS.100).aspx.
let unitize (f:float -> float) (v:float<'u>) = LanguagePrimitives.FloatWithMeasure<'u> (f (float v))
例:
[] type m [ ] type kg let unitize (f:float -> float) (v:float<'u>) = LanguagePrimitives.FloatWithMeasure<'u> (f (float v)) //this function doesn't take units let badinc a = a + 1. //this one does! let goodinc v = unitize badinc v goodinc 3. goodinc 3.
旧版本:
let unitize (f:float -> float) (v:float<'u>) = let unit = box 1. :?> float<'u> unit * (f (v/unit))
荣誉给KVB
也许monad
type maybeBuilder() = member this.Bind(v, f) = match v with | None -> None | Some(x) -> f x member this.Delay(f) = f() member this.Return(v) = Some v let maybe = maybeBuilder()
这是一个简短的介绍,为不知情的monads.
选项合并运算符
我想要一个defaultArg
语法更接近C#null-coalescing运算符的函数版本??
.这使我可以使用非常简洁的语法从Option中获取值,同时提供默认值.
/// Option-coalescing operator - this is like the C# ?? operator, but works with /// the Option type. /// Warning: Unlike the C# ?? operator, the second parameter will always be /// evaluated. /// Example: let foo = someOption |? default let inline (|?) value defaultValue = defaultArg value defaultValue /// Option-coalescing operator with delayed evaluation. The other version of /// this operator always evaluates the default value expression. If you only /// want to create the default value when needed, use this operator and pass /// in a function that creates the default. /// Example: let foo = someOption |?! (fun () -> new Default()) let inline (|?!) value f = match value with Some x -> x | None -> f()
比例/比率功能构建器
再次,琐碎,但方便.
//returns a function which will convert from a1-a2 range to b1-b2 range let scale (a1:float<'u>, a2:float<'u>) (b1:float<'v>,b2:float<'v>) = let m = (b2 - b1)/(a2 - a1) //gradient of line (evaluated once only..) (fun a -> b1 + m * (a - a1))
例:
[] type m [ ] type px let screenSize = (0. , 300. ) let displayRange = (100. , 200. ) let scaleToScreen = scale displayRange screenSize scaleToScreen 120. //-> 60.
转置列表(见Jomo Fisher的博客)
///Given list of 'rows', returns list of 'columns' let rec transpose lst = match lst with | (_::_)::_ -> List.map List.head lst :: transpose (List.map List.tail lst) | _ -> [] transpose [[1;2;3];[4;5;6];[7;8;9]] // returns [[1;4;7];[2;5;8];[3;6;9]]
这是一个尾递归版本(从我粗略的分析)稍微慢一些,但是当内部列表超过10000个元素(在我的机器上)时,它具有不抛出堆栈溢出的优点:
let transposeTR lst = let rec inner acc lst = match lst with | (_::_)::_ -> inner (List.map List.head lst :: acc) (List.map List.tail lst) | _ -> List.rev acc inner [] lst
如果我很聪明,我会尝试将其与异步并行化......
(我知道,我知道,System.Collections.Generic.Dictionary并不是真正的'C#'字典)
C#到F#
(dic :> seq<_>) //cast to seq of KeyValuePair |> Seq.map (|KeyValue|) //convert KeyValuePairs to tuples |> Map.ofSeq //convert to Map
(来自Brian,在这里,Mauricio在下面的评论中提出了改进.(|KeyValue|)
是一个匹配KeyValuePair的活跃模式 - 来自FSharp.Core - 相当于(fun kvp -> kvp.Key, kvp.Value)
)
有趣的选择
要获得所有不可变的优点,但使用Dictionary的O(1)查找速度,您可以使用dict
运算符,它返回一个不可变的IDictionary(请参阅此问题).
我目前看不到使用此方法直接转换Dictionary的方法,除了
(dic :> seq<_>) //cast to seq of KeyValuePair |> (fun kvp -> kvp.Key, kvp.Value) //convert KeyValuePairs to tuples |> dict //convert to immutable IDictionary
F#到C#
let dic = Dictionary() map |> Map.iter (fun k t -> dic.Add(k, t)) dic
奇怪的是,FSI将报告类型(例如):
val it : Dictionary= dict [("a",1);("b",2)]
但如果你反馈dict [("a",1);("b",2)]
,FSI报道
IDictionary= seq[[a,1] {Key = "a"; Value = 1; } ...
树排序/将树平移到列表中
我有以下二叉树:
___ 77 _ / \ ______ 47 __ 99 / \ 21 _ 54 \ / \ 43 53 74 / 39 / 32
其代表如下:
type 'a tree = | Node of 'a tree * 'a * 'a tree | Nil let myTree = Node (Node (Node (Nil,21,Node (Node (Node (Nil,32,Nil),39,Nil),43,Nil)),47, Node (Node (Nil,53,Nil),54,Node (Nil,74,Nil))),77,Node (Nil,99,Nil))
压扁树的简单方法是:
let rec flatten = function | Nil -> [] | Node(l, a, r) -> flatten l @ a::flatten r
这不是尾递归的,我相信@
运算符会使它为O(n log n)或O(n ^ 2),其中包含不平衡的二叉树.稍微调整一下,我想出了这个尾递归的O(n)版本:
let flatten2 t = let rec loop acc c = function | Nil -> c acc | Node(l, a, r) -> loop acc (fun acc' -> loop (a::acc') c l) r loop [] (fun x -> x) t
这是fsi中的输出:
> flatten2 myTree;; val it : int list = [21; 32; 39; 43; 47; 53; 54; 74; 77; 99]
LINQ-to-XML助手
namespace System.Xml.Linq // hide warning about op_Explicit #nowarn "77" [] module XmlUtils = /// Converts a string to an XName. let xn = XName.op_Implicit /// Converts a string to an XNamespace. let xmlns = XNamespace.op_Implicit /// Gets the string value of any XObject subclass that has a Value property. let inline xstr (x : ^a when ^a :> XObject) = (^a : (member get_Value : unit -> string) x) /// Gets a strongly-typed value from any XObject subclass, provided that /// an explicit conversion to the output type has been defined. /// (Many explicit conversions are defined on XElement and XAttribute) /// Example: let value:int = xval foo let inline xval (x : ^a when ^a :> XObject) : ^b = ((^a or ^b) : (static member op_Explicit : ^a -> ^b) x) /// Dynamic lookup operator for getting an attribute value from an XElement. /// Returns a string option, set to None if the attribute was not present. /// Example: let value = foo?href /// Example with default: let value = defaultArg foo?Name " " let (?) (el:XElement) (name:string) = match el.Attribute(xn name) with | null -> None | att -> Some(att.Value) /// Dynamic operator for setting an attribute on an XElement. /// Example: foo?href <- "http://www.foo.com/" let (?<-) (el:XElement) (name:string) (value:obj) = el.SetAttributeValue(xn name, value)