Pull to refresh

Comments 68

Спасибо за статью.

Кажется, что «Размеченное объединение» принято называть алгебраическим типом данных.

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


Почему очевидно?
ну потому что они были написаны задолго до появления в F# и полагаю, задолго до того, как разработчики .NET даже подумали о том, что он может появится. Без функциональной парадигмы каррируемость совершенно бесполезна, потому что в том же C# функции (методы) вовсе не являются значениями. Там для этого выделен специальный класс Action и делегаты собственно, но это все костылики =))
Потому, что есть, например, перегрузка методов по типу и количеству параметров. то есть если написано:

Class.test b

где b — String

Непонятно, что имеется ввиду:
— метод test(string, string) -> int и надо вернуть string -> int
— метод test(sting) -> int и надо вернуть int

таким мобразом .NET методы фактически получают один параметр — кортеж

Good, но не совсем — можно было бы использовать вывод типов для разруливания таких проблем.
>можно было бы использовать вывод типов для разруливания таких проблем.

Как?
let i = test «Hello»
Как определить, i должно иметь тип int или string → int?

>Кажется, что «Размеченное объединение» принято называть алгебраическим типом данных.

Алг. типы данных — более общее понятие. Можно сказать так: алг. типы данных в F# реализованы в терминах размеченных объединений (discriminated unions).
По дальнейшему использованию=)

Если дальше в коде написано let j = i + 5, то тип i — int, а если написано let j = i «qwerty», то string → int.
>По дальнейшему использованию=)

F# строго типизированный язык.

>Если дальше в коде написано let j = i + 5, то тип i — int, а если написано let j = i «qwerty», то string → int.

Дальнейшее использование может быть слишком «дальнейшим». Например, использование может быть в клиентском коде, т. е. в момент компиляции test «Hello» ничего не известно о том, как результат такого вызова будет использоваться. Грубо говоря, библиотечная функция, возвращающая test «Hello» может быть в одной dll'ке, а твой let j = i + 5 — в другой. Первая dll'ка ничего не должна знать о второй.
Вывод типов не противоречит строгой типизации.

Второй частью вы затрагиваете вопрос, который не был описан в статье — разбиение программы на модули. Например, является ли глобальная константа let j = 1 доступной вне сборки.

В любом случае эту неоднозначность можно разрешить, если обязать программиста фиксировать тип интерфейса глобальных публичных объектов, таких как сигнатура функции, interface, класс или константа, а вывод типов использовать во всех оставшихся случаях.
>Вывод типов не противоречит строгой типизации.

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

>… если обязать программиста фиксировать тип интерфейса глобальных публичных объектов.

Это противоречит твоему желанию «[определить тип] по дальнейшему использованию=)»
Но противоречит, если тип должен определяться контекстом будущего использования


Если ограничить вывод типов компилируемым модулем, то противоречие снимается. Этого можно достичь, если зафиксировать тип объектов, которые видны вне модуля.

Мое желание указывать тип как можно меньше (но оставаться в рамках строгой типизации) и работать с .net кодом так, что бы имея дело со сборкой не иметь ввиду на каком языке она написана, то есть хочу однородности. Я не против того, что бы указать тип публичных объектов сборки, но обычно большая часть кода приходиться на тело методов/функций, поэтому хорошо бы иметь там вывод типов.

Это сделать реально, в качестве примеров можно привести haskell, кроме того это реально сделать и для .NET — существует Nemerle. Например, в нем можно создать generic Dictionary без указания параметров типов — компилятор сам их выведет по его использованию.
Модули и сборки не при чём. Вопрос в неоднозначности выбора варианта перегрузки и в неоднозначности выбора перегруженная-функция/каррированная-функция.

Предположим, есть две перегрузки некоторой функции:
SomeFunc: (int) → SomeClass
SomeFunc: (string → int) → SomeClass

let i = test «Hello» // Тип int или string → int, если был бы «вывод типов шиворот навыворот».
let j = SomeFunc i // Какая перегрузка должна вызваться?

Обычно в современных языках тип выражения слева от «=» определяется по типу выражения справа от «=». Ты же предлагаешь наоборот — значение типа справа от «=» выбирать по будущему типу выражения слева от «=».
Теперь все ОК: каррированию мешает отсутствие мощной системы вывода типов, а ей мешает наличие возможности перегружать методы.

Хотя мне кажется, что такой код встречается не часто и избежать неоднозначности можно явно объявлять тип там, где его не может вывести компилятор. То есть писать что-то вроде let i: string → int = test «Hello».
Но противоречит, если тип должен определяться контекстом будущего использования.

import Control.Concurrent

main = do
    c <- newChan     -- newChan :: Chan a
    writeChan c 10   -- уточняем до Num a => Chan a
    writeChan c 2.6  -- всё ещё нормально, уточняем до Fractional a => Chan a
    writeChan c "ab" -- врёшь, не пройдёшь!

Внезапно, тип выражения стал зависимым от дальнейшего использования. В до жути строго типизированном языке.
— newChan :: Chan a

IO (Chan a) конечно же, прошу прощения.
В OCaml такой эффект можно наблюдать с Hashtbl.t.
да, все вопросы видимости-невидимости остались за пределами, и так уже многовато вышло ) Как-нибудь в другой раз.
на самом деле все таким образом определенные идентификаторы (что функции, что значения) можно видеть из другой сборки, установив референс.
Можно определить module или namespace в файле, но если они не определены, то считается, что имя файла = имя модуля.
шикарная статья, но где-то посередине у меня сломался мозг. Что это, неприятие сознанием ФП или просто вечерняя усталость, сказать затрудняюсь. Было бы отлично воспринимать такую информацию каплями, а не бурным потоком. Но все равно — спасибо. Буду считать вашу статью введением в язык.
ну я примерно с этой целью ее и писал — как введение.
Я конечно уже апостериори думал поделить ее на три, но поскольку изначально мысль была именно показать рядом все три стороны, то уж решил будь как будет.
Неприятие ФП. Средний императивный прогер достигает нирваны начинает понимать ФП после 5-10 часов медитирования над своим кодом в Haskell/F#/OCaml.
Ага а еще в Lisp, АФС и даже Perl )
Из всех виденных мной языков сложнее всех давался Prolog. Даже Lisp по сравнению с ним прост в понимании.

Ах да, и ещё есть J, который я не осилил до сих пор. rosettacode.org/wiki/Spiral#J
Да Prolog просто взламывает мозг своим синтаксисом после обычного программирования.
Хорошая статья. Прочитал с удовольсвием, спасибо.
Статья хорошая, но язык показался непродуманным: разный синтаксис в похожих ситуациях, недодуманный синтаксис, специальный синтаксис для ситуаций, которые должен разрешать интерпретатор/компилятор и т. д.

Например, если сравнивать с Python, то я выберу Python.
возможно в чем-то вы правы, но во-первых это язык еще развивается, и последняя версия — всего лишь CTP. Так что возможно кое-какие шероховатости исправят.
Питон сам по себе на данный момент наверняка более вылизан и продуман, однако пара F#- .NET CLR выглядит очень перспективно имхо.
В F# я вижу мало отличий от OCaml.
Возразить особо нечего. Ну так это ведь не тайна за семью печатями, что F# — новый «сын» ML серии. Однако и нельзя на этом основании делать вывод, что он бесполезен, поскольку есть OCaml.
В основном все отличия связаны с интеграцией в .NET.

Кстати, похожесть на OCaml лично мне помогла при освоении языка. Скажем, про fslex, fsyacc в сети практически нет документации, зато она есть про ocamllex, ocamlyacc. Кроме того, есть хорошие учебники по OCaml, что способствует.
История равзития семейства ML несколько более чем у Python.
Так что я не согласен с тем, что синтаксис непродуманный.
В каком он месте не продуман? В питоне даже лямбда только однострочная, и это только из-за упрощения синтаксиса.
В Питоне масса недостатков и однострочная лямбда — далеко не самый тяжёлый, но тут их целая куча. Точнее, у меня ощущение, что синтаксис целиком непродуман.
Не, ну сахера конечно много но в целом вполне нормальный синтаксис.
>разный синтаксис в похожих ситуациях
Мне после лиспа так любой синтаксис кажется перегруженным и усложнённым :)
С другой стороны этот синтаксис можно довольно легко прочитать, а чтобы прочитать более-менее длинную программу на лисп или хаскелл, нужно кажется постичь дзен. =)
бугога… питон сравнивать с F#/окамл :D
есть задачи и критерии для задач, при которых ЯП выбирается не исходя из «продуманного и простого синтаксиса»

посмотрите например тесты по производительности вычислений и все поймете — что питон один из тормозных языков и несмотря на его красивость я бы выбрал скриптовоя язык lua с jit

ну а окамлу и F# конкуренты только другие фп языки.
Я смотрю тесты. По моим сведениям Python один из самых производительных скриптовых языков.
В том-то и дело, что скриптовый. GIL и reference counting вместо нормальной сборки мусора это конечно да…
Кстати, с OCaml я Python не сравниваю, я сравниваю с F#. Причём, я знаю приличное количество языков, чтобы видеть, что синтаксис F# не продуман.
Да… Автор не обманул. Почитаю позже, потому что заинтересовало. Спасибо за статью.
Вопрос автору.

Однажды прочитал мнение, что F# приводит к тому, что программист больше думает, меньше программирует.

Я сейчас смотрю на эти программки и читается текст очень тяжело. Может потому, что я к синтаксису не привык?
Вот я вижу, вы уже изрядно попользовались F#.

На ваш взгляд, что легче будет: прочитать сложный алгоритм, реализованный на процедурном (ооп) языке программирования или на функциональном?
Знаете, я скажу так: это дело привычки, что там, что там. На ФЯП во многих случаях код получается значительно короче и декларативней, чем на ООЯП — это правда. Но не всегда.
А вообще любой сложный код читается хорошо в первую очередь когда есть комменты, без них и в сишном коде закопаться легче легкого :)
Чем дальше в лес, тем больше у MS языков программирования новых
не припомню так уж много новых ЯП у MS. Вроде уже x последних лет упорно держатся за C#.
трудно будет людям, которые захотят начать программировать
могу сказать по личному опыту — далеко не так, как кажется на первый взгляд. На поверку все оказывается довольно логично.
Мне в настоящий момент не очень нравиться как компилятор F# генерирует CIL-код. Например, если в классе F# создать поле, то ILDASM показывает, что кроме поля генерируються get и set свойства, при этом модификаторы доступа к полю (имеется ввиду CIL поле) указать в F# нельзя, в итоге, в C# можно напрямую обращаться к полям, хотя доступны и свойства, просто по умолчанию у поля почему-то public — прямое нарушение принципа инкапсуляции. А если в F# к полю создать свойства, как в примере выше, то из C# вообще будет видно 4 свойства и 1 поле. Хотя, наверное, к релизу подправят.
Тяжкий комент получился.
Лишний код в CIL наверно поправят, насчет видимости — в F# есть все те же самые модификаторы private, inline, public, только порядок другой — здесь модификатор должен непосредственно предшествовать идентификатору.
Написав val mutable private prVal: string получим изменяемое поле видимое только внутри класса. То же самое с member, причем модификаторы как и в сишарп можно ставить и перед get и set отдельно, если есть необходимость.
Почему по умолчанию поля члены класса сделаны публичными — могу только догадываться. Вроде в Object Pascal так же было (вспоминая школьные годы =))
После Nemerle всё читается легко и понятно :) Хотя, конечно, Nemerle бы увидеть в VS 2010 на месте F#, я бы за такое дорого дал. Но — что уж поделать. Тяжело расставаться с фигурными скобочками ;-) Я бы лучше уж begin\end писал, как в старые добрые времена, чем на пробелы завязываться.
Мне кажется что Nemerle тоже рассматривался в качестве прототипа. Но отдали предпочтение Ocaml.
А писать Begin end, ставить ;; в конце операторов и использовать функции внутри друг друга с помощью in вполне можно и сейчас, #light не отключает эту возможность, а всего лишь добавляет другую. Ну в крайнем случае можно эту директиву стереть, чтобы не смущала :)
да, я понял, что не исключает)
не знаете, кстати, почему отдали предпочтение Ocaml? Ведь Nemerle гораздо более «C# friendly»-язык, порог вхождения для был бы меньше и т.п.
* для => для существующих C#-программистов
Честно отвечу — не знаю. Я Nemerle смотрел в свое время, но не так сильно, чтобы мог хоть с какой-то убедительностью пытаться их сравнивать.
А у меня вот какой вопрос — из чего состоит область промышленного применения ФЯП? Вообще, для чего F# нужен, кроме того, что он используются в реализации математических алгоритмов, вы не сказали. Как с его помощью деньги то заработать и прибыль бизнесу принести?
Вот я тупой прогер — создаю формочки и клиенты всякие для заказчиков, во всякие базы данных лазию и инфу оттуда гоняют туда — обратно. Так вот, я правильно понимаю, что для моего профилирования F# мне не помошник?

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

