суббота, 14 марта 2009 г.

F# : Начало науки

Начал изучать данный язык и столкнулся с нехваткой литературы. Видел только 3 книжки на английском и пару статей на Хабре и GotDotNet. И то, они что-то давно не обновлялись. А нехватка литературы ощущается потому что: 1) мало кто еще изучает язык -> тяжело найти ответ на возникший вопрос. 2) Функциональное программирование для обычного программиста довольно сложная штука, даже не смотря на то, что почти полностью прочел книжку по Прологу. 3) не ясны особенности языка. Так, например, при указании параметров функции "f x y z -1" и "f x y z-1" разные вещи. Также влияет табуляция строки:
let rec run f x n =
match n with
| t when t <= 0 -> ()
| _ ->   f x
run f x (n-1)
и
let rec run f x n =
match n with
| t when t <= 0 -> ()
| _ ->   f x
run f x (n-1)
Разные вещи. Еще с ";" не все ясно. Так что вопросов море.
Также хочу сказать, что не собираюсь пытаться писать для обучения языку. Здесь скорее всего будут какие-то наработки или интересные моменты.
Ну и первое, что рассмотрим на F# - нахождение производных. Собственно сама формула выглядит так:
let d (f : float->float) sigma x =
let dx = sqrt sigma
(f (x+dx) - f (x-dx)) / (2.0*dx);
Думаю тут все ясно. Хочется только показать красивый способ задания самой функции производной:
let f' x =  d f sigma x;
Теперь производную можно найти в любой точке таким образом: f' x или кому проще - f'(x).
Для тестирования возьмем такую функцию:
let f1 x =  x ** 3.0 - x - 1.0
let f2 x =  x*x*x - x - 1.0
Здесь появляется оператор **, который возводит в степень. Конечно удобная вещь, давно мечтал, но мы еще его вспомним.
Для точности проведем 5 млн. измерений. Для этого воспользуемся как вышеуказанной функцией run, так и такой модификацией:
let rec run2 f x n =
for i=0 to n-1 do
f x
Кстати, не претендую на очень качественные тесты, так что топтать нет смысла, но если предложите вариант, да еще не сильно усложняющий код - буду рад. Так вот замер времени будет производится следующим образом:
let time f =
let timer = System.Diagnostics.Stopwatch.StartNew ()
f ()
let t = timer.Elapsed
printfn "Time taken = %O" t

Ну и собственно все, дальше делаем всевозможные комбинации и получаем 4 варианта:
let f'1 x =  d (fun x -> x ** 3.0 - x - 1.0) sigma x |> ignore
let f'2 x =  d (fun x -> x*x*x - x - 1.0) sigma x  |> ignore
let f'3 x =  d f1 sigma x  |> ignore
let f'4 x =  d f2 sigma x  |> ignore
ignore - для указания, что функция возвращает unit.
Ну и еще для разных run и run2. получим так:
printfn "With recursion:"
time (fun q -> run f'1 t N)
time (fun q -> run f'2 t N)
time (fun q -> run f'3 t N)
time (fun q -> run f'4 t N)
printfn "With 'for' operator:"
time (fun q -> run2 f'1 t N)
time (fun q -> run2 f'2 t N)
time (fun q -> run2 f'3 t N)
time (fun q -> run2 f'4 t N)

Так же, для сравнения, сделаем похожие вещи на C# и Matlab (если кого заинтересует, могу выложить и их код). Ну и собственно результаты измерений (в секундах):
matlab - 63.7067
C# - 0.4072
C# - 0.4320 (через делегат)

F# - рекурсия:
f'1 - 1.5936
f'2 - 0.4817
f'3 - 1.8626
f'4 - 0.6929

F# - цикл:
f'1 - 1.5817
f'2 - 0.4471
f'3 - 1.8588
f'4 - 0.7032
Ну что можно сказать, оператор ** довольно сильно тормозит, так что нужно следить, где его использовать. Также, я не ожидал что F# будет быстрее C#, так что результат меня вполне удовлетворил. А вот Matlab все-таки MATrix LABoratory, так что в ближайшее время посмотрю, кто из них быстрее списки перемножает :)

Комментариев нет: