当前位置:  开发笔记 > 编程语言 > 正文

方便的F#片段

如何解决《方便的F#片段》经验,为你挑选了15个好方法。

关于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/.



1> Juliet..:

Perl风格的正则表达式匹配

let (=~) input pattern =
    System.Text.RegularExpressions.Regex.IsMatch(input, pattern)

它允许您使用let test = "monkey" =~ "monk.+"符号匹配文本.



2> Kevin Won..:

中缀运营商

我从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"


+1:我不知道为什么,但这让我lulz :)

3> Joel Mueller..:

多行字符串

这非常简单,但它似乎是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#编译器看到一个反斜杠后跟一个字符串文字中的回车符时,它将删除从反斜杠到下一行的第一个非空格字符的所有内容.这允许您使用排成一行的多行字符串文字,而不使用一串字符串连接.


只是为了补充一点,C#style @"string"也适用于F#中的多行!
未来的FYI.你不再需要反斜杠了.

4> Benjol..:

通用备忘录,礼貌的男人自己

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


您应该将`HashIdentity.Structural`传递给`Dictionary`,否则它将使用键上的默认.NET引用相等而不是F#的结构相等.

5> Benjol..:

对文本文件的简单读写

这些都是微不足道的,但是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  



6> thr..:

对于性能密集型的东西,你需要检查null

let inline isNull o = System.Object.ReferenceEquals(o, null)
if isNull o then ... else ...

比那时快20倍

if o = null then ... else ...


另请注意,如果您使用泛型,则"o = null"需要"Equality"约束

7> James Hugard..:

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



8> Benjol..:

'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



9> Juliet..:

也许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.



10> Joel Mueller..:

选项合并运算符

我想要一个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()



11> Benjol..:

比例/比率功能构建器

再次,琐碎,但方便.

//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.



12> Benjol..:

转置列表(见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

如果我很聪明,我会尝试将其与异步并行化......



13> Benjol..:

F#Map < - > C#Dictionary

(我知道,我知道,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; } ...


在将KeyValues转换为元组时,我认为你缺少`Seq.map`.此外,您可以使用`(| KeyValue |)`而不是`fun kvp - > kvp.Key,kvp.Value`

14> Juliet..:

树排序/将树平移到列表中

我有以下二叉树:

             ___ 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]



15> Joel Mueller..:

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)


@Benjol - 这是一个编译技巧.F#编译器将`?`运算符的定义转换为一个名为`op_Dynamic`的静态类方法,该方法接受一个字符串参数.然后它将`?`运算符的使用转换为对此方法的调用,将问号后面的部分作为字符串参数.所以在运行时它们都是静态类型的,而不是动态的,它只是提供了一些很好的简洁语法,你可以定义它的行为.与`?< - `运算符的原理相同.
推荐阅读
mobiledu2402851173
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有