Может, студентов на лабах учить алгоритмы программировать? Вряд ли они променяют на F# любимый matlab.
вопрос конечно фундаментальный, и в двух словах не ответить.
На самом деле у ФП в целом и F# много преимуществ перед императивными языками, например простота в распараллеливании вычислений, вытекающая из самой идеологии (что кстати очень актуально в связи с хз сколькими ядрами на проце, которые толком и не использует-то никто), обработка больших объемов данных кстати, из бд, тоже будет писаться проще и выглядеть красивее. Linq то глядите как приглянулся всем (ну многим по крайней мере), а ведь по сути — это и есть шажок к ФП. Ну с другой стороны конечно интерфейсы пока явно не стезя F#. =)
Насчет быстродействия — конечно плюсам он на одном ядре проигрывает, а уже C# — не так чтобы очень сильно (голословно, пруфлинка нету). С другой стороны если взять в расчет распараллеливание, то уже могут быть варианты.
Ага. Вот вы статью хорошую написали, эдакий «живой» справочник, но что с ней делать обычному рабочему люду и как ее использовать — непонятно. Все хорошо разжевано, но как интегрировать решения на F# в существующие подходы — тоже неясно — надо идти копать в мсдн. Вообще, имхо, самая первая, нулевая проблема всех новых фишек и рюшек, аля нового ФЯП от Microsoft в том, что изначально мало кто соображает, как его применять.

Статья была бы еще более ценнее если бы она была ну хоть капельку похожа на how-to подход. Например — у нас вот у всех есть вот такого рода решения задачи, изначально (через шарп, к примеру), она решается вот так. А теперь у нас есть F# и решить эту задачу можно вот так-то. Смотрите, какие бонусы и плюсы мы получаем, теперь это работает быстрее, глюков меньше, проблемы решаются такие-то и такие-то.

Вот каким образом мне, .net девелоперу использовать в своих задачах F#? Я не знаю, мне надо идти ресерчить это дело, я потрачу свое время, чтобы разрюхать, как мне подключить к решению F#. И только после этого я возьмусь за вашу статью, а без этого — нет смысла.

Во всех статьях на хабре, что проскальзывали по ф-шарпу, нет точки входа к возможности воспользоваться предложенными знаниями. Ну что мне толку с алгоритма определения последовательностей чисел Фибоначчи? Никакого, поверьте:)

А вот если бы была разжевана актуальная задача, ну например более удобная реорганизация большого (огромного) массива данных в структуры данных удобные для построения отчетов — этож цены такой статье бы не было! У нас вот восьмиядерник каждую ночь перемалывает стастистику по вводу и проработке нормативных правовых актов по всей РФ, так мы вынуждены весь процессинг в хранимых процедурах вести, по другому квалификации не хватает и времени, чтобы разобраться. Ну это я так, к слову о наболевшем:)
я в следующей статье собираюсь выложить код небольшой утилиты на F#, которую лично написал, и которая действительно была мне полезна по работе. Она конечно не бог весть что, но явно не числа Фибоначчи (которые кстати везде где я видел на хабре написаны неправильно, потому что не учитывают возможный StackOverflow :)).
Думаю дня через три-четыре будет, а то я еще от написания этой не отошел. Можете заглянуть.
>Я не знаю, мне надо идти ресерчить это дело

Смотреть видеоролики не так напряжно, как идти чего-то ресерчить. Начни с презентации на PDC 2008, 300 Мб: channel9.msdn.com/pdc2008/TL11/

>А теперь у нас есть F# и решить эту задачу можно вот так-то.

В этом докладе есть подобные наглядные примеры.
Ингода, просто доставляет удовольствие «помучить» новые языки программирования. По F# могу посоветовать книгу самого Дона Сайма. Мне у него понравились примеры использования F# при программировании на ASP.NET — очень эффективно с различными типами данных можно работать. Для рисования формочек F# наврядли подойдет, да MS и не позиционирует его с этой стороны. По поводу библиотек, то я бы лично не рискнул на F# их писать, пока сыроват F# для этих дел.
Если касаться вопросов ЦОС, то здесь лучше использовать спциализированную аппаратуру или мощнсти современных GPU, управляемый код здесь совсем не нужен.
Дык вот ктоб на хабр подобный подход запостил (F# + ASP.NET)? :)
Променяем ;-) Хотя бы в общеобразовательных целях. Да, о применении много вопросов, но с другой стороны, выходит, что все спрашивают, а применять никто почти и не пытается. Мне приходилось сталкиваться при поиске работы по ключевому слову F# (просто ради интереса), с какой-то финансовой компанией, где требовался аналитик со знанием F#. Я думаю, что стоит говорить об успешности его применения после широкого внедрения Microsoft Visual Studio 2010.

А пока для примера хотелось бы продемонстрировать код метода градиентного спуска (курс: численные методы оптимизации) и сделать пару комментариев.

Променяем ;-) Хотя бы в общеобразовательных целях. Да, о применении много вопросов, но с другой стороны, выходит, что все спрашивают, а применять никто почти и не пытается. Мне приходилось сталкиваться при поиске работы по ключевому слову F# (просто ради интереса), с какой-то финансовой компанией, где требовался аналитик со знанием F#. Я думаю, что стоит говорить об успешности его применения после широкого внедрения Microsoft Visual Studio 2010.

А пока для примера хотелось бы продемонстрировать код метода градиентного спуска (курс: численные методы оптимизации) и сделать пару комментариев.

#light

(* Один шаг метода градиентного спуска *)
(* points — некоторая начальная точка в n-мерном пространстве
derivates — список частных производных по каждой из координат точки.
Подразумевается, что размерности points и derivates совпадают
rezFunction — целевая функция *)
let GradientDescent point derivates rezFunction =
(* Начальный шаг *)
let eta = 1.0
(* Конечный шаг *)
let eps = 1.0e-15
let grad = derivates |> List.map (fun f -> f point)
let currentValue = rezFunction point
let rec gradStep currentPoint etaCurrent =
if etaCurrent < eps then
currentPoint
else
let newPoint = List.map2 (fun pointCoordinate gradCoordinate -> pointCoordinate — etaCurrent * gradCoordinate) currentPoint grad
let newValue = rezFunction newPoint
if newValue < currentValue then
newPoint
else
gradStep currentPoint (0.5 * etaCurrent)
gradStep point eta

Преимущество подхода, на мой взгляд в том, что функция не зависит от передаваемых ей частных производных. Конечно, это можно написать и на C/C++/C#, но на этих языках это выглядит несколько неественно, а в этом случае, при создании списка функций-«частных переменных» используется каррирование, что делает в этом случае язык F# крайне удобным инструментом.
> применять его выше по коду, чем место его определения, нельзя

а если очень хочется? например, когда две функции вызывают друг друга.

> не получив полного набора параметров, она попросту возвращает другую функцию

то есть, если я забыл один параметр, то вместо сообщения об ошибке я получую чёрт знает что?

> Списки задаются в прямоугольных скобках [ ], последовательности — в {}, массивы — в [| |].

зачем стока? в чём их отличия?
1. Не знаю, когда такая способность может пригодиться, но если очень нужно, тогда можно сделать методами класса — с ними все как обычно.
2. Ну почему черт знает что — другую функцию. Ну в конце концов, вы же ее тоже где-то будете использовать? Там и получите вашу ошибку, о том, что пытаетесь подставить значение неверного типа (ну например требуется int, а вы ставите int -> int). Ну честно говоря не разу еще в такую ситуацию не попадал.
3. Чем-то да отличаются ) Списки имеют тип, который я выше показал, их можно легко разбирать pattern matching'ом, массивы являются изменяемым типом, их элементы можно изменять по отдельности. Последовательности — это просто здешний мем для IEnumerable, то есть ею может быть все что угодно. А еще они могут быть бесконечными =)
1. то есть к классу можно обращаться независимо от того где он определён? ну и какой подход тогда более декларативен? ;-)

2. трабла в том, что я получу ошибку не там, где она на самом деле есть. и придётся бегать с отладчиком в поисках места, где же вместо инта появляется функция.

не туда откоментил, сорри.
1. я этого не говорил. внутри класса можно обращаться как хочется:
type Sample = class
new () = {}
member x.A i = x.B i
member x.B i = x.A i
end
а к самому классу можно обращаться только ниже, как в пределах файла, так и в пределах проекта (порядок файлов имеет значение) Из другого проекта — пожалуйста, реф поставить и вперед.

2. Ну чем меньше возможностей, тем меньше проблем, как бы. :) Но вообще я думаю вряд ли возможно увести это неверное значение дальше чем на один шаг.
В принципе все функциональные языки делаются под ряд определенных задач, потому они удобны. А синтаксис кстати не такой уж и сложный, по крайней мере не вызывает отвращения и страха.
UFO just landed and posted this here
Only those users with full accounts are able to leave comments. Log in, please.