5

I need to convert a string into a Literal so I can pass it as an argument to CsvProvider. But I am not able to do it. The code below runs with no problems:

open System.IO
open FSharp.Data
open FSharp.Data.JsonExtensions

let charSwitch (a: char) b x =
    if x = a then
        b
    else
        x

let jsonDataPath = Path.Combine(__SOURCE_DIRECTORY__, @"data\fractal.json")
let jsonData = JsonValue.Load(jsonDataPath)

/// Path with traded assets
let trp = ((jsonData?paths?tradedAssets).AsString() |> Core.String.map (charSwitch '\\' '/')).ToString()
printfn "trp is a standard string: %s" trp
// trp is a standard string: H:/Dropbox/Excel/Data/Fractal/Traded.csv

However, when add the following two lines

[<Literal>]
let tradedPath = trp

at the end I get the message This is not a valid constant expression or custom attribute value.

I even tried to make a copy of trp but that did not help.

Any way to circumvent this problem?

Soldalma
  • 4,636
  • 3
  • 25
  • 38
  • @FoggyFinder - CsvProvider only works if the Sample argument is a literal. For example, `type TickerName = CsvProvider<"C:/temp/myfile.csv">` works fine but 'let fil = "C:/temp/myfile.csv"` together with `type TickerName = CsvProvider' does not work. Placing `[]` on the line just above 'let fil = "C:/temp/myfile.csv"` solves the problem. But I cannot do this with the string shown on my question, presumably because it has been calculated. There are a couple of questions on SO about type providers' need for literal Sample arguments. – Soldalma Mar 02 '17 at 20:43
  • 1
    You should be able to use the `Load` method on your `TickerName` type to load a resource from another source that follows the same schema as your local `""C:/temp/myfile.csv"` resource. – TheInnerLight Mar 02 '17 at 20:47
  • @TheInnerLight - I was not able to create the `TickerName` type because I need `traderPath` to be a literal in `type TickerName = CsvProvider`. Unable to create the `TickerName` type, I cannot call its Load method. I could create the `TickerName` type by hardcoding the file name like this: `type TickerName = CsvProvider<"C:/temp/myfile.csv">` but I don't want do that because I want the Sample file to be determined at runtime. – Soldalma Mar 02 '17 at 20:59
  • 1
    @Soldalma There is zero scope for determining the sample at runtime. That is not how type providers operate. Why can't you do `type TickerName = CsvProvider<"C:/temp/myfile.csv">` then `TickerName.Load(trp)`? – TheInnerLight Mar 03 '17 at 12:33

2 Answers2

6

Sadly, you can't magically turn an ordinary value into a literal value by applying the [<Literal>] attribute to it.

The special thing about literal values is that are compiled as a constant and that means they have to be determinable at compile time.

For example, this is a literal string:

[<Literal>]
let testLiteral = "This is a literal string"

You can combine several literal strings into a new literal string:

[<Literal>]
let a = "a"
[<Literal>]
let b = "b"
[<Literal>]
let ab = a + b

You cannot apply arbitrary functions to literals because then they would not be determinable at compile time.

More about literals.

TheInnerLight
  • 12,034
  • 1
  • 29
  • 52
  • Fair enough. But does this mean I cannot determine the CSV file path that will be the Sample argument for CsvProvider at runtime? Is there no alternative to hardcoding this path? – Soldalma Mar 02 '17 at 20:49
  • 4
    @Soldalma The sample is used to generate the types so no, there is no alternative to knowing it at compile time. Your workflow should be to use a schema file than can be resolved at compile time and give it different targets that follow the same schema via the `Load` method at runtime. – TheInnerLight Mar 02 '17 at 20:51
  • @Soldalma you can of course use the `[]` attribute on the `__SOURCE_DIRECTORY__` itself. This can be relative to the exe. – s952163 Mar 02 '17 at 23:49
3

Looking at your last comment you're trying to use the CsvProvider, you can of course use something else to parse the csv file, but it's also possible to use [<Litera>] on the __SOURCE_DIRECTORY__ as well as give a ResolutionFolder argument (this has to be a Literal though) to the provider. Here are two examples, one uses a sample in the Project root to create the type but then uses a command line argument for the actual file. The other one uses relative path to parse the file.

open System
open FSharp.Data
open FSharp.Data.JsonExtensions


#if INTERACTIVE
#r @"..\packages\FSharp.Data.2.3.2\lib\net40\FSharp.Data.dll"
#endif 


[<Literal>]
let file = __SOURCE_DIRECTORY__ + @"\file1.csv"
[<Literal>]
let path3 = __SOURCE_DIRECTORY__
[<Literal>]
let path4 = "."

type SampleFile = CsvProvider<file,HasHeaders=true>
type SampleFile3 = CsvProvider<"file1.csv",HasHeaders=true,ResolutionFolder=path3>


[<EntryPoint>]
let main argv = 

    //let nonLiteralPath = @".\file1.csv" // you could hardcode this in the file but:
    let nonLiteralPath = argv.[0]  // you can also use a path specified on the command line
    let DataFile = SampleFile.Load(nonLiteralPath)
    [for row in DataFile.Rows -> row.``Key #1``]  |> printfn "%A"
    let x= SampleFile3.GetSample()  // use a relative path, this will be the root of the project at design time
                                    // or the root of the exe at the execution time
    [for row in x.Rows -> row.``Key #2``] |> printfn "%A"   

    printfn "%A" argv

And for the output:

enter image description here

s952163
  • 6,276
  • 4
  • 23
  • 47