Search code examples
f#entry-point

Calling a specific function defined in `argv` for `[<EntryPoint>]` or dynamically naming the function to be called


Is it possible to somehow define the function name called and/or module name opened dynamically in F#?

Googling the interwebs seems to suggest using introspection is only possible if the "names" are members of a type.

The reason I'm asking is because my solution for Advent of Code's [<EntryPoint>] is starting to look pretty boilerplatey and definitely not DRY.

All my "puzzle solving" functions are defined in Day -specific modules and have the same signature String -> Unit -> int64 i.e. they take a file name for input (for testing's sake as most, if not all, puzzles have test vectors) and unit (so that they don't all get executed when pattern matching)

open AoC2020.Utils
open AoC2020.Day1
open AoC2020.Day2
open AoC2020.Day3
open AoC2020.Day4
open AoC2020.Day5
open AoC2020.Day6
open AoC2020.Day7
open AoC2020.Day8
open AoC2020.Day9

[<EntryPoint>]
let main argv =
    let day = argv |> getProblem
    match day with
    | "1" -> day1 "1" ()
    | "1b" -> day1part2 "1" ()
    | "2" -> day2 "2" ()
    | "2b" -> day2part2 "2" ()
    | "3" -> day3 "3" ()
    | "3b" -> day3part2 "3" ()
    | "4" -> day4 "4" ()
    | "4b" -> day4part2 "4" ()
    | "5" -> day5 "5" ()
    | "5b" -> day5part2 "5" ()
    | "6" -> day6 "6" ()
    | "6b" -> day6part2 "6" ()
    | "7" -> day7 "7" ()
    | "7b" -> day7part2 "7" ()
    | "8" -> day8 "8" ()
    | "8b" -> day8part2 "8" ()
    | _ -> 1L
    |> printfn "%d"
    0

..or am I doing something completely wrong and there's a better way to achieve the same thing?


Solution

  • More generally, I would probably not bother creating a sophisticated launcher for Advent of Code - for the few puzzles I did, I just kept the code for each puzzle in a script file and run it by selecting it all and running it in F# Interactive - rather than having a command line interface.

    However, if you prefer a command line interface, then you should be able to do what you want using reflection. Each F# module is compiled to a class and you can use Assembly.GetExecutingAssembly().GetTypes() to get a list of all loaded classes, find the class you want and invoke a method that you know it has such as run.

    For example:

    module Day1 = 
      let run () = printfn "day 1"
    
    module Day2 = 
      let run () = printfn "day 2"
    
    module Day2b = 
      let run () = printfn "day 2b"
    

    A (very basic) function that takes a module name and runs its run function looks like this:

    open System.Reflection
    
    let run name = 
      let typ = 
        Assembly.GetExecutingAssembly().GetTypes()
        |> Seq.find (fun t -> t.Name = name)
      typ.GetMethod("run").Invoke(null, [||]) |> ignore
    

    To test this:

    run "Day1"
    run "Day2"
    run "Day2b"