Сравнительно недавно 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