What does the operator </> do in FAKE build scripts? - f#

I just found this target in a FAKE build script generated by ProjectScaffold:
// Copies binaries from default VS location to expected bin folder
// But keeps a subdirectory structure for each project in the
// src folder to support multiple project outputs
Target "CopyBinaries" (fun _ ->
!! "src/**/*.??proj"
-- "src/**/*.shproj"
|> Seq.map (fun f -> ((System.IO.Path.GetDirectoryName f)
</> "bin/Release", "bin"
</> (System.IO.Path.GetFileNameWithoutExtension f)))
|> Seq.iter (fun (fromDir, toDir) -> CopyDir toDir fromDir (fun _ -> true))
)
My question: What does this strange </> operator do?
(My internet search was not very successful.)

The operator </> is an infix operator and combines two path segments into one complete path. In this aspect it is almost the same as the ## operator. The </> operator was created after the ## operator because the ## operator behaves strangely on UNIX-like systems when the second path starts with root.
Here is an example taken from the issue description on GitHub.
"src" ## "/home/projects/something" returns "src/home/projects/something"
"src" </> "/home/projects/something" returns "/home/projects/something"
The operator is defined in EnvironmentHelper:
https://fsharp.github.io/FAKE/apidocs/fake-environmenthelper.html
These links points to the problem description:
https://github.com/fsharp/FAKE/issues/670,
https://github.com/fsharp/FAKE/pull/695

Related

Is it possible to define an F# operator that applies multiple functions to a single argument (almost the opposite of the ||> operator)?

