3

I found a quick project I thought would be perfect for learning F#. However I just cannot wrap my brain around it for some reason. After hours of tutorials and even some movies I still just... don't get it.

So I wanted to start versioning our stored procedures at work using Enterprise Manager "Generate Scripts" and wanted to blank out the script date. I gave up and did it in C# but now I'm REALLY curious and am hoping for some insight.

I am not completely empty handed, here is my C# code:

string path = @"C:\Subversion\CompanyName\SQL\DBName";
string fileSpec = "*.sql";
string pattern = @"Script Date: \d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}";
string replace = "Script Date: ";

foreach (var file in Directory.EnumerateFiles(path, fileSpec))
{
    string content = File.ReadAllText(file);

    content = Regex.Replace(content, pattern, replace);

    File.WriteAllText(file, content, Encoding.Unicode);
}

I am guessing there is some cool looking 2-3 line solution in F#... I'd love to see it.

Thx for the tips! I have updated it to match what is being done below to make the visual comparison potentially more enlightening. Also great comments below.

  • 6
    I know I shouldn't reward such a question but my sense of humor is overriding my sense of good community membership, so +1 for "F# hurts my brain". – David Oct 26 '11 at 19:13
  • You translated the F# back into C#. – gradbot Oct 27 '11 at 01:54
  • LOL... yeah, huh. :) Actually, I 'translated' the considerably more appropriate .NET calls back to the body of original foreach loop to help illustrate the point. ildjarn suggested I note. I noted. ;) – user1015250 Oct 27 '11 at 02:35
  • You might like this: http://stackoverflow.com/questions/833180/handy-f-snippets/833580#833580 – Benjol Nov 01 '11 at 06:43

3 Answers3

11
let path = @"C:\Subversion\CompanyName\SQL\DBName"
let fileSpec = "*.sql"
let pattern = @"Script Date: \d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}"
let replace = "Script Date: "

Directory.EnumerateFiles (path, fileSpec)
|> Seq.iter (fun file ->
    let content = Regex.Replace (File.ReadAllText file, pattern, replace)
    File.WriteAllText (file, content, Encoding.Unicode))

Note that your C# code is (EDIT: was) also far more verbose than necessary.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • 2
    You could save one more line by using `for` instead of `Seq.iter`. Why use a library function when there is a built-in language construct? – Tomas Petricek Oct 26 '11 at 20:10
  • 1
    @Tomas : Personal preference -- I don't like using imperative constructs in F# when I can avoid it. And I could also save one line by putting `Seq.iter` on the same line as the call to `Directory.EnumerateFiles`. ;-] – ildjarn Oct 26 '11 at 20:13
  • @ildjarn I agree - it's definitely a personal preference thing. I guess I just accept the fact that some things in F# (and .NET) are imperative. After all, `for` itself isn't really imperative (not more than `Seq.iter`). The part that is really imperative is `File.WriteAllText` and that's just a way to get some actual work done! – Tomas Petricek Oct 26 '11 at 20:36
  • 3
    @Tomas : Indeed, but once you get sucked into using `for`/`while`, their weaknesses become readily apparent, namely lack of `continue` and `break`. I.e., if one needed to break out of the loop early and had already written the loop using `for..in`, then they'd have to rewrite it using either a recursive function or a higher-order function; however if one were using `Seq.iter` to begin with, it's painless to switch to `Seq.tryPick` and pipe the result to `ignore`. At least that's my rationale. ;-] – ildjarn Oct 26 '11 at 20:47
  • Oooh File.WriteAllText... score! Well now I know about that one. :) Sorry I'm just not that experienced dealing with actual file manipulation to know the tricks all the cool kids use. SQL server typically talks to the disk for me. To be honest its 100% complete copypasta from stackoverflow, i just needed it done fast after blowing a couple hours trying to figure out F#. And btw I LOVE the F# implementation its exactally what I was hoping for. I really appreciate you all taking the time to take such a lame first question seriously. – user1015250 Oct 27 '11 at 00:05
  • @ildjarn Very good point about `continue` and `break`. Changing `iter` to some other function easily is quite valuable there. On the other hand, if you're using `for`, you can wrap it nicely inside sequence expression (if you decide to return values instead of perform imperative actions). If you prefer not using syntactic sugar (a personal choice :-)), then you'd probably use `map` instead. – Tomas Petricek Oct 27 '11 at 09:40
6

Pretty much all you need to do is to replace type name in variable declarations (where you could write var in C#) with the let keyword, remove all semicolons and curly braces and replace foreach with
for file in files do. You're also using some namespaces, so in F#, you need something like:

open System.IO
open System.Text.RegularExpressions

You're using one mutable variable (content), but you don't really need to mutate it. You can just use a different variable (such as newContent) to store the result of replacement. So, the middle part will be:

let content = File.ReadAllText(file)  // No need for a stream reader here!

let pattern = @"Script Date: \d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}"
let replace = "Script Date: "    
let newContent = Regex.Replace(content, pattern, replace)

You could also use the same variable name, which means that you're declaring a new (immutable) variable that hides the previous one in the rest of the scope (see this SO answer for more information)

I would also change the last bit (even in C#!) to close the stream even if an exception occurs. In C#, you would use using. To write the same thing in F#, you can use the use keyword:

use fileStream = new FileStream(file, FileMode.Truncate, FileAccess.Write, FileShare.None)
use writer = new StreamWriter(fileStream, Encoding.Unicode)
writer.Write(content)
writer.Flush()

The use keyword ensures that the object is disposed (closed) when it goes out of scope - that is, at the end of the for loop iteration (in this case).

Community
  • 1
  • 1
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thank you for your answer. It is definitely useful to see how C# statements can be directly ported to F# statements. ildjarn's answer really showed how it was done "the F# way". but thank you for this and the comments above. fwiw +1 – user1015250 Oct 27 '11 at 00:31
1

Trivial. Compile. Open in Reflector. Choose decompiler language to F#.

Ivan G.
  • 5,027
  • 2
  • 37
  • 65