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条回答
在下西门庆
2楼-- · 2020-01-24 06:34

Creating XElements

Nothing amazing, but I keep getting caught out by the implicit conversion of XNames:

#r "System.Xml.Linq.dll"
open System.Xml.Linq

//No! ("type string not compatible with XName")
//let el = new XElement("MyElement", "text") 

//better
let xn s = XName.op_Implicit s
let el = new XElement(xn "MyElement", "text")

//or even
let xEl s o = new XElement(xn s, o)
let el = xEl "MyElement" "text"
查看更多
贼婆χ
3楼-- · 2020-01-24 06:36

Multi-Line Strings

This is pretty trivial, but it seems to be a feature of F# strings that is not widely known.

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

This produces:

val sql : string = "select a,b,c from table where a = 1"

When the F# compiler sees a back-slash followed by a carriage return inside a string literal, it will remove everything from the back-slash to the first non-space character on the next line. This allows you to have multi-line string literals that line up, without using a bunch of string concatenation.

查看更多
Rolldiameter
4楼-- · 2020-01-24 06:36

Scale/Ratio function builder

Again, trivial, but handy.

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

Example:

[<Measure>] type m
[<Measure>] type px

let screenSize = (0.<px>, 300.<px>)
let displayRange = (100.<m>, 200.<m>)
let scaleToScreen = scale displayRange screenSize

scaleToScreen 120.<m> //-> 60.<px>
查看更多
贼婆χ
5楼-- · 2020-01-24 06:37

Transposing a list (seen on Jomo Fisher's blog)

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

And here is a tail-recursive version which (from my sketchy profiling) is mildly slower, but has the advantage of not throwing a stack overflow when the inner lists are longer than 10000 elements (on my machine):

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

If I was clever, I'd try and parallelise it with async...

查看更多
Lonely孤独者°
6楼-- · 2020-01-24 06:37

DataSetExtensions for F#, DataReaders

System.Data.DataSetExtensions.dll adds the ability to treat a DataTable as an IEnumerable<DataRow> as well as unboxing the values of individual cells in a way that gracefully handles DBNull by supporting System.Nullable. For example, in C# we can get the value of an integer column that contains nulls, and specify that DBNull should default to zero with a very concise syntax:

var total = myDataTable.AsEnumerable()
                       .Select(row => row.Field<int?>("MyColumn") ?? 0)
                       .Sum();

There are two areas where DataSetExtensions are lacking, however. First, it doesn't support IDataReader and second, it doesn't support the F# option type. The following code does both - it allows an IDataReader to be treated as a seq<IDataRecord>, and it can unbox values from either a reader or a dataset, with support for F# options or System.Nullable. Combined with the option-coalescing operator in another answer, this allows for code such as the following when working with a DataReader:

let total =
    myReader.AsSeq
    |> Seq.map (fun row -> row.Field<int option>("MyColumn") |? 0)
    |> Seq.sum

Perhaps a more idiomatic F# way of ignoring database nulls would be...

let total =
    myReader.AsSeq
    |> Seq.choose (fun row -> row.Field<int option>("MyColumn"))
    |> Seq.sum

Further, the extension methods defined below are usable from both F# and from C#/VB.

open System
open System.Data
open System.Reflection
open System.Runtime.CompilerServices
open Microsoft.FSharp.Collections

/// Ported from System.Data.DatasetExtensions.dll to add support for the Option type.
[<AbstractClass; Sealed>]
type private UnboxT<'a> private () =

    // This class generates a converter function based on the desired output type,
    // and then re-uses the converter function forever. Because the class itself is generic,
    // different output types get different cached converter functions.

    static let referenceField (value:obj) =
        if value = null || DBNull.Value.Equals(value) then
            Unchecked.defaultof<'a>
        else
            unbox value

    static let valueField (value:obj) =
        if value = null || DBNull.Value.Equals(value) then
            raise <| InvalidCastException("Null cannot be converted to " + typeof<'a>.Name)
        else
            unbox value

    static let makeConverter (target:Type) methodName =
        Delegate.CreateDelegate(typeof<Converter<obj,'a>>,
                                typeof<UnboxT<'a>>
                                    .GetMethod(methodName, BindingFlags.NonPublic ||| BindingFlags.Static)
                                    .MakeGenericMethod([| target.GetGenericArguments().[0] |]))
        |> unbox<Converter<obj,'a>>
        |> FSharpFunc.FromConverter

    static let unboxFn =
        let theType = typeof<'a>
        if theType.IsGenericType && not theType.IsGenericTypeDefinition then
            let genericType = theType.GetGenericTypeDefinition()
            if typedefof<Nullable<_>> = genericType then
                makeConverter theType "NullableField"
            elif typedefof<option<_>> = genericType then
                makeConverter theType "OptionField"
            else
                invalidOp "The only generic types supported are Option<T> and Nullable<T>."
        elif theType.IsValueType then
            valueField
        else
            referenceField

    static member private NullableField<'b when 'b : struct and 'b :> ValueType and 'b:(new:unit -> 'b)> (value:obj) =
        if value = null || DBNull.Value.Equals(value) then
            Nullable<_>()
        else
            Nullable<_>(unbox<'b> value)

    static member private OptionField<'b> (value:obj) =
        if value = null || DBNull.Value.Equals(value) then
            None
        else
            Some(unbox<'b> value)

    static member inline Unbox =
        unboxFn

/// F# data-related extension methods.
[<AutoOpen>]
module FsDataEx =

    type System.Data.IDataReader with

        /// Exposes a reader's current result set as seq<IDataRecord>.
        /// Reader is closed when sequence is fully enumerated.
        member this.AsSeq =
            seq { use reader = this
                  while reader.Read() do yield reader :> IDataRecord }

        /// Exposes all result sets in a reader as seq<seq<IDataRecord>>.
        /// Reader is closed when sequence is fully enumerated.
        member this.AsMultiSeq =
            let rowSeq (reader:IDataReader)  =
                seq { while reader.Read() do yield reader :> IDataRecord }
            seq {
                use reader = this
                yield rowSeq reader
                while reader.NextResult() do
                    yield rowSeq reader
            }

        /// Populates a new DataSet with the contents of the reader. Closes the reader after completion.
        member this.ToDataSet () =
            use reader = this
            let dataSet = new DataSet(RemotingFormat=SerializationFormat.Binary, EnforceConstraints=false)
            dataSet.Load(reader, LoadOption.OverwriteChanges, [| "" |])
            dataSet

    type System.Data.IDataRecord with

        /// Gets a value from the record by name. 
        /// DBNull and null are returned as the default value for the type.
        /// Supports both nullable and option types.
        member this.Field<'a> (fieldName:string) =
            this.[fieldName] |> UnboxT<'a>.Unbox

        /// Gets a value from the record by column index. 
        /// DBNull and null are returned as the default value for the type.
        /// Supports both nullable and option types.
        member this.Field<'a> (ordinal:int) =
            this.GetValue(ordinal) |> UnboxT<'a>.Unbox

    type System.Data.DataRow with

        /// Identical to the Field method from DatasetExtensions, but supports the F# Option type.
        member this.Field2<'a> (columnName:string) =
            this.[columnName] |> UnboxT<'a>.Unbox

        /// Identical to the Field method from DatasetExtensions, but supports the F# Option type.
        member this.Field2<'a> (columnIndex:int) =
            this.[columnIndex] |> UnboxT<'a>.Unbox

        /// Identical to the Field method from DatasetExtensions, but supports the F# Option type.
        member this.Field2<'a> (column:DataColumn) =
            this.[column] |> UnboxT<'a>.Unbox

        /// Identical to the Field method from DatasetExtensions, but supports the F# Option type.
        member this.Field2<'a> (columnName:string, version:DataRowVersion) =
            this.[columnName, version] |> UnboxT<'a>.Unbox

        /// Identical to the Field method from DatasetExtensions, but supports the F# Option type.
        member this.Field2<'a> (columnIndex:int, version:DataRowVersion) =
            this.[columnIndex, version] |> UnboxT<'a>.Unbox

        /// Identical to the Field method from DatasetExtensions, but supports the F# Option type.
        member this.Field2<'a> (column:DataColumn, version:DataRowVersion) =
            this.[column, version] |> UnboxT<'a>.Unbox


/// C# data-related extension methods.
[<Extension; AbstractClass; Sealed>]
type CsDataEx private () =

    /// Populates a new DataSet with the contents of the reader. Closes the reader after completion.
    [<Extension>]    
    static member ToDataSet(this:IDataReader) =
        this.ToDataSet()

    /// Exposes a reader's current result set as IEnumerable{IDataRecord}.
    /// Reader is closed when sequence is fully enumerated.
    [<Extension>]
    static member AsEnumerable(this:IDataReader) =
        this.AsSeq

    /// Exposes all result sets in a reader as IEnumerable{IEnumerable{IDataRecord}}.
    /// Reader is closed when sequence is fully enumerated.
    [<Extension>]
    static member AsMultipleEnumerable(this:IDataReader) =
        this.AsMultiSeq

    /// Gets a value from the record by name. 
    /// DBNull and null are returned as the default value for the type.
    /// Supports both nullable and option types.
    [<Extension>]
    static member Field<'T> (this:IDataRecord, fieldName:string) =
        this.Field<'T>(fieldName)

    /// Gets a value from the record by column index. 
    /// DBNull and null are returned as the default value for the type.
    /// Supports both nullable and option types.
    [<Extension>]
    static member Field<'T> (this:IDataRecord, ordinal:int) =
        this.Field<'T>(ordinal)
查看更多
可以哭但决不认输i
7楼-- · 2020-01-24 06:39

Parallel map

let pmap f s =
    seq { for a in s -> async { return f s } }
    |> Async.Parallel
    |> Async.Run
查看更多
登录 后发表回答