Есть ли в документации F # поиск функций по их типам?
Скажем, я хочу знать, есть ли у F # функция библиотеки типа
('T -> bool) -> 'T list -> int
т.е. то, что подсчитывает количество элементов списка, для которых функция возвращает true. (или возвращает индекс первого элемента, который возвращает true)
Я использовал большой список на сайте MSR для F #, прежде чем документация по MSDN была готова. Я мог бы просто найти страницу для вышеуказанного текста, потому что типы были перечислены. Но теперь в документации MSDN перечислены только типы на отдельных страницах - страница модуля представляет собой макет описательного текста. Google kinda-sorta работает, но он не может помочь с
// 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 или что-то еще?
Ответы
Ответ 1
На основе ответа kvb я создал полное приложение. Он размещался на github в http://github.com/sandersn/fing.
Код по-прежнему довольно уродлив, но он работает для простых случаев. На данный момент я вынул kvb most-general-unifier (mgu
), потому что он добавляет много неочевидных результатов. Необычные вещи, такие как структурные ограничения и большинство общего супертипа, еще не работают.
Также существует двоичная версия для командной строки, если вы не хотите строить из исходного кода. (Тем не менее, по-прежнему требуется современная версия среды выполнения .NET.) В конце концов, я найду какой-нибудь хостинг ASP.NET, изучу ASP и обернуту все это в веб-приложении, чтобы установка вообще не требовалась. (Я предполагаю, что если есть спрос, я могу создать клиентский GUI, но у меня еще меньше опыта с подобными вещами.)
Ответ 2
Я не знаю такого инструмента. Однако было бы забавным занятие, которое можно было бы написать с помощью System.Reflection
(или даже лучше, библиотеки метаданных в PowerPack), чтобы вы могли учитывать эквивалентные имена переменных типа modulo и т.д.
EDIT. Я был прав - это было весело. В последующем много бородавок, но это не так уж плохо для ~ 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
| _ -> () }
Ответ 3
Это последнее и самое лучшее: 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 -> ?