Search code examples
f#literalstype-providers

Can I create a conditional literal?


In order to create a Json provider I need to pass a literal with the path. There are several people working on the project from different locations, and the paths are different in each case. (Actually only the beginning of each path). I tried to create a literal with pattern matching but the compiler does not accept it. Is there another way to do this?

My failed attempt is below:

open FSharp.Data

[<Literal>]
let bitbucketRoot = // Error message: This is not a valid constant expression
    let computerName = Environment.MachineName 
    match computerName with
    | "DESKTOP-G3OF32U" -> "C:\\Users\\Fernando"
    | "HPW8" -> @"H:\Dropbox\"
    | _ -> failwith "Unknown computer"

[<Literal>] // Error message: This is not a valid constant expression
let projDataPath = bitbucketRoot + @"Bitbucket\VSProjects\Fractal10\Fractal10\data\" 

[<Literal>] // Error message: This is not a valid constant expression
let jsonPath = projDataPath + "fractal.json"
type PathInfo = JsonProvider<Sample=jsonPath>

Solution

  • You cannot create a conditional literal as the other comments point it out. However this is a fairly frequent use case and the way to deal with it is as follows:

    #r @"..\packages\FSharp.Data\lib\net40\FSharp.Data.dll"
    open FSharp.Data
    open System
    open System.IO
    
    [<Literal>]
    let JsonSource = __SOURCE_DIRECTORY__ + @"\test.json"
    
    type JSonType = JsonProvider<JsonSource>
    
    let json1 = JSonType.GetSamples()
    
    let anotherPath = @"C:\tmp"
    let anotherJson = anotherPath + @"\test.json"
    let json2 = JSonType.Load(anotherJson)
    

    The __SOURCE_DIRECTORY__ directive will point to the project root (just display it in the REPL) and then you can add the filename to it and make that a literal. If you check in this file into a git repo, then everyone who checks it out can have it in a relative path, and you can refer it when generating the type. When actually using the type or referring to the full file you can just use the .Load() method to load any file, and this doesn't have to be a literal.

    There is actually a second way, which could work for you depending on the circumstances, compile a sample, and distribute it as a .dll. You can refer to this and use it directly without having access to the actual file. Please see the Using the JSON Provider in a Library section at the end of the documentation.

    I have not tried referring to the json in a config file, it might also be possible.