F# higher-order functions on discrete unions -
i trying write rest client in f# uses strongly-typed values define resource "signatures". have decided use discrete unions represent signature list of optional arguments. planning on providing generic way convert parameter value list of tuples represent key/value pairs used create request. learning exercise trying use idomatic f#.
i've gotten stuck, trying define 2 different discrete unions have similar signatures. there way me dynamically select right pattern matching function @ runtime?
type = | first of string | second of string type b = | first of string | second of string | third of string let createa f s = [a.first f; a.second s;] let createb f s t = [b.first f; b.second s; b.third t] let toparama elem = match elem | a.first f -> "first", f | a.second s -> "second", s let toparamb elem = match elem | b.first f -> "first", f | b.second s -> "second", s | b.third t -> "third", t let rec toparam f state args = match args | [] -> state | head::tail -> let state' = (f head)::state toparam f state' tail let arga = createa "one" "two" let argb = createb "one" "two" "three" let resulta = arga |> toparam toparama [] let resultb = argb |> toparam toparamb []
the result correct, i'm not happy api:
val resulta : (string * string) list = [("second", "two"); ("first", "one")] val resultb : (string * string) list = [("third", "three"); ("second", "two"); ("first", "one")]
update:
the question asked call like?
let resulta = arga |> toparam []
then toparam figure out whether call toparama or toparamb.
i think i've realized original approach works fine current situation. however, i'd still interested know if pseudo-code possible.
i think vanilla f#-way explicitly state api method you're constructing parameter list:
type apiargs = apia of list | apib of b list
and conflate toparama
, toparamb
functions this:
let toparam = function | apia args -> let toparama = function | a.first x -> "first", x | a.second x -> "second", x list.map toparama args | apib args -> let toparamb = function | b.first x -> "first", x | b.second x -> "second", x | b.third x -> "third", x list.map toparamb args
i see 2 possibilities improvement here. first, code repetitive , boring. can generate code type provider api, or use reflection @ run time conversion.
second, polymorphic behavior of converting either a
or b
(string * string) list
happens @ run time, think can pull off @ compile time:
type x = x static member ($) (_, args : list) = let toparama = function | a.first x -> "first", x | a.second x -> "second", x list.map toparama args static member ($) (_, args : b list) = let toparamb = function | b.first x -> "first", x | b.second x -> "second", x | b.third x -> "third", x list.map toparamb args let inline toparam' args = x $ args
if inspect toparam'
's inferred type, similar this:
val inline toparam' : args: ^a -> ^_arg3 when (x or ^a) : (static member ( $ ) : x * ^a -> ^_arg3)
(the ^a
notation so-called "hat type", read more here)
and calling toparam'
arguments of different type yield correct results:
> toparam' (createa "one" "two");; val : (string * string) list = [("first", "one"); ("second", "two")] > toparam' (createb "1" "2" "3");; val : (string * string) list = [("first", "1"); ("second", "2"); ("third", "3")] >
this technique described in detail in this blog, believe outdated way it. more inspiration, take @ these projects: fscontrol, fsharpplus, higher.