3

Is it possible to "extend" the F# compiler to do custom compile-time string checks? I'm thinking of something similar to the checks on StringFormat strings when using sprintf etc. When I say "extend", I don't mean build a custom version of the compiler, I mean use existing supported techniques.

Off the top of my head, you might have a RegexFormat type. You provide the regex and the compiler would do the static analysis using the regex. E.g.

//Setup RegexFormat with IP address regex and type abbreviation IpRegexFormat?
//Compile error.  ipAddress expects IpRegexFormat!
let ip = ipAddress "192.168.banana.1" 

If not, maybe this is a type provider for me :) - If the whole thing is a terrible idea, let me know!

bentayloruk
  • 4,060
  • 29
  • 31
  • Yes, it is possible. No, don't do it. Try and see if type providers do it for you. – Ramon Snir Nov 30 '12 at 11:07
  • Would love to hear more @RamonSnir. How and why not! – bentayloruk Nov 30 '12 at 11:12
  • I've done several modifications to F# a couple of years ago. While this is very fun, and the result is a better F# - it takes time, Visual Studio doesn't support it, and type providers can do most of the work (disclaimer: I don't like type providers, haven't used them in weeks). – Ramon Snir Nov 30 '12 at 11:35
  • Ah, I see. You mean actually modifying the compiler. I wasn't clear enough in the question. When I said "extend", I meant via legitimate extension points (i.e. inline types/attributes), not a custom build. – bentayloruk Nov 30 '12 at 11:40
  • I see. Then I see no way to do that (except, the thoroughly discussed type providers). – Ramon Snir Nov 30 '12 at 11:47
  • You would probably want to use the regex type provider or similar. This would provide an adapter layer into the type thats consuming it. – 7sharp9 Nov 30 '12 at 12:33
  • 2
    possible duplicate of [Compile-time constraints for strings in F#, similar to Units of Measure - is it possible?](http://stackoverflow.com/questions/9417150/compile-time-constraints-for-strings-in-f-similar-to-units-of-measure-is-it) – Daniel Nov 30 '12 at 15:23

3 Answers3

7

We have a Regex type provider in Fsharpx.

Here are some samples:

type PhoneRegex = Regex< @"(?<AreaCode>^\d{3})-(?<PhoneNumber>\d{3}-\d{4}$)">

[<Test>] 
let ``Can call typed IsMatch function``() =      
    PhoneRegex.IsMatch "425-123-2345"
    |> should equal true

[<Test>] 
let ``Can call typed CompleteMatch function``() =      
    PhoneRegex().Match("425-123-2345").CompleteMatch.Value
    |> should equal "425-123-2345"

[<Test>] 
let ``Can return AreaCode in simple phone number``() =
    PhoneRegex().Match("425-123-2345").AreaCode.Value
    |> should equal "425"

[<Test>] 
let ``Can return PhoneNumber property in simple phone number``() =
    PhoneRegex().Match("425-123-2345").PhoneNumber.Value
    |> should equal "123-2345"

It's not exactly what you are looking for, but I guess you could easily take this type provider and customize it with your static literal rules.

forki23
  • 2,784
  • 1
  • 28
  • 42
  • 2
    Oh yes! I had forgotten we already have ALL the type providers in FSharpx :) I may go this way. The only side issue is that I want to ship this in a tool which is used from fs scripts. Any idea what the type provider security model does in this case? – bentayloruk Nov 30 '12 at 11:15
  • I'm not sure. You have to try it. Might be different in MonoDevelop, Visual Studio or FSI. – forki23 Nov 30 '12 at 11:58
1

I think here the real answer is to use a DU -

type ip = 
|IP of byte * byte * byte * byte 
member x.ToString() = 
    match x with
    |IP(a,b,c,d) -> sprintf "%i.%i.%i.%i"

Then the compile time check is just

let actualip = IP(1uy,1uy,1uy,1uy).ToString()
John Palmer
  • 25,356
  • 3
  • 48
  • 67
  • 1
    Blast, IP was a bad example. DU would work for IP, but not something like "string must be an email address". I'm interested in a more general solution (or why a more general solution would be bad :). – bentayloruk Nov 30 '12 at 11:25
  • @bentayloruk - well you can define a similar DU for email addresses - but for something more general / cleverer you probably need a type provider - particularly if you want compile time checking – John Palmer Nov 30 '12 at 11:34
  • yes, it is the compile time checking I'm after. I want the sprintf experience for all the strings! :) Thanks for your input. – bentayloruk Nov 30 '12 at 11:46
  • @JohnPalmer how would you define a type for an email address? I mean a real type safe version. – forki23 Nov 30 '12 at 12:20
  • @forki23 For an email address I would just do `type email = |Email of string * string`. If you are really worried you could check that neither string has an `@`. I have actually written a fully automated parser that takes a DU as input and gives an output - you could use this to enforce some rules like no `@` signs - but only at run time. – John Palmer Nov 30 '12 at 20:29
  • 1
    You can do a lot runtime checking for email addresses, that's true. But a type provider could try to send an email to the given address and if the server fails then it raises a compile error ;-) Might be a little bit slow, but it's a compelete new world. – forki23 Dec 01 '12 at 10:01
0

The easiest solution is to do what the BCL have done with Uri, Guid, etc and create a type that parses a string input.

I think modifying the compiler, while interesting, is overkill (a "terrible idea," as you say).

A similar question has been asked before.

Community
  • 1
  • 1
Daniel
  • 47,404
  • 11
  • 101
  • 179