My attempt to do this is here (forgive the for loop - I was just curious to see if this was possible):
let (|>>) a (b : ('a -> unit) list) =
for x in b do
x a
but when I try to use it I get the error
That None of the types error message can occur if the function you're trying to use is defined further down the file or isn't imported correctly. Otherwise, your function definition seems ok.
I would discourage the use of a custom operator for this. I think they should be used very rarely. This one doesn't seem general enough to be worth defining and could make code hard to read. Here is one alternative:
[ printf "%A"; printfn "%A" ] |> List.iter ((|>) 1)
But it's even clearer and shorter to write out your operator definition inline:
for f in [ printf "%A"; printfn "%A" ] do f 1

referencing one's own class library in a F# project on VS2015

i have a solution called Algos
on Solution explorer i have 2 projects inside this solution
one called Algos (again ! maybe i should change the name for avoiding confusion ?)
which is a console application
one called MyLibrary which is a Class Library
I have in the solution explorer added in the References of the Project Algo MyLibrary and i can see it in the list.
// useful functions
// returns the minimum + index of the minimum
namespace Misc
exception InnerError of string
module Search =
let mini (s : (int*int) list) =
match s with
| [] -> (-1,(-1,-1))
| _ -> s |> Seq.mapi (fun i x -> (i, x)) |> Seq.minBy snd
let maxi (s : (int*int) list) =
match s with
| [] -> (-1,(-1,-1))
| _ -> s |> Seq.mapi (fun i x -> (i, x)) |> Seq.maxBy snd
module Bit =
let rec sumbits (n:int):int=
let rec helper acc m =
match m with
| 0 -> acc
| 1 -> acc+1 // enlever cela ?
| _ -> let r = m%2
helper (acc+r) (m>>>1)
helper 0 n
let power2 k =
let powers_of_2 = [|1;2;4;8;16;32;64;128;256;512;1024;2048;4096;8192;16384;32768;65536;131072;262144;524288;1048576;2097152;4194304;8388608;16777216|]
if ((k >= 24) || (k<0)) then raise (InnerError("power exponent not allowed"))
else powers_of_2.[k]
i'm just to use Misc.Bit.power2 in the main code
open MyLibrary
let a = Misc.Bit.power2 3
but Misc.Bit will be underlined and I have a compiler error
Severity Code Description Project File Line Suppression State
Error The value, constructor, namespace or type 'Bit' is not defined Algos C:\Users\Fagui\Documents\GitHub\Learning Fsharp\Algos\Algos\TSP.fs 50
what have i done wrong ? does it come from other parts of the source code perhaps ?
there are no other warnings.
both projects use .NET Framework 4.5.2
MyLibrary uses Target F# Runtime 4.3.1 while there is no similar indication for Algos.
thanks
Two things come to mind here: I don't see where the namespace MyLibrary is defined, and you haven't mentioned that you actually compiled the dependency.
Generally, to reference and use a library within the same solution, you need to:
add the library to the using program's dependency, preferably via: References – right-click – add Reference (Reference Manager) – Projects – Solution, by checking the checkbox of the dependency.
compile the dependency. When referenced properly, this should automatically occur before dependent code is compiled, but IntelliSense will only update after compiles! So hit compile when you see outdated errors.
use identifiers from the library via their correct namespace. Make sure that the qualified names or open declarations in the using code are correct.
To my knowledge, this should be all you need to do.
There are rare cases where files get write-locked but never released on compilation and you need to restart Visual Studio to be able to compile again. I've also encountered a case where some interaction of Git and Visual Studio created corrupted, half-deleted files with effectively no file owner; this required a reboot to fix. If you're really scratching your head and the errors are clearly nonsensical, maybe try moving the folder to check for file system damage.
This should certainly work. Please also make sure that you target same versions of F# and .NET: .NET 4.5.2 and 4.4.0.0 in the library and the console application

FAKE CscHelper: Csc vs csc?

I don't understand the difference between these two.
I want to compile a single c# file into a dll using FAKE's CscHelper. This is my build file:
// include Fake lib
#r #"packages/FAKE/tools/FakeLib.dll"
open Fake
open CscHelper
Target "Default" (fun _ ->
["Discover.cs"] |> csc (fun p -> { p with Output="Discover.dll"; Target=Library })
)
RunTargetOrDefault "Default"
This is the error I get:
build.fsx(7,24): error FS0001: Type mismatch. Expecting a
string list -> unit
but given a
string list -> int
The type 'unit' does not match the type 'int'
If I replace "csc" with "Csc" it compiles correctly. Why? In the documentation the code samples are literally identical other than that single character. The method signatures appear the same other than return type. Why are there two variants and how do you make the lowercase one work?
The lower case form is proper. You can always pipe the results to the ignore function to be sure to return a unit ().
// include Fake lib
#r #"packages/FAKE/tools/FakeLib.dll"
open Fake
open CscHelper
Target "Default" (fun _ ->
["Discover.cs"] |> csc (fun p -> { p with Output="Discover.dll"; Target=Library }) |> ignore
)
RunTargetOrDefault "Default"
The actual tool tip tells you what is going on (it returns exit status code which is of type int):
Type mismatch. Expecting a
'string list -> unit'
but given a
'string list -> int'
The type 'unit' does not match the type 'int'
val csc : setParams:(CscParams -> CscParams) -> inputFiles:string list -> int
Full name: Fake.CscHelper.csc
Compiles the given C# source files with the specified parameters.
Parameters
setParams - Function used to overwrite the default CSC parameters.
inputFiles - The C# input files.
Returns
The exit status code of the compile process.
Sample
["file1.cs"; "file2.cs"] |> csc (fun parameters -> { parameters with Output = ... Target = ... ... })
You may have already found this out or not, but it is good that everyone knows there are options. Thank you. Good day.

Better way to get tree representation of directory using F#?

I am new(ish) to F# and am trying to get a tree representation of a filesystem directory. Here's what I came up with:
type FSEntry =
| File of name:string
| Directory of name:string * entries:seq<FSEntry>
let BuildFSDirectoryTreeNonTailRecursive path =
let rec GetEntries (directoryInfo:System.IO.DirectoryInfo) =
directoryInfo.EnumerateFileSystemInfos("*", System.IO.SearchOption.TopDirectoryOnly)
|> Seq.map (fun info ->
match info with
| :? System.IO.FileInfo as file -> File (file.Name)
| :? System.IO.DirectoryInfo as dir -> Directory (dir.Name, GetEntries dir)
| _ -> failwith "Illegal FileSystemInfo type"
)
let directoryInfo = System.IO.DirectoryInfo path
Directory (path, GetEntries directoryInfo)
But... pretty sure that isn't tail recursive. I took a look at the generated IL and didn't see any tail prefix. Is there a better way to do this? I tried using an accumulator but didn't see how that helps. I tried mutual recursive functions and got nowhere. Maybe a continuation would work but I found that confusing.
(I know that stack-depth won't be an issue in this particular case but still would like to know how to tackle this non-tail recursion problem in general)
OTOH, it does seem to work. The following prints out what I am expecting:
let PrintFSEntry fsEntry =
let rec printFSEntryHelper indent entry =
match entry with
| File name -> printfn "%s%s" indent name
| Directory(name, entries) ->
printfn "%s\\%s" indent name
entries
|> Seq.sortBy (function | File name -> 0 | Directory (name, entries) -> 1)
|> Seq.iter (printFSEntryHelper (indent + " "))
printFSEntryHelper "" fsEntry
This should probably be a different question but... how does one go about testing BuildFSDirectoryTreeNonTailRecursive? I suppose I could create an interface and mock it like I would in C#, but I thought F# had better approaches.
Edited: Based on the initial comments, I specified that I know stack space probably isn't an issue. I also specify I'm mainly concerned with testing the first function.
To expand on my comment from earlier - unless you anticipate working with inputs that would cause a stack overflow without tail recursion, there's nothing to be gained from making a function tail-recursive. For your case, the limiting factor is the ~260 characters in path name, beyond which most Windows APIs will start to break. You'll hit that way before you start running out of stack space due to non-tail recursion.
As for testing, you want your functions to be as close to a pure function as possible. This involves refactoring out the pieces of the function that are side-effecting. This is the case with both of your functions - one of them implicitly depends on the filesystem, the other prints text directly to the standard output.
I guess the refactoring I suggest is fairly close to Mark Seemann's points: few mocks - checked, few interfaces - checked, function composition - checked. The example you have however doesn't lend itself nicely to it, because it's an extremely thin veneer over EnumerateFileSystemInfo. I can get rid of System.IO like this:
type FSInfo = DirInfo of string * string | FileInfo of string
let build enumerate path =
let rec entries path =
enumerate path
|> Seq.map (fun info ->
match info with
| DirInfo (name, path) -> Directory(name, entries path)
| FileInfo name -> File name)
Directory(path, entries path)
And now I'm left with an enumerate: string -> seq<FSInfo> function that can easily be replaced with a test implementation that doesn't even touch the drive. Then the default implementation of enumerate would be:
let enumerateFileSystem path =
let directoryInfo = DirectoryInfo(path)
directoryInfo.EnumerateFileSystemInfos("*", System.IO.SearchOption.TopDirectoryOnly)
|> Seq.map (fun info ->
match info with
| :? System.IO.FileInfo as file -> FileInfo (file.Name)
| :? System.IO.DirectoryInfo as dir -> DirInfo (dir.Name, dir.FullName)
| _ -> failwith "Illegal FileSystemInfo type")
You can see that it has virtually the same shape as the build function, minus recursion, since the entire 'core' of your logic is in EnumerateFileSystemInfos which lives beyond your code. This is a slight improvement, not in any way test-induced damage, but still it's not something that will make it onto anyone's slides anytime soon.

Site scraping with F# and Canopy

I am trying to write a simple scraper using F# and Canopy (see http://lefthandedgoat.github.io/canopy/). I am trying to extract text from all element with the class ".application-tile". However, in the code below, I get the following build error and I don't understand it.
This expression was expected to have type
OpenQA.Selenium.IWebElement -> 'a
but here has type
OpenQA.Selenium.IWebElement
Any idea why this is happening? Thanks!
open canopy
open runner
open System
[<EntryPoint>]
let main argv =
start firefox
"taking canopy for a spin" &&& fun _ ->
url "https://abc.com/"
// Login Page
"#i0116" << "abc#abc.com"
"#i0118" << "abc"
click "#abcButton"
// Get the Application Tiles -- BUILD ERROR HAPPENS HERE
elements ".application-tile" |> List.map (fun tile -> (tile |> (element ".application-name breakWordWrap"))) |> ignore
run()
open canopy
open runner
start firefox
"taking canopy for a spin" &&& fun _ ->
url "http://lefthandedgoat.github.io/canopy/testpages/"
// Get the tds in tr
let results = elements "#value_list td" |> List.map read
//or print them using iter
elements "#value_list td"
|> List.iter (fun element -> System.Console.WriteLine(read element))
run()
That should do what you want.
canopy has function called 'read' that takes in either a selector or an element. Since you have all of them from 'elements "selector"' you can map read over the list.
List.map takes in a function, runs it, and returns a list of results. (in C# its like elements.Select(x => read(x))
List.iter is the same as .foreach(x => System.Console.Writeline(read(x))
I believe that the error is happening in the projection lambda inside your List.map call. From the canopy documentation elements returns all elements that match css selector or text. element gets an element with given css selectors or text.
So here you are obtaining a list of Elements that match the selector ".application-tile". List.map requires a lambda that takes an IElement (the type contained in elements) that will project it into a new form (the generic 'a).
I don't know much about this framework but I'm not sure why you're taking an element and then piping it into another call to element.
Looking further through the documentation we find the read function:
"Read the text (or value or selected option) of an element." Is this what you want?

Resources