Работая в F#, я ни разу не сталкивался с необходимостью использовать нечто в этом роде, но тем не менее оно есть. У F# нет ключевого слова для создания таких функций, но мы можем использовать для этого атрибуты:
//using params analogue in F#
type SomeBoilerplateType =
static member Add ([<ParamArray>] x) = x |> (Array.map float) |> Array.sum
SomeBoilerplateType.Add(1, 2)
SomeBoilerplateType.Add(1,2,3,4,5)
SomeBoilerplateType.Add([|for i in 0..10 -> i|])
Печально, что использовать это можно только в методах классов (как и, например,
ad hoc-полиморфизм (секция "Overloaded Methods"),
именованные и
опциональные аргументы (перейдя по последней ссылке, промотайте вниз :) )).
Кстати, есть ещё
кое-какие тонкости.
Индексаторы
Индексаторы являются "официальной" фишкой F#, только называются они не Indexers, как в C#, а
Indexed Properties. На MSDN написано про них практически всё, кроме одной детали: вместо того, чтобы именовать свойство, используемое для индексирования, как Item, можно дать ему любое другое имя, но при этом также задать классу атрибут [<DefaultMember("name")>], где Name — имя свойства.
Пример с Item:
//indexers for F#
type Slowpoke() =
let hidden = [|for i in 0..10 -> i|]
let mutable current = 0
member this.Item
with get(_) =
let res = hidden.[current]
current <- (current + 1) % hidden.Length
System.Threading.Thread.Sleep(1000)
res
let slowpoke = new Slowpoke()
slowpoke.[666]
slowpoke.[666]
Пример с атрибутом (заодно — другой вариант синтаксиса для свойства без сеттера):
open System.Reflection
[<DefaultMember("AnotherName")>]
type Slowpoke'() =
let hidden = [|for i in 0..10 -> i|]
let mutable current = 0
member this.AnotherName(_) =
let res = hidden.[current]
current <- (current + 1) % hidden.Length
System.Threading.Thread.Sleep(1000)
res
let slowpoke' = new Slowpoke'()
slowpoke'.[156]
Кстати, на самом деле, для примера с Item компилятор просто сам добавляет этот атрибут:
typeof<Slowpoke>.GetCustomAttributes(false)
> [|System.Reflection.DefaultMemberAttribute
{MemberName = "Item";
TypeId = System.Reflection.DefaultMemberAttribute;}; ... |]
Функции с аргументами-типами (Type Functions)
Кстати, о
typeof
. Это функция. Не не простая, а так называемая
Type Function — функция, которая принимает в качестве своих аргументов типы, а не какие-то значения. Такие функции можно объявлять только в модулях, в классах же, типах и выражениях их объявлять нельзя.
Например, мы можем объявить вот такую функцию:
let set = new System.Collections.Generic.HashSet()
let hasBeenAlreadyCalledWithThisType<'T> = not <| set.Add(typeof<'T>.Name)
hasBeenAlreadyCalledWithThisType<int> //false
hasBeenAlreadyCalledWithThisType<float> //false
hasBeenAlreadyCalledWithThisType<int> //true
Эта функция будет вести список всех типов, с которыми она была вызвана и отвечать true/false в зависимости от того, "знаком" ли ей уже текущий тип.
Подробнее о type functions вы можете почитать
в спецификации F#.
[<AutoOpen>]
Я просто процитирую спецификацию:
When applied to an assembly and given a string argument, causes the namespace or module to be opened automatically when the assembly is referenced. When applied to a module without a string argument, causes the module to be opened automatically when the enclosing namespace or module is opened.
Компилятор, голос!
Можно заставить компилятор выдавать заданные нами сообщения с помощью атрибута
[<CompilerMessage("SomeUserSpecifiedMessage", MessageNumber)>].
P.S.: Были кое-какие проблемы с отображением кода F#, я решил их. При использовании F# brush для SyntaxHighlighter'а нужно закомментить строки про symbolic-класс, иначе будут глюки.