I am trying to experiment in F# for one of the utility tools we need, wherein we want to trawl through a folder of xml files and look for a particular tag. If found then insert another similar tag alongwith it. Finally, output all the filenames for which such additional tags have been inserted. But am getting a compilation error, of which I am not able to make a lot of sense.
let configFile =
Directory.GetFiles(Path.Combine("rootdir", "relativepath"), #"*.xml")
|> Seq.map(fun configFileName ->
let xmlNavigator = XPathDocument(configFileName).CreateNavigator()
let node = xmlNavigator.SelectSingleNode(#"Product/ABc[#type='xyz']")
match node with
| null -> "not configuration present"
| _ ->
let nodeAppender() = node.InsertAfter("<Risk type=""abc1"" methodology=""xyz1""/>")
let parentNode = node.SelectAncestors(XPathNodeType.Root, false)
parentNode.Current.OuterXml)
|> Seq.iter (printfn "%s")
The compilation error is as below:
This value is not a function and cannot be applied
Your string is escaped improperly. It should be:
node.InsertAfter("<Risk type=\"abc1\" methodology=\"xyz1\"/>")
EDIT: Apparently I was typing this as Brian posted his answer. Either escaping each quote char or prefixing with # as-is will work.
It would help to point out what line/column the error location is at.
At a glance, in nodeAppender, it looks like you left off the # on the string literal, which means it is five strings in a row (rather than one string with escaped quotes), which may be the cause of the error.
Related
I am new to FAKE and trying to implement something in FAKE as described below :
I have a file having more than 100 lines, I want to change few lines in the code , let say I want to change 2nd line i.e. IFR.SIIC._0.12 to
IFR.SIIC._0.45
How will I do this .
Will I do this using ReplaceInFile or RegexReplaceInFileWithEncoding ?
There are many functions that could help you: which one you'll pick will depend on how you'd prefer to write your code. For example, ReplaceInFile wants you to supply it with a function, while RegexReplaceInFileWithEncoding wants you to give it a regular expression (in string form, not as a Regex object). Depending on what text you want to replace, one might be easier than the other. For example, you could use ReplaceInFile like so:
Target "ChangeText" (fun _ ->
"D:\Files\new\oneFile.txt" // Note *no* !! operator to change a single file
|> ReplaceInFile (fun input ->
match input with
| "IFR.SIIC._0.12" -> "IFR.SIIC._0.45"
| "another string" -> "its replacement"
| s -> s // Anything else gets returned unchanged
)
)
That would be useful if, for example, you have a set of specific strings that you want to match, in just a single file. However, there's a simpler function called ReplaceInFiles (note the plural) which allows you to replace text in multiple files at once. Also, instead of taking a function as its parameter, ReplaceInFiles takes a sequence of (old,new) pairs. This is often easier to write:
let stringsToReplace = [
("IFR.SIIC._0.12", "IFR.SIIC._0.45") ;
("another string", "its replacement")
]
Target "ChangeText" (fun _ ->
!! "D:\Files\new\*.txt"
|> ReplaceInFiles stringsToReplace
)
If you want to specify your search and replacement strings in the form a regular expression, then you'd want RegexReplaceInFileWithEncoding or RegexReplaceInFilesWithEncoding (note the plural: the former takes a single file while the latter takes multiple files). I'll just show you an example of the multiple-files version:
Target "ChangeText" (fun _ ->
!! "D:\Files\new\*.txt"
|> RegexReplaceInFilesWithEncoding #"(?<part1>\w+)\.(?<part2>\w+)\._0\.12"
#"${part1}.${part2}._0.45"
System.Text.Encoding.UTF8
)
That would allow you to change IFR.SIIC._0.12 to IFR.SIIC._0.45 and ABC.WXYZ._0.12 to ABC.WXYZ._0.45.
Which one of these you'll want to use all depends on how many files you have, and how many different replacement strings you need (and how hard it would be to write them a regex).
Right now I have two types:
type Rating = (String, Int)
type Film = (String, String, Int, [Rating])
I have a file that has this data in it:
"Blade Runner"
"Ridley Scott"
1982
("Amy",5), ("Bill",8), ("Ian",7), ("Kevin",9), ("Emma",4), ("Sam",7), ("Megan",4)
"The Fly"
"David Cronenberg"
1986
("Megan",4), ("Fred",7), ("Chris",5), ("Ian",0), ("Amy",6)
How can I look through then file storing all of the entries into something like FilmDatabase = [Film] ?
Haskell provides a unique way of sketching out your approach. Begin with what you know
module Main where
type Rating = (String, Int)
type Film = (String, String, Int, [Rating])
main :: IO ()
main = do
films <- readFilms "ratings.dat"
print films
Attempting to load this program into ghci will produce
films.hs:8:12: Not in scope: `readFilms'
It needs to know what readFilms is, so add just enough code to keep moving.
readFilms = undefined
It is a function that should do something related to Film data. Reload this code (with the :reload command or :r for short) to get
films.hs:9:3:
Ambiguous type variable `a0' in the constraint:
(Show a0) arising from the use of `print'
...
The type of print is
Prelude> :t print
print :: Show a => a -> IO ()
In other words, print takes a single argument that, informally, knows how to show itself (that is, convert its contents to a string) and creates an I/O action that when executed outputs that string. It’s more-or-less how you expect print to work:
Prelude> print 3
3
Prelude> print "hi"
"hi"
We know that we want to print the Film data from the file, but, although good, ghc can’t read our minds. But after adding a type hint
readFilms :: FilePath -> Film
readFilms = undefined
we get a new error.
films.hs:8:12:
Couldn't match expected type `IO t0'
with actual type `(String, String, Int, [Rating])'
Expected type: IO t0
Actual type: Film
In the return type of a call of `readFilms'
In a stmt of a 'do' expression: films <- readFilms "ratings.dat"
The error tells you that the compiler is confused about your story. You said readFilms should give it back a Film, but the way you called it in main, the computer should have to first perform some I/O and then give back Film data.
In Haskell, this is the difference between a pure string, say "JamieB", and a side effect, say reading your input from the keyboard after prompting you to input your Stack Overflow username.
So now we know we can sketch readFilms as
readFilms :: FilePath -> IO Film
readFilms = undefined
and the code compiles! (But we can’t yet run it.)
To dig down another layer, pretend that the name of a single movie is the only data in ratings.dat and put placeholders everywhere else to keep the typechecker happy.
readFilms :: FilePath -> IO Film
readFilms path = do
alldata <- readFile path
return (alldata, "", 0, [])
This version compiles, and you can even run it by entering main at the ghci prompt.
In dave4420’s answer are great hints about other functions to use. Think of the method above as putting together a jigsaw puzzle where the individual pieces are functions. For your program to be correct, all the types must fit together. You can make progress toward your final working program by taking little babysteps as above, and the typechecker will let you know if you have a mistake in your sketch.
Things to figure out:
How do you convert the whole blob of input to individual lines?
How do you figure out whether the line your program is examining is a title, a director, and so on?
How do you convert the year in your file (a String) to an Int to cooperate with your definition of Film?
How do you skip blank or empty lines?
How do you make readFilms accumulate and return a list of Film data?
Is this homework?
You might find these functions useful:
readFile :: FilePath -> IO String
lines :: String -> [String]
break :: (a -> Bool) -> [a] -> ([a], [a])
dropWhile :: (a -> Bool) -> [a] -> [a]
null :: [a] -> Bool
read :: Read a => String -> a
Remember that String is the same as [Char].
Some clues:
dropWhile null will get rid of empty lines from the start of a list
break null will split a list into the leading run of non-empty lines, and the rest of the list
Haskell has a great way of using the types to find the right function. For instance: In Gregs answer, he wants you to figure out (among other things) how to convert the year of the film from a String to an Int. Well, you need a function. What should be the type of that function? It takes a String and returns an Int, so the type should be String -> Int. Once you have that, go to Hoogle and enter that type. This will give you a list of functions with similar types. The function you need actually has a slightly different type - Read a => String -> a - so it is a bit down the list, but guessing a type and then scanning the resulting list is often a very useful strategy.
I have found this code in Ejabberd:
maybe_post_request([$< | _ ] = Data, Host, ClientIp)
I don't understand what [$< | _ ] = Data part do with Data. Could somebody explain?
The construct
[$< | _] = Data
applies a pattern match to Data, expecting it to be a list variable whose first element is the character < and ignoring the rest the elements. Try it in the Erlang shell:
1> Data = "<foo>".
"<foo>"
2> [$<|_] = Data.
"<foo>"
But if Data doesn't match, we get an exception:
3> f(Data), Data = "foo".
"foo"
4> [$<|_] = Data.
** exception error: no match of right hand side value "foo"
I don't understand what [$< | _ ] = Data part do with Data. Could
somebody explain?
It binds the variable Data to the entire first argument to the function.
The left hand side pattern matches the first argument so that this function clause only matches when the first argument is a string (list) starting with the character <. The variable Data is assigned the entire string fr use in the function body.
It's a way of having your cake and eating it at the same time. Data refers to the whole thing while the [$<|_] lets you match it and pull it apart. The putting then together with = in a patterns allows you to do both. In a pattern like this it is generally called an alias. It means that both sides much match and in an argument in a function head (which is where you saw it) the order is irrelevant so the function head could have been written as
maybe_post_request([$< | _ ] = Data, Host, ClientIp)
or
maybe_post_request(Data = [$< | _ ], Host, ClientIp)
Of course in the function body or in the shell they are not equivalent.
I personally prefer the first alternative as that says matching, pulling apart to me.
This compiler like:
let test Xf Yf = Xf + Yf
This compiler no like:
let test Xfd Yfd = Xfd + Yfd
Warning:
Uppercase variable identifiers should not generally be used in patterns, and may indicate a misspelt pattern name.
Maybe I'm not googling properly, but I haven't managed to track down anything which explains why this is the case for function parameters...
I agree that this error message looks a bit mysterious, but there is a good motivation for it. According to the F# naming guidelines, cases of discriminated unions should be named using PascalCase and the compiler is trying to make sure that you don't accidentally misspell name of a case in pattern matching.
For example, if you have the following union:
type Side =
| Left
| Right
You could write the following function that prints "ok" when the argument is Left and "wrong!" otherwise:
let foo a =
match a with
| Lef -> printfn "ok"
| _ -> printfn "wrong!"
There is a typo in the code - I wrote just Lef - but the code is still valid, because Lef can be interpreted as a new variable and so the matching assigns whatever side to Lef and always runs the first case. The warning about uppercase identifiers helps to avoid this.
F# tries to enforce case rules for active patterns - consider what does this code do
let f X =
match X with
|X -> 1
|_ -> 2
This is quite confusing. Also, function parameters are similar to patterns, you can do
let f (a,b,_) = a,b
for example. Not quite sure why the third letter triggers the warning though
I am writing my first F# library
I am trying to use string.Format and it complains that no such function exists.
Is it not available or am I doing something wrong?
If you want to avoid using the full name, you can use open in F#:
open System
let s = String.Format("Hello {0}", "world")
This should work in both F# interactive (enter the open clause first) and in normal compiled applications. The key thing is that you must write String with upper-case S. This is because string in C# isn't a usual type name - it is a keyword refering to the System.String type.
Alternatively, you could also take a look at the sprintf function. It is an F#-specific alternative to String.Format which has some nice benefits - for example it is type checked:
let s = sprintf "Hello %s! Number is %d" "world" 42
The compiler will check that the parameters (string and int) match the format specifiers (%s for string and %d for integers). The function also works better in scenarios where you want to use partial function application:
let nums = [ 1 .. 10 ]
let formatted = nums |> List.map (sprintf "number %d")
This will produce a list of strings containing "number 1", "number 2" etc... If you wanted to do this using String.Format, you'd have to explicitly write a lambda function.
the full name of it is:
System.String.Format