I'm a newbie to F# and so I've written a small extension method library to practice the language. The extension method attempts to determine if a url string is for an image. I wrote the code as an extension method so that it can be used as an extension or static method in other .net languages, namely C#.
The code first checks the url's extension to see if a guess can be made about its content. If no guess can be made, then the image is requested and the content type in the response's header is checked.
Any advice, refactorings, or criticisms welcome. Thanks!
namespace IsImageUrlDotNet
open System.Net
open System
open System.IO
[<System.Runtime.CompilerServices.Extension>]
module IsImageUrlDotNetLib =
let ImageFileExtensions = ["png"; "jpg"; "gif"; "raw";"bmp"; "svg"; "jpeg"; "psd";]
let NonImageFileExtensions = ["exe"; "pdf"; "html"; "htm"; "txt"; "mp3"; "wav"; "odp";]
let private hasAFileExtensionInList(fileExtensionList:string list) (url:string) =
if Path.HasExtension url = false then
false
else
let urlSplitList = url.ToLower().Split '.'
let lastExtension = urlSplitList.[urlSplitList.Length - 1]
List.contains lastExtension fileExtensionList
let private hasAnImageFileExtension(url:string) =
hasAFileExtensionInList ImageFileExtensions url
let private hasAnNonImageFileExtension(url:string) =
hasAFileExtensionInList NonImageFileExtensions url
let private requestUrlAndCheckIfImage(url:string) =
let req = WebRequest.Create(Uri(url))
use resp = req.GetResponse()
let contentType = resp.ContentType
if contentType.Contains("text/html") then
false
else
contentType.Contains("image")
[<System.Runtime.CompilerServices.Extension>]
let IsImageUrl(opt:string) =
match opt with
| null | "" -> false
| url when Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute) = false -> false
| url when hasAnImageFileExtension url -> true
| url when hasAnNonImageFileExtension url -> false
| _ -> requestUrlAndCheckIfImage opt
Sample Test (C#):
[TestMethod]
public void ShouldBeAbleToIdentifyACommonImageUrl()
{
const string CommonImageUrl = "https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png";
var isImage = CommonImageUrl.IsImageUrl();
Assert.IsTrue(isImage);
}
1 Answer 1
1. F# has a good system of type inference. You don't need to specify the type explicitly, the compiler in many cases will do it for you. You can read more about it here.
2. As @JoelMueller said in comment, F# there's such an amazing opportunity as partial application.
The idea of partial application is that if you fix the first N parameters of the function, you get a function of the remaining parameters
Taken from Partial application
So, you can rewrite the functions hasAnImageFileExtension
, hasAnNonImageFileExtension
:
let private hasAnImageFileExtension =
hasAFileExtensionInList ImageFileExtensions
let private hasAnNonImageFileExtension =
hasAFileExtensionInList NonImageFileExtensions
3. Several very small comments:
a) In hasAFileExtensionInList
instead urlSplitList.[urlSplitList.Length - 1]
you can use Seq.last
b) for me, construction like:
if Path.HasExtension url = false then
false
else ...
better, rerwite as:
if Path.HasExtension url then
...
else
false
4.
In function IsImageUrl
it is better to use if-then-else
instead pattern matching
5. Question.
Why not use Path.GetExtension ?
When string not valid will throw the exception. It is better to provide this option.
I don't post a new version of the code, as I plan to edit the answer.
-
\$\begingroup\$ Nice comments, thanks for the review. I'll try to remember to review and update the code hopefully later today. \$\endgroup\$Mark Rogers– Mark Rogers2016年07月21日 13:16:37 +00:00Commented Jul 21, 2016 at 13:16
ImageFileExtensions
includes "gif" and "raw" twice each, but is missing "jpeg". Also, it might be worth doing a performance comparison betweenList.contains
andSet.contains
for this use case. \$\endgroup\$hasAFileExtensionInList
has a single parameter that is a tuple. A more idiomatic F# solution would be to define it with two parameters, most-significant parameter last:let private hasAFileExtensionInList (fileExtensionList:string list) (url:string) =...
. This is because it allows for partial function application in the definition of the follow-on functions:let hasAnImageFileExtension = hasAFileExtensionInList ImageFileExtensions
\$\endgroup\$