No conozco ninguna de estas herramientas. Sin embargo, puede ser un ejercicio divertido escribir uno usando System.Reflection
(o mejor aún, la biblioteca de metadatos en el PowerPack), para que pueda tener en cuenta nombres de variables de tipo de módulo de equivalencia, etc.
EDIT - Tenía razón - es era un ejercicio divertido. Lo que sigue tiene muchas verrugas, pero no es tan malo para ~ 150 líneas de código. Con suerte, esto será suficiente para comenzar a alguien que quiera trabajar en una herramienta real. No hace nada avanzado, como buscar funciones con parámetros reordenados, y la biblioteca de metadatos es un poco exigente con el uso de nombres completamente calificados, por lo que debe ser un poco cuidadoso. Para responder a la pregunta en su puesto original, ejecuté
find "('a -> Microsoft.FSharp.Core.bool) -> Microsoft.FSharp.Collections.list`1<'a> -> Microsoft.FSharp.Core.int"
y obtuvo la siguiente lista de candidatos:
Microsoft.FSharp.Core.Operators.(+)
Microsoft.FSharp.Core.Operators.(-)
Microsoft.FSharp.Core.Operators.(*)
Microsoft.FSharp.Core.Operators.(/)
Microsoft.FSharp.Core.Operators.(%)
Microsoft.FSharp.Core.Operators.sqrt
Microsoft.FSharp.Core.LanguagePrimitives.EnumOfValue
Microsoft.FSharp.Core.LanguagePrimitives.EnumToValue
Microsoft.FSharp.Core.LanguagePrimitives.AdditionDynamic
Microsoft.FSharp.Core.LanguagePrimitives.CheckedAdditionDynamic
Microsoft.FSharp.Core.LanguagePrimitives.MultiplyDynamic
Microsoft.FSharp.Core.LanguagePrimitives.CheckedMultiplyDynamic
Microsoft.FSharp.Core.LanguagePrimitives.GenericZero
Microsoft.FSharp.Core.LanguagePrimitives.GenericOne
Microsoft.FSharp.Collections.List.find
Microsoft.FSharp.Collections.List.findIndex
Microsoft.FSharp.Collections.List.maxBy
Microsoft.FSharp.Collections.List.minBy
De éstos, sólo List.findIndex
tiene exactamente el tipo genérico que estás buscando, pero con la combinación correcta de parámetros de tipo, también lo hacen los demás (por ejemplo, si 'a = int
entonces List.find
tiene el tipo deseado). Desafortunadamente, las restricciones no se tienen en cuenta en la búsqueda, por lo que las funciones que no son List
no pueden coincidir.
Sin más preámbulos, aquí está el código que utilicé: deberá agregar una referencia al ensamblado FSharp.PowerPack.Metadata para que funcione.
open Microsoft.FSharp.Metadata
open System.Text.RegularExpressions
(* type parameters let us switch out representation if need be *)
type Tag<'ty> = | Tuple | Arr | Ground of 'ty
type Ty<'ty,'a> = Param of 'a | Complex of Tag<'ty> * Ty<'ty,'a> list
(* Gets something stable from an FSharpEntity so that we can see if two are identical *)
let rec getType (e:FSharpEntity) =
if (e.IsAbbreviation) then
getType e.AbbreviatedType.NamedEntity
else
e.ReflectionType
(* FSharpType -> Ty<System.Type,string> *)
let rec cvt (e:FSharpType) =
if e.IsTuple then
Complex(Tuple, e.GenericArguments |> Seq.map cvt |> List.ofSeq)
elif e.IsFunction then
Complex(Arr, e.GenericArguments |> Seq.map cvt |> List.ofSeq)
elif e.IsGenericParameter then
Param e.GenericParameter.Name
else
Complex(Ground(e.NamedEntity |> getType), e.GenericArguments |> Seq.map cvt |> List.ofSeq)
(* substitute type for variable within another type *)
let rec subst v t = function
| Complex(tag,l) -> Complex(tag, l |> List.map (subst v t))
| Param i when i = v -> t
| Param j -> Param j
(* get type variables used in a type *)
let rec usedVars = function
| Param i -> Set.singleton i
| Complex(tag, l) -> Set.unionMany (List.map usedVars l)
(* Find most general unifier (if any) for two types *)
let mgu t1 t2 =
let rec mgu subs = function
| [] -> Some subs
| (Complex(tag1,l1),Complex(tag2,l2))::rest ->
if tag1 <> tag2 then
None
else
let rec loop r = function
| [],[] -> mgu subs r
| [],_ | _,[] -> None
| x::xs, y::ys -> loop ((x,y)::r) (xs,ys)
loop rest (l1,l2)
| (Param i, Param j)::rest when i = j -> mgu subs rest
| ((Param i, x) | (x, Param i))::rest ->
if (Set.contains i (usedVars x)) then
None (* type would be infinite when unifying *)
else
mgu ((i,x)::subs) (rest |> List.map (fun (t1,t2) -> (subst i x t1, subst i x t2)))
mgu [] [t1,t2]
(* Active patterns for parsing - this is ugly... *)
let (|StartsWith|_|) r s =
let m = Regex.Match(s, r)
if m.Success && m.Index = 0 then
Some(m.Value, s.Substring(m.Length))
else None
let rec (|Any|) (|P|_|) = function
| P(x,Any (|P|_|) (l,r)) -> x::l, r
| s -> [],s
let rec (|Any1|_|) (|P|_|) = function
| P(x,Any (|P|_|) (l,r)) -> Some(x::l, r)
| _ -> None
let (|Seq|_|) (|P|_|) (|Q|_|) = function
| P(x,Q(y,r)) -> Some((x,y),r)
| _ -> None
let (|Choice|_|) (|P|_|) (|Q|_|) = function
| P(p) -> Some p
| Q(p) -> Some p
| _ -> None
let (|Delimit|_|) s (|P|_|) = function
| P(x,Any ((|Seq|_|) ((|StartsWith|_|) s) (|P|_|)) (l,r)) -> Some(x::(List.map snd l), r)
| _ -> None
let (|Delimit1|_|) s (|P|_|) = function
| P(x,StartsWith s (_,Delimit s (|P|_|) (l,r))) -> Some(x::l, r)
| _ -> None
(* Basically a BNF grammar for types *)
let rec (|TyE|_|) = function
| ArrE(p) | TupleE(p) | AtomE(p) -> Some(p)
| _ -> None
and (|ArrE|_|) = function
| Choice (|TupleE|_|) (|AtomE|_|) (dom,StartsWith "->" (_,TyE(rng,r))) -> Some(Complex(Arr,[dom;rng]), r)
| _ -> None
and (|TupleE|_|) = function
| Delimit1 @"\*" (|AtomE|_|) (l,r) -> Some(Complex(Tuple,l), r)
| _ -> None
and (|AtomE|_|) = function
| ParamE(x,r) | GroundE(x,r) | StartsWith @"\(" (_,TyE(x,StartsWith @"\)" (_,r))) -> Some(x,r)
| _ -> None
and (|ParamE|_|) = function
| StartsWith "'[a-zA-Z0-9]+" (s,r) -> Some(Param s, r)
| _ -> None
and (|GroundE|_|) = function
| StartsWith "[`.a-zA-Z0-9]+" (gnd, StartsWith "<" (_, Delimit "," (|TyE|_|) (l, StartsWith ">" (_,r)))) ->
let ty = FSharpAssembly.FSharpLibrary.GetEntity gnd |> getType
Some(Complex(Ground(ty), l), r)
| StartsWith "[`.a-zA-Z0-9]+" (gnd, r) ->
let ty = FSharpAssembly.FSharpLibrary.GetEntity gnd |> getType
Some(Complex(Ground(ty), []), r)
| _ -> None
(* parse a string into a type *)
let parse (s:string) =
(* remove whitespace before matching *)
match s.Replace(" ","") with
| TyE(ty,"") -> ty
| _ -> failwith "Not a well-formed type"
(* an infinite stream of possible variable names - for performing renaming *)
let rec names =
let letters = ['a' .. 'z'] |> List.map string
seq {
yield! letters
for n in names do
for l in letters do
yield n + l
}
(* finds entities in the F# library with the requested signature, modulo type parameter unification *)
let find s =
let ty = parse s
let vars = usedVars ty
seq {
for e in FSharpAssembly.FSharpLibrary.Entities do
for m in e.MembersOrValues do
(* need try/catch to avoid error on weird types like "[]`1" *)
match (try Some(cvt m.Type) with _ -> None) with
| Some ty2 ->
(* rename all type variables from the query to avoid incorrectly unifying with type variables in signatures *)
let used = usedVars ty2
let newVars = Seq.choose (fun v -> if Set.contains v used then None else Some(Param v)) names
let varMap = Map.ofSeq (Seq.zip vars newVars)
let ty = Map.fold (fun t v p -> subst v p t) ty varMap
match mgu ty ty2 with
| None ->()
| Some _ -> yield sprintf "%s.%s.%s" e.Namespace e.DisplayName m.DisplayName
| _ ->() }
, gracias, esto se ve muy bien. Cuando tenga tiempo, intentaré convertir esto en algo tan útil como Hoogle. –