Первый взгляд на Visual F#

Сравнительно недавно Microsoft выпустили новый язык программирования — Visual F#.

Компилятор и транслятор Visual F# уже включены в .NET Framework 4, а среда Visual Studio 2010 поддерживает проекты F#. Так, как его разработкой занимался Дон Сайм, участвовавший в разработке OCaml, сам VF# оказался калькой OCaml для работы под .NET: различий между ними практически нет.

Одно из отличий Visual F# от других .NET языков — он не императивный, а функциональный с примесью декларативности. Поясняю: императивный язык — язык, на котором описывается конкретная последовательность действий, другими словами не что надо получить, а как это получить; функциональный язык — как математическая запись, где всё выражается функциями; декларативный язык — противоположность императивного: описывается что надо получить, а не как.

Что ж, матчасти хватит, приступим к обзору.

Синтаксис

Синтаксис F# после всех других языков кажется очень странным: если во всех остальных языках блоки кода как-то отделялись, то в F# разделение на блоки кода обеспечивает отступ. То есть код относится к тому блоку, отступ которого на 1 меньше. После C–подобных языков адаптация, как мне кажется, не займёт долгое время, т. к. при правильном форматировании там блок кода также на 1 отступ дальше заголовка. Напрмер:

open System
let THE_CONSTANT_VALUE = 250
let main =
   printf "THE_CONSTANT_VALUE = %d" THE_CONSTANT_VALUE
   printf "Press any key to exit. . ."
   Console.ReadKey()

Функциональность

Функциональная концепция F# требует времени, чтобы привыкнуть, больше, чем синтаксис. В этом языке всё представляется как некоторая функция, возвращающая некоторое значение. Переменные есть, но работа с ними несколько затруднена, но об этом позже. Плюс к тому, каждое объявление начинается с кейворда let(по-русски пусть).

let ten = 10 // таким образом, при обращении к имени ten получим 10

Переменные

Работа с переменными в F# несколько затруднена. Если объявить что-то путём, показанным выше, то это будет константа и её значение модифицировать невозможно. Есть ещё 2 пути:

let mutable myInt = 25
myInt <- myInt - 10

и

let ref myFloat = 12.5
myFloat := !myFloat - 2.5

Первый путь объявляет переменную, как просто поле и доступ к ней вполне нормальный ( да–да, в F# присваивание это <-, а = — это инициализация значения и сравнение ). Но такую переменную нельзя использовать в субъектах.
Второй путь объявляет переменную другого типа. Её можно использовать во вложенных объектах, но доступ к ней другой: присваивание — :=, а обращение к значению — !.

Массивы

В F# есть много инструментов для работы с массивами и их заполнения. Например, массив можно заполнять циклами прямо из объявления:

let lstA = [ 0..10 ] // числа от 1 до 10
let lstB = [ 1..2..10 ] // 1, 3, 5, 7, 9
let arrA = [| for i in 0..10 -> i, i * i |] // (0, 0) , (1, 1) , (2, 4)...

Что интересно, массивы могут быть какими угодно, из чего угодно благодаря динамической типизации F#. Отличаются от списков они тем, что им можно переназначать элементы, а список как задан — так и задан.

Методы

По части методов F# много чем отличается от всего остального. Начнём с того, что F# полностью поддерживает каррирование методов. То есть, допустим, имеем код:

let add a b = a + b
let addTen = add 10
// здесь метод addTen уже задаёт один аргумент методу add, поэтому строка
printf "%d" ( addTen 15 )// выведет 25

Но методы .NET не поддерживают каррируемость, поэтому вызываются с помощью т.н. кортежа:

open System.IO
let fexists file:string : bool =
    System.IO.File.Exists( file )

Объектная ориентированность

Я думаю, если бы сейчас выпустили язык, не реализующий объектную ориентированность, то его уж точно никто не стал бы даже рассматривать. F# поддерживает разные контейнеры и структуры данных, он вообще неплохо подходит для обработки данных.

type PERSONE = { Name : string; Age : int }
let me = { Name = "DWf"; Age = 17 }

Это была простая запись(record), проверяется она по шаблону следующим образом:

match me with
    | "DWf", 17 -> "Здравствуйте, DarkWolf!"
    | _, i when i < 14 -> "Вон отсюда, школота!"
    | "Crypton", _ -> "Нащяльника..."
    | name, age -> "Ты %s и тебe %i... Не знаю таких" name age

То есть, match x with чем-то похож на switch из C, но наделён куда более широкими возможностями.

Теперь о классах. Класс в F# задаётся следующим образом:

type Myclass = class
    val intValue : int
    val strValue : string
    new ( i:int, s:string ) = {// В конструкторе можно только инициализировать
        intValue = i;          // переменные. Вызов методов невозможен.
        strValue = s }
    new () =
        new Myclass( 10, 5 )
    member m.INTEGER_VALUE : int =
        intValue
    member m.GetStringValue () : string = 
        strValue
end

Конструкторы в классах F# обязательны. Путь это будет new () = { }, но он должен быть.
Здесь m.INTEGER_VALUE — это свойство, отдающее значение intValue, а m.GetStringValue () — метод, возвращающий strValue. Здесь префикс m. создаёт алиас текущего экземпляра класса, то есть, заменяет this из того же C++/C#.

К слову сказать, F# определяет возвращаемое значение метода, если оно не указано в заголовке, по типу последнего выражения метода.

В F#, никто не мог подумать, есть интерфейсы и наследование.

Например, так имплементируется интерфейс:

type IStringSerializable = interface
    abstract Serialize : maxLen : int -> string
end

type MyClass = class
    val mutable ID   : int
    val mutable Name : string
    new () = {
        ID = 0;
        Name = "UNNAMED"
    }
    interface IStringSerializable with
        member mc.Serialize mLen = 
            sprintf "MyClass:INSTANCE={ID=%d;Name=%s}" mc.ID mc.Name
    end
end

А вот так наследуется класс:

type IDObject = class
    val mutable id : int
    new () = { id = -1 }
    member ido.ID
        with get () = ido.id
        and set v = ido.id <- v
end

type NamedObject( _name : string ) = class
    val mutable name = _name
    inherit IDObject
    member no.Name
        with get () = no.name
        and set v = no.name <- v
end

Также, приятно порадовала инициализация свойств класса из конструктора ( в примере № 2 ).

В общем, пока мало, что можно сказать о F#, кроме того, что он неплохо ( очень даже ) ориентирован на обработку данных и проведение математических вычислений. Прикладное применение F# со временем покажет свои плюсы и минусы.

В заключение приведу примеры 2 первых приложений, написанных мной на F#.

Пример 1: консольное приложение с классом.

open System

//
// Возведение в степень
//
// Аргументы:
// num : int32 - Число
// pwr : int32 - Степень
// Возврат: int32
// Число num в степени pwr
//
let rec Power ( num : int32 ) ( pwr : int32 ) =
    if pwr = 1 then num
    else num * Power num ( pwr - 1 )

//
// Тестовый класс
//
type TestType = class
    // ПОЛЯ:
    // Поле X
    val x : int32
    // Поле Y
    val y : int32
    // КОНСТРУКТОРЫ:
    //     Пустой конструктор
    new () =
        new TestType( 0, 0 )
    //     Конструктор с заданием обоих полей
    new ( _x, _y ) = {
        x = _x;
        y = _y
    }
    // МЕТОДЫ КЛАССА:
    //     Сравнение X и Y
    //     Аргуенты: нет
    //     Возврат : число > 0, если x > y или число < 0 если x < y или 0, если x = y
    member tt.Compare () : int32 =
        if not ( tt.x = tt.y ) then tt.x - tt.y
    // else if tt.x < tt.y then tt.y - tt.x
        else 0
    //     Возведение X степень Y
    //     Аргументы: нет
    //     Возврат : x ^ y
    member tt.PowerXY () : int32 =
        Power tt.x tt.y;
    //     Возведение Y степень X
    //     Аргументы: нет
    //     Возврат : y ^ x
    member tt.PowerYX () : int32 =
        Power tt.y tt.x;
    //     Деление X на Y
    //     Аргументы: нет
    //     Возврат : частное от деления x на y
    member tt.DivXY () : int32 =
        tt.x / tt.y
    //     Деление Y на X
    //     Аргументы: нет
    //     Возврат : частное от деления y на x
    member tt.DivYX () : int32 =
        tt.y / tt.x
    // СВОЙСТВА КЛАССА:
    //     Сумма X и Y
    member tt.Summ : int32 =
        tt.x + tt.y
    //     Абсолютная разность X и Y
    member tt.Delta : int32 =
        abs ( tt.x - tt.y )
    //     Произведение X на Y
    member tt.Mult : int32 =
        tt.x * tt.y
end

let Main =
    printf "My First Serious Program on F#\n"
    let mutable _x = 0
    let mutable _y = 0
    printf "Input x: "
    _x <- System.Int32.Parse( Console.ReadLine() )
    printf "Input y: "
    _y <- System.Int32.Parse( Console.ReadLine() )
    let tt = new TestType( _x, _y )
    printf "TestType created with:\n\tTestType.x = %i\n\tTestType.y = %i\n\n" tt.x tt.y
    // 1
    printf "Calling TestType.Compare : unit -> int32. . .\n"
    printf "\tResult: %d\n\n" ( tt.Compare() )
    // 2
    printf "Calling TestType.PowerXY : unit -> int32. . .\n"
    printf "\tResult: %d\n\n" ( tt.PowerXY() )
    // 3
    printf "Calling TestType.PowerYX : unit -> int32. . .\n"
    printf "\tResult: %d\n\n" ( tt.PowerYX() )
    // 4
    printf "Calling TestType.DivXY : unit -> int32. . .\n"
    printf "\tResult: %d\n\n" ( tt.DivXY() )
    // 5
    printf "Calling TestType.DivYX : unit -> int32. . .\n"
    printf "\tResult: %d\n\n" ( tt.DivYX() )
    // 6
    printf "Getting TestType.Summ : int32. . .\n"
    printf "\tResult: %d\n\n" tt.Summ
    // 7
    printf "Getting TestType.Delta: int32. . .\n"
    printf "\tResult: %d\n\n" tt.Delta
    // 8
    printf "Getting TestType.Mult : int32. . .\n"
    printf "\tResult: %d\n\n" tt.Mult
    // 9
    printf "Test ended.\nPress any key to exit . . ."
    Console.ReadKey()

И пример 2: оконный Hello, World

open System
open System.Drawing 
open System.Windows.Forms
  
let Main =
    printf "Тест Windows Forms\n"
    printf "Создание формы. . .\n"
    let form = new System.Windows.Forms.Form( Height = 100, Width = 250, Text = "Hello, World!",
        StartPosition = FormStartPosition.CenterScreen, MinimizeBox = false, ShowIcon = false,
        MaximizeBox = false, FormBorderStyle = FormBorderStyle.FixedDialog )
    //
    let btn = new System.Windows.Forms.Button( Text = "Закрыть окно", Width = 200, Height = 25,
        Location = new System.Drawing.Point( 25, 25 ), DialogResult = DialogResult.OK )
    //
    form.Controls.Add( btn )
    form.ResumeLayout( true )
    //
    printf "Отображение формы. . .\n"
    if form.ShowDialog() = DialogResult.OK then
        printf "Форма закрыта кнопкой.\n"
    else
        printf "Форма закрыта кнопкой строки заголовка.\n"
    printf "Нажмите любую клавишу для выхода. . ."
    Console.ReadKey()

Василий «DarkWolf» Юмашов, 17.01.2010