At work I am developing an application using hand-coded Swing, and I've found that I have an easier time reading, writing, and maintaining hierarchical component creation using code blocks like:
JPanel mainPanel = new JPanel(new BorderLayout());
{
JLabel centerLabel = new JLabel();
centerLabel.setText("Hello World");
mainPanel.add(centerLabel, BorderLayout.CENTER);
}
{
JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0,0));
{
JLabel label1 = new JLabel();
label1.setText("Hello");
southPanel.add(label1);
}
{
JLabel label2 = new JLabel();
label2.setText("World");
southPanel.add(label2);
}
mainPanel.add(southPanel, BorderLayout.SOUTH);
}
So I wondered what that would look like using F# and WinForms, and I translated this code example (go to "Browse Code" tab, then click on F# -> editor.fsx in tree view) in two ways and I'd like feedback on which of the following two ways seems better. The first uses code blocks like the approach shown in Swing, and I think it is nice but a bit cluttered with begin ... end
everywhere:
open System
open System.Windows.Forms
let form = new Form()
form.Width <- 400
form.Height <- 300
form.Visible <- true
form.Text <- "Hello World Form"
begin
// Menu bar, menus
let mMain = new MainMenu()
begin
let mFile = new MenuItem("&File")
begin
let miQuit = new MenuItem("&Quit")
miQuit.Click.Add(fun _ -> form.Close())
mFile.MenuItems.Add(miQuit) |> ignore
end
mMain.MenuItems.Add(mFile) |> ignore
end
form.Menu <- mMain
end
begin
// RichTextView
let textB = new RichTextBox()
textB.Dock <- DockStyle.Fill
textB.Text <- "Hello World\n\nOK."
form.Controls.Add(textB)
end
The second approach is interesting because it takes advantage of the fact that in F# everything is an expression, but it seems a little harder for me to follow and I'm not sure if that's just because I'm used to the code block approach:
open System
open System.Windows.Forms
let form = new Form()
form.Width <- 400
form.Height <- 300
form.Visible <- true
form.Text <- "Hello World Form"
form.Menu <-
// Menu bar, menus
let mMain = new MainMenu()
mMain.MenuItems.Add(
let mFile = new MenuItem("&File")
mFile.MenuItems.Add(
let miQuit = new MenuItem("&Quit")
miQuit.Click.Add(fun _ -> form.Close())
miQuit
) |> ignore
mFile
) |> ignore
mMain
form.Controls.Add(
// RichTextView
let textB = new RichTextBox()
textB.Dock <- DockStyle.Fill
textB.Text <- "Hello World\n\nOK."
textB
)
Or maybe someone can suggest some other approach for structuring F# + WinForms code which is in this same spirit (i.e. emphasizing the component hierarchy both visually and in limiting scope).
Update
I recently learned that in F#, begin ... end
and ( ... )
are interchangeable. Which means that my concern with the first example is eliminated since I can write:
open System
open System.Windows.Forms
let form = new Form()
form.Width <- 400
form.Height <- 300
form.Visible <- true
form.Text <- "Hello World Form"
(
// Menu bar, menus
let mMain = new MainMenu()
(
let mFile = new MenuItem("&File")
(
let miQuit = new MenuItem("&Quit")
miQuit.Click.Add(fun _ -> form.Close())
mFile.MenuItems.Add(miQuit) |> ignore
)
mMain.MenuItems.Add(mFile) |> ignore
)
form.Menu <- mMain
)
(
// RichTextView
let textB = new RichTextBox()
textB.Dock <- DockStyle.Fill
textB.Text <- "Hello World\n\nOK."
form.Controls.Add(textB)
)
And indeed, the second example could be written as
open System
open System.Windows.Forms
let form = new Form()
form.Width <- 400
form.Height <- 300
form.Visible <- true
form.Text <- "Hello World Form"
form.Menu <-
// Menu bar, menus
let mMain = new MainMenu()
mMain.MenuItems.Add begin
let mFile = new MenuItem("&File")
mFile.MenuItems.Add begin
let miQuit = new MenuItem("&Quit")
miQuit.Click.Add(fun _ -> form.Close())
miQuit
end |> ignore
mFile
end |> ignore
mMain
form.Controls.Add begin
// RichTextView
let textB = new RichTextBox()
textB.Dock <- DockStyle.Fill
textB.Text <- "Hello World\n\nOK."
textB
end
So syntax isn't an issue anymore, but there still is a difference in style.
-
\$\begingroup\$ I'd argue it looks difficult to follow because of the way you're using F# - WinForms certainly isn't its choice arena, and while there may be coincidental conveniences, it is inherently less concise for this purpose than the variety of .NET languages available that had UI in mind but mostly providing a generally explanatory syntax, a little like English. \$\endgroup\$Grant Thomas– Grant Thomas2011年04月11日 10:37:52 +00:00Commented Apr 11, 2011 at 10:37
-
1\$\begingroup\$ @Mr. Disappointment: I respectfully disagree. It may not be obvious how to do it at first, but declaring hierarchies of objects in F# benefits from nested declarations. C-like languages like C# allow it only through blocks, resulting in a verbose use of braces. \$\endgroup\$Joh– Joh2011年06月08日 08:00:52 +00:00Commented Jun 8, 2011 at 8:00
1 Answer 1
Here is my take on your code:
open System
open System.Windows.Forms
let form =
new Form(
Width = 400,
Height = 300,
Visible = true,
Text = "Hello World Form")
// Menu bar, menus
let mMain =
let miQuit = new MenuItem("&Quit")
miQuit.Click.Add(fun _ -> form.Close())
let mFile = new MenuItem("&File")
mFile.MenuItems.Add(miQuit) |> ignore
let mMain = new MainMenu()
mMain.MenuItems.Add(mFile) |> ignore
mMain
form.Menu <- mMain
// RichTextView
let textB = new RichTextBox(Dock = DockStyle.Fill, Text = "Hello World\n\nOK.")
form.Controls.Add(textB)
I tried to improve by assigning properties directly in the constructor call. I also used nested let declarations to reflect the hierarchy. Finally, I used indentation instead of explicit begin/end or parens to delimit blocks.
Note also how I used mMain within the declaration of the top-level mMain. Others might have used tmp, I think using the same name makes the intent clear. It might be a bit confusing for new-comers, though.