Есть ли в документации F# способ поиска функций по их типам?
Скажем, я хочу знать, если F# имеет библиотечную функцию типа
('T -> bool) -> 'T list -> int
то есть то, что подсчитывает, сколько элементов списка, для которых функция возвращает true. (или возвращает индекс первого элемента, который возвращает true)
Я использовал большой список на сайте MSR для F# до того, как документация по MSDN была готова. Я мог бы просто найти на странице текст выше, потому что типы были перечислены. Но теперь в документации MSDN перечислены только типы на отдельных страницах - страница модуля представляет собой массу описательного текста. Google вроде-работает, но не может помочь
// compatible interfaces
('T -> bool) -> Seq<'T> -> int
// argument-swaps
Seq<'T> -> ('T -> bool) -> int
// type-variable names
('a -> bool) -> Seq<'a> -> int
// wrappers
('a -> bool) -> 'a list -> option<int>
// uncurried versions
('T -> bool) * 'T list -> int
// .NET generic syntax
('T -> bool) -> List<'T> -> int
// methods
List<'T> member : ('T -> bool) -> int
На Haskell есть отдельная программа для этого под названием Hoogle. Есть ли у F# эквивалент, как Fing или что-то?
3 ответа
Основываясь на ответе kvb, я создал полное приложение. Он размещен на github по адресу http://github.com/sandersn/fing.
Код все еще довольно уродливый, но он работает для простых случаев. Я достал самый универсальный унификатор kvb (mgu
) пока что добавляет много неочевидных результатов. Такие причудливые вещи, как структурные ограничения и самый общий супертип, тоже пока не работают.
Есть также двоичный файл для версии из командной строки, если вы не хотите собирать из исходного кода. (Однако для этого все еще требуется современная версия установленной среды выполнения.NET.) В конце концов я найду хостинг ASP.NET, изучу ASP и заверну все это в веб-приложении, так что установка вообще не требуется. (Думаю, если есть спрос, я мог бы создать графический интерфейс на стороне клиента, но у меня еще меньше опыта в подобных вещах.)
Я не знаю ни одного такого инструмента. Тем не менее, это может быть забавное упражнение, чтобы написать один, используя System.Reflection
(или, что еще лучше, библиотеку метаданных в PowerPack), чтобы можно было учитывать имена переменных по модулю и т. д.
РЕДАКТИРОВАТЬ - я был прав - это было забавное упражнение. Последующее имеет много бородавок, но не так уж плохо для ~150 строк кода. Надеюсь, этого будет достаточно, чтобы начать кого-то, кто хочет работать над реальным инструментом. Он не делает ничего сложного, например, проверку функций с переупорядоченными параметрами, а библиотека метаданных немного требовательна к использованию полностью определенных имен, поэтому вам нужно быть немного осторожнее. Чтобы ответить на вопрос в вашем оригинальном посте, я выполнил
find "('a -> Microsoft.FSharp.Core.bool) -> Microsoft.FSharp.Collections.list`1<'a> -> Microsoft.FSharp.Core.int"
и получил следующий список кандидатов:
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
Из них только List.findIndex
имеет именно тот универсальный тип, который вы ищете, но с правильной комбинацией параметров типа, так же как и остальные (например, если 'a = int
затем List.find
имеет желаемый тип). К сожалению, ограничения не учитываются при поиске, поэтомуList
функции не могут совпадать.
Без лишних слов, вот код, который я использовал - вам нужно добавить ссылку на сборку FSharp.PowerPack.Metadata, чтобы она заработала.
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
| _ -> () }
Это самое последнее и самое лучшее: http://fsdn.azurewebsites.net/
Из документов: https://github.com/fsdn-projects/FSDN
Поддерживаемые подписи API
API signature Query example Functions and values in modules int -> string Fields of records and structs Ref<'a> => 'a Methods and properties 'a list -> int or 'a list => int Constructors string -> Uri Names (function and method names) head : 'a list -> 'a Active patterns (||) : ... -> Expr -> ?