F# Interactive Pass ParamArray of Functions

2019-08-29 04:00发布

问题:

I'm trying to expand on James Hugard's post How do I plot a data series in F#? and I'm running into a glitch when using a variable number of function arguments. If I specifically name out the functions for Hugard's LineChartForm like in this example, the code works as expected when loaded into the F# Interactive Interpreter:

(* This code is based off of a Stack Overflow post by 
   James Hugard @ 
   https://stackoverflow.com/questions/3276357/how-do-i-plot-a-data-series-in-f
   *) 
module HuggardPlot

#r "System.Windows.Forms.DataVisualization" 
open System.Windows.Forms
open System.Windows.Forms.DataVisualization.Charting 

type LineChartForm( title, minX, maxX, func1, func2) =
   inherit Form( Text=title )

   let chart = new Chart(Dock=DockStyle.Fill)
   let area = new ChartArea(Name="Area1")

   (* Add the first plot Hugard style *) 
   let series = new Series()
   do series.ChartType <- SeriesChartType.Line
   do for i in minX .. maxX do 
      series.Points.AddXY(i, func1(i)) |> ignore   
   do series.ChartArea <- "Area1"
   do chart.Series.Add( series )

   (* Add the second plot Hugard style *) 
   let series2 = new Series()
   do series2.ChartType <- SeriesChartType.Line
   do for i in minX .. maxX do 
      series2.Points.AddXY(i, func2(i)) |> ignore   
   do series2.ChartArea <- "Area1"
   do chart.Series.Add( series2 )

   (* Add area1 to the plot *) 
   do chart.ChartAreas.Add(area)
   do base.Controls.Add( chart ) 

(* Convenience method to make it easier to plot functions *) 
let plotTwoFunctions minX maxX f1 f2 = 
   let LCF = new LineChartForm("lines", minX, maxX, f1, f2); 
   LCF.Show();

On the other hand, if I try to use the technique in the Parameter Arrays section of http://msdn.microsoft.com/en-us/library/dd233213.aspx to pass a variable number of functions via a ParamArray with the following code:

(* This code is based off of a Stack Overflow post by 
   James Hugard @ 
   https://stackoverflow.com/questions/3276357/how-do-i-plot-a-data-series-in-f
   *) 
module HuggardPlot

#r "System.Windows.Forms.DataVisualization" 
open System
open System.Windows.Forms
open System.Windows.Forms.DataVisualization.Charting 

type LineChartForm(
                    title, 
                    minX, 
                    maxX, 
                    [<ParamArray>] funcs: Object[]) =
   inherit Form( Text=title )

   let chart = new Chart(Dock=DockStyle.Fill)
   let area = new ChartArea(Name="Area1")

   do for func in funcs do 
      (* Add the first plot Hugard style *) 
      let series = new Series()
      do series.ChartType <- SeriesChartType.Line
      do for i in minX .. maxX do 
         series.Points.AddXY(i, func(i)) |> ignore   
      do series.ChartArea <- "Area1"
      do chart.Series.Add( series )      

   (* Add area1 to the plot *) 
   do chart.ChartAreas.Add(area)
   do base.Controls.Add( chart ) 

(* Convenience method to make it easier to plot functions *) 
let plotTwoFunctions minX maxX f1 f2 = 
   let LCF = new LineChartForm("lines", minX, maxX, f1, f2); 
   LCF.Show();

I get an error on line 27
series.Points.AddXY(i, func(i)) |> ignore
stating "This value is not a function and cannot be applied" with a red squiggly under "func".

I suspect this has something to do with the fact that I am using [<ParamArray>] funcs: Object[] as a parameter definition to Mr. Hugard's LineChartForm Type.

What should I change [<ParamArray>] funcs: Object[] to so line 27 of the second code example will recognize "func" as a function?

回答1:

If you want to create a method that takes params array of functions, then you need to define the parameter as [<ParamArray>] funcs: (float -> float)[]. In your original version, the type was Object[] meaning that individual elements of the array were objects - if you change the type of elements to functions, then F# will recognize that you can call them.

However, if you're simply interested in charting F# data, then there is already a good wrapper for the WinForms DataVisualization library called F# Chart and available on GitHub. There is also a comprehensive documentation available on MSDN (if you use the latest version of the library, then FSharpChart has been renamed to just Chart).

To create a plot comparing two functions over a specified range, you can simply write:

let plotTwoFunctions minX maxX f1 f2 = 
  Chart.Combine
    [ Chart.Line [ for x in minX .. maxX -> x, f1 x ]
      Chart.Line [ for x in minX .. maxX -> x, f2 x ] ]

This creates two individual line charts (with data generated by the two functions) and then combines them into a single chart using Chart.Combine.