F# меня испортил, или почему я больше не хочу писать на C#

    Раньше я очень любил C#


    Это был мой основной язык программирования, и каждый раз, когда я сравнивал его с другими, я радовался тому, что в свое время случайно выбрал именно его. Python и Javascript сразу проигрывают динамической типизацией (если к джаваскрипту понятие типизации вообще имеет смысл применять), Java уступает дженериками, отстутствием ивентов, value-типов, вытекающей из этого карусели с разделением примитивов и объектов на два лагеря и зеркальными классами-обертками вроде Integer, отсутствием пропертей и так далее. Одним словом — C# клевый.


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


    А потом я из любопытства попробовал F#.


    И что в нем такого?


    Буду краток, в порядке значимости для меня:


    • Иммутабельные типы
    • Функциональная парадигма оказалась гораздо более строгой и стройной, чем то, что мы сегодня называем ООП.
    • Типы-суммы, они же Discriminated Unions или размеченные объединения.
    • Лаконичность синтаксиса
    • Computation Expressions
    • SRTP (Статически разрешаемые параметры-типы)
    • По умолчанию даже ссылочным типам нельзя присвоить null, и компилятор требует инициализацию при объявлении.
    • Выведение типов или type inference

    С null все понятно, ничто так не засоряет код проекта, как бесконечные проверки возвращаемых значений вроде Task<IEnumerable<Employee>>. Так что сначала давайте обсудим иммутабельность и одновременно лаконичность.


    Допустим, имеем следующий POCO класс:


    public class Employee
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
        public bool HasAccessToSomething { get; set; }
        public bool HasAccessToSomethinElse { get; set; }
    }

    Просто, емко, ничего лишнего. Казалось бы, куда лаконичней?


    Соответствующий код на F# выглядит так:


    type Employee =
    { Id: Guid
      Name: string
      Email: string
      Phone: string
      HasAccessToSomething: bool
      HasAccessToSomethingElse: bool }

    Вот теперь действительно нет ничего лишнего. Полезная информация содержится в ключевом слове декларации типа данных, имени этого типа, именах полей и их типах данных. В примере из C# в каждой строчке есть ненужные public и { get; set; }. Помимо этого, в F# мы получили иммутабельность и защиту от null.


    Ну, положим, иммутабельность мы можем и в C# организовать, а public с автодополнением написать недолго:


    public class Employee
    {
        public Guid Id { get; }
        public string Name { get; }
        public string Email { get; }
        public string Phone { get; }
        public bool HasAccessToSomething { get; }
        public bool HasAccessToSomethinElse { get; }
    
        public Employee(Guid id, string name, string email, string phone, bool hasAccessToSmth, bool hasAccessToSmthElse)
        {
            Id = id;
            Name = name;
            Email = email;
            Phone = phone;
            HasAccessToSomething = hasAccessToSmth;
            HasAccessToSomethinElse = hasAccessToSmthElse;
        }
    }

    Готово! Правда, количество кода увеличилось в 3 раза: все поля мы продублировали дважды.
    Мало того, когда добавится новое поле, мы можем забыть добавить его в параметры конструктора и/или забыть присвоить значение внутри конструктора, и компилятор нам ничего не скажет.


    В F# при добавлении поля вам нужно добавить новое поле. Все.


    Инициализация же выглядит вот так:


    let employee =
        { Id = Guid.NewGuid()
          Name = "Peter"
          Email = "peter@gmail.com"
          Phone = "8(800)555-35-35"
          HasAccessToSomething = true
          HasAccessToSomethinElse = false}

    И если вы забудете одно поле, то код не скомпилируется. Поскольку тип неизменяемый, единственный способ внести изменение — создать новый экземпляр. Но что делать, если мы хотим изменить только одно поле? Все просто:


    let employee2 = { employee with Name = "Valera" }

    Как это сделать в C#? Ну, вы и без меня знаете.


    Добавьте вложенные ссылочные поля, и теперь ваш { get; } ничего не гарантирует — вы можете изменить поля этого поля. Стоит ли упоминать коллекции?


    Но так ли нам нужна эта иммутабельность?


    Я не случайно добавил два булевых поля про доступ куда-то. В реальных проектах за доступ отвечает какой-нибудь сервис, и часто он принимает на вход модель и мутирует ее, проставляя где надо true. И вот я в очередном месте программы получаю такую модель, в которой эти булевы свойства выставлены в false. Что это значит? Юзер не имеет доступ или просто модель не прогнали еще через аксес сервис? А может прогнали, но там забыли проинициализировать какие-то поля? Я не знаю, я должен проверить и прочитать кучу кода.


    Когда же структура неизменяема — я знаю, что там стоят актуальные значения, потому что компилятор обязывает меня полностью инициализировать объект декларации.
    В противном случае при добавлении нового поля я должен:


    • Проверить все места, где этот объект создается — возможно, там тоже нужно заполнить это поле
    • Проверить соответствующие сервисы, мутирующие этот объект
    • Написать/обновить юнит-тесты, затрагивающе это поле
    • Актуализировать маппинги
      Кроме того, можно не боятся, что мой объект мутирует внутри чужого кода или в другом потоке.


      Но в C# настолько трудно добиться настоящей иммутабельности, что писать такой код просто нерентабельно, иммутабельность такой ценой никак не сэкономит время разработки.



    Ну, хватит об иммутабельности. Что еще имеем? В F# мы так же бесплатно получили:


    • Structural Equality
    • Structural Comparison

    Теперь мы можем использовать такие конструкции:


    if employee1 = employee2 then
    //...

    И это действительно будет проверять равенство объектов. Equals который проверяет равенство по ссылке никому даром не нужен, у нас уже есть Object.ReferenceEquals, спасибо.


    Кто-то может сказать, что это никому не нужно, потому что мы не сравниваем объекты в реальных проектах, поэтому Equals & GetHashCode нам нужны так редко, что можно и ручками переопределить. Но я думаю, что причинно-следственная связь тут работает в братную сторону — мы не сравниваем объекты, потому что переопределять руками это все и поддерживать слишком дорого. Но когда это достается бесплатно, применение находится мгновенно: вы можете использовать прямиком ваши модели как ключи в словарях, складывать модели в HashSet<> & SortedSet<>, сравнивать объекты не по айдишнику (хотя эта опция, разумеется, доступна), а просто сравнивать.


    Discriminated Unions


    Думаю, большинство из нас впитали с молоком первого тимлида правило о том, что строить логику на эксепшнах плохо. Например, вместо try { i = Convert.ToInt32("4"); } catch()... правильней использовать int.TryParse.


    Но помимо этого примитивного и до тошноты затертого примера, мы постоянно нарушаем это правило. Юзер ввел невалидные данные? ValidationException. Вышли за границы массива? IndexOutOfRangeException!


    В умных книжках пишут, что исключения нужны для исключительных ситуаций, непредсказуемых, когда что-то пошло совсем не так и нет смысла пытаться продолжать работу. Хороший пример — OutOfMemoryException, StackOverflowException, AccessViolationException и т.д. Но вылезти за границы массива — это непредсказуемо? Серьезно? Индексатор на вход принимает Int32, множество допустимых значений которого составляет 2 в 32 степени. В большинстве случаев мы работаем с массивами, длина которых не превышает 10000. В редких случаях миллион. То есть значений Int32, которые вызовут исключение сильно больше, чем те, которые отработают корректно, то есть при случайно выбранном инте статистически более вероятно попасть в "исключительную" ситуацию!
    То же самое с валидацией — юзер ввел кривые данные. Вот это сюрприз.


    Причина, по которой мы активно злоупотребляем исключениями, проста: нам не хватает мощности системы типов, чтобы адекватно описать сценарий "если все нормально, отдай результат, если нет, верни ошибку". Строгая типизация обязывает нас возвращать один и тот же тип во всех ветках исполнения метода (к счастью), но не хватало еще только в каждый тип добавлять string ErrorMessage & bool IsSuccess. Поэтому в реалиях C# исключения — пожалуй, меньшее из зол в данной ситуации.


    Опять-таки, можно написать класс


    public class Result<TResult, TError>
    {
        public bool IsOk { get; set; }
        public TResult Result { get; set; }
        public TError Error { get; set; }
    }

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


    В F# подобные вещи определяются проще:


    type Result<'TResult, 'TError> =
        | Ok of 'TResult
        | Error of 'TError
    
    type ValidationResult<'TInput> =
        | Valid of 'TInput
        | Invalid of string list
    
    let validateAndExecute input =
        match validate input with // проверяем результат функции валидации
        | Valid input -> Ok (execute input) // если валидно - возвращаем "Ок" с результатом
        | Invalid of messages -> Error messages // если нет, возвращаем ошибку со списком сообщений

    Никаких исключений, все лаконично, и главное, что код самодокументирован. Вам не нужно писать в xml doc, что метод кидает какое-то исключение, вам не нужно судорожно оборачивать вызов чужого метода в try/catch просто на всякий случай. В такой системе типов исключение — действительно непредсказуемая, неправильная ситуация.


    Когда вы кидаете исключения направо и налево, вам нужна нетривиальная обработка ошибок. Вот у вас появляется класс BusinessException или ApiException, теперь вам нужно наплодить исключений, отнаследованных от них, следить, чтобы везде использовались именно они, а если вы что-то перепутаете, то вместо, например, 404 или 403 клиент получит 500. Вас же ждет нудный разбор логов, чтение стек трейсов и так далее.


    F# компилятор кидает ворнинг, если мы в match перебрали не все возможные варианты. Что очень удобно, когда вы добавляете новый кейс в DU. В DU мы определяем воркфлоу, например:


    type UserCreationResult =
        | UserCreated of id:Guid
        | InvalidChars of errorMessage:string
        | AgreeToTermsRequired
        | EmailRequired
        | AlreadyExists

    Тут мы сразу видим все возможные сценарии для данной операции, что гораздо наглядней общего списка исключений. А когда мы добавили новый кейс AgreeToTermsRequired в соответствии с новыми требованиями, компилятор кинул ворнинг там, где мы этот результат обрабатываем.


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


    Индексация же по массиву теперь тоже очень лаконична, никаких if/else и проверки длины:


    let doSmth myArray index =
        match Array.tryItem index myArray with
        | Some elem -> Console.WriteLine(elem)
        | None -> ()

    Здесь используется тип стандартной библиотеки Option:


    type Option<'T> =
        | Some of 'T
        | None

    Каждый раз, когда вы его используете, код сам вам говорит, что отсутствие значения здесь возможно согласно логике, а не из-за ошибки программиста. И компилятор кинет ворнинг, если вы забудете обработать все возможные варианты.


    Строгость парадигмы


    Чистые функции и expression-based дизайн языка дают нам возможность писать очень стабильный код.
    Чистая функция соответствует следующим критериям:


    • Единственный результат ее работы — вычисление значения. Она не изменяет ничего во внешнем мире.
    • Функция всегда возвращает одно и то же значение для одного и того же аргумента.

    Добавьте к этому тотальность (функция может корректно вычислить значение для любого возможного входящего параметра) и вы получите потокобезопасный код, который работает всегда правильно и который легко тестировать.


    Expression-based design говорит нам, что все является выражением, у всего есть результат выполнения. Например:


    let a = if someCondition then 1 else 2

    Компилятор заставит нас учесть все возможные комбинации, мы не можем остановиться просто на if, забыв про else.
    В C# это обычно выглядит так:


    int a = 0;
    if(someCondition)
    {
        a = 1;
    }
    else
    {
        a = 2;
    }

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


    Конечно же, на одних чистых функциях далеко не уедешь — нам нужно I/O, как минимум. Но эти нечистые эффекты можно сильно ограничить до пользовательского ввода и работы с хранилищами данных. Бизнес-логика может быть реализована на чистых функциях, и в этом случае она будет стабильней швейцарских часов.


    Уход от привычного ООП


    Стандартный кейс: у вас есть сервис, который зависит от парочки других сервисов и репозитория. Те сервисы, в свою очередь, могут зависеть от других сервисов и от своих репозиториев. Все это скручивается могучим DI фреймворком в тугую колбасу функционала, отдается веб-апи контроллеру при реквесте.


    Каждая зависимость нашего сервиса, которых в среднем, допустим, от 2 до 5, как и сам наш сервис, обычно имеет 3-5 методов, разумеется, большая часть которых совершенно не нужна в каждом конкретном сценарии. Из всего этого раскидистого дерева методов нам нужно в каждом отдельном сценарии обычно 1-2 метода от каждой (?) зависимости, но мы связываем воедино весь блок функционала и создаем кучу объектов. И моки, конечно же. Куда ж без них — нам нужно же как-то протестировать всю эту красоту. И вот я хочу покрыть тестом метод, но для того, чтобы вызвать этот метод, мне нужен объект этого сервиса. Чтобы его создать, я должен пропихнуть в него моки. Загвоздка в том, чтобы понять, какие именно моки — какие-то в моем методе вообще не вызываются, они мне не нужны. Какие-то вызываются, но только пара методов из них. Поэтому в каждом тесте я делаю нудный сетап этих моков с возвращаемыми значениями и прочей требухой. Потом я хочу протестировать второй сценарий в том же методе. Меня ждет новый сетап. Иной раз в тестах на метод кода больше, чем в самом методе. И да, для каждого метода я должен лезть в его кишки и смотреть, какие же зависимости мне действительно нужны в этот раз.


    Проявляется это не только в тестах: когда я хочу использовать какой-то 1 метод сервиса, я должен удовлетворить все зависимости, чтобы создать сам сервис, даже если в моем методе половина из них не используется. Да, это на себя берет DI фреймворк, но все равно все эти зависимости необходимо зарегистрировать в нем. Нередко это может быть проблемой, например, если часть зависимостей лежит в другой сборке, и теперь нам нужно на нее добавить ссылку. В отдельных случаях это может сильно портить архитектуру, и тогда приходится извращаться с наследованием или выделять общий блок в отдельный сервис, тем самым увеличивая число компонентов в системе. Проблемы, безусловно, решаемые, но неприятные.


    В функциональной парадигме это работает немного по-другому. Самый крутой пацан здесь — чистая функция, а не объект. И преимущественно, как вы уже поняли, тут используют иммутабельные значения, а не мутабельные переменные. Кроме того, функции прекрасно композируются, поэтому, в большинстве случаев, нам не нужны объекты сервисов вообще. Репозиторий достает из базы то, что тебе нужно? Ну так достань и передай в сервис само значение, а не репозиторий!


    Простой сценарий выглядит примерно так:


    let getReport queryData =
        use connection = getConnection()
        queryData
        |> DataRepository.get connection // зависимость от коннекшна мы внедряем в функцию, а не в конструктор
        // и вот нам уже не нужно следить за lifestyle'ом зависимостей в огромном дереве
        |> Report.build

    Для тех, кто не знаком с оператором |> и каррированием, это равносильно следующему коду:


    let gerReport queryData =
        use connection = getConnection()
        Report.build(DataRepository.get connection queryData)

    На C#:


    public ReportModel GetReport(QueryData queryData)
    {
        using(var connection = GetConnection())
        {
            // Report здесь -- статический класс. В него компилируются F# модули
            return Report.Build(DataRepository.Get(connection, queryData));
        }
    }

    А поскольку функции прекрасно композируются, можно написать вообще вот так:


    let getReport qyertData =
        use connection = getConnection()
        queryData
        |> (DataRepository.get connection >> Report.build)

    Заметьте, тестировать Report.build теперь проще некуда. Вам моки не нужны вообще. Более того, есть фреймворк FsCheck, который генерирует сотни входных параметров и запускает с ними ваш метод, и показывает данные, на которых метод сломался. Пользы от таких тестов несравнимо больше, они действительно проверяют на прочность вашу систему, юнит-тесты ее скорее неуверенно щекочут.


    Все, что вам нужно сделать для запуска таких тестов — 1 раз написать генератор для вашего типа. Чем это лучше написания моков? Генератор универсален, он подходит для всех будущих тестов, и вам не нужно знать имплементацию чего бы то ни было, для того, чтобы его написать.


    Кстати, зависимость от сборки с репозиториями или с их интерфейсами теперь не нужна. Все сборки оперируют общими типами и зависят только от нее, а не от друг друга. Если же вдруг вы решите сменить, например, EntityFramework на Dapper, сборку с бизнес логикой это не затронет вообще никак.


    Statically Resolved Type Parameters (SRTP)


    Тут лучше показать, чем рассказать.


    let inline square
         (x: ^a when ^a: (static member (*): ^a -> ^a -> ^a)) = x * x

    Эта функция будет работать для любого типа, у которого определен оператор умножения с соответствующей сигнатурой. Разумеется, это работает и с обычными статическими методами, не только с операторами. И не только со статическими!


    let inline GetBodyAsync x = (^a: (member GetBodyAsync: unit -> ^b) x)
    
    open System.Threading.Tasks
    type A() =
        member this.GetBodyAsync() = Task.FromResult 1
    
    type B() =
        member this.GetBodyAsync() = async { return 2 }
    
    A() |> GetBodyAsync |> fun x -> x.Result // 1
    B() |> GetBodyAsync |> Async.RunSynchronously // 2

    Нам не нужно определять интерфейс, писать обёртки для чужих классов, имплементить интерфейс, единственное условие — чтобы у типа был метод с подходящей сигнатурой! Я не знаю способа сделать так в C#.


    Computation Expressions


    Мы рассматривали пример с типом Result. Допустим, мы хотим выполнить каскад операций, каждая из которых нам возвращает этот самый Result. И если хоть одно звено в этой цепи вернет ошибку, мы хотим прекратить выполнение и вернуть ошибку сразу.
    Вместо того, чтобы писать бесконечные


    let res arg =
        match doJob arg with
        | Error e -> Error e
        | Ok r ->
            match doJob2 r with
            | Error e -> Error e
            | Ok r -> ...

    Мы можем один раз написать


    type ResultBuilder() =
        member __.Bind(x, f) =
            match x with
            | Error e -> Error e
            | Ok x -> f x
        member __.Return x = Ok x
        member __.ReturnFrom x = x
    
    let result = ResultBuilder()

    И использовать это так:


    let res arg =
        result {
            let! r = doJob arg
            let! r2 = doJob2 r
            let! r3 = doJob3 r2
            return r3
        }

    Теперь на каждой строчке с let! в случае Error e мы вернем ошибку. Если же все все будет хорошо, в конце вернем Ok r3.
    И вы можете делать такие штуки для чего угодно, включая даже использование кастомных операций с кастомными названиями. Богатый простор для построения DSL.


    Кстати, есть такая штука и для асинхронного программирования, даже две — task & async. Первый для работы с привычными нам тасками, второй — для работы с Async. Эта штука из F#, от тасок главным образом отличается тем, что у нее cold start, она также имеет интеграцию с Tasks API. Вы можете строить сложные воркфлоу с каскадным и параллельным исполнением, а запускать их лишь когда они готовы. Выглядит это так:


    let myTask =
        task {
            let! result = doSmthAsync() // суть как у await Task
            let! result2 = doSmthElseAsync(result)
            return result2
        }
    
    let myAsync =
        async {
            let! result = doAsync()
            let! result2 = do2Async(result)
            do! do3Async(result2)
            return result2
        }
    
    let result2 = myAsync |> Async.RunSynchronously
    
    let result2Task = myAsync |> Async.StartAsTask
    
    let result2FromTask = myTask |> Async.AwaitTask

    Структура файлов в проекте


    Поскольку рекорды (DTO, модели и тд) объявляются лаконично и не содержат никакой логики, в проекте существенно уменьшается количество файлов. Доменные типы могут быть описаны в 1 файле, типы, специфичные для какого-то узкого блока или слоя могут быть определены в другом файле тоже вместе.


    Кстати, в F# важен порядок строк кода и файлов — по умолчанию в текущей строчке вы можете использовать только то, что уже описали выше. Это by design, и это очень круто, потому что предохраняет вас от циклических зависимостей. Это так же помогает при ревью — порядок файлов в проекте выдает ошибки проектирования: если в самом верху определен высокоуровневый компонент, значит кто-то накосячил с зависимостями. И это видно с первого взгляда, а теперь представьте, сколько времени вам потребуется для того, чтобы в C# при ревью такое обнаружить.


    Для сравнения, вся логика и доменные типы игры Змейка у меня описана в 7 файлах, все кроме одного меньше 130 строк кода.


    Пруф


    Итог


    Получив все эти мощные инструменты и привыкнув к ним, начинаешь решать задачи быстрее и изящней. Большая часть кода, 1 раз написанная и 1 раз протестированная работает всегда. Писать же снова на C# для меня значит отказаться от них и потерять в продуктивности. Я словно возвращаюсь в прошлый век — вот я бегал в удобных кроссовках, а теперь в лаптях. Лучше, чем ничего, но хуже, чем что-то. Да, в него потихоньку добавляют разные фичи — и pattern matching, и рекорды завезут, и даже nullable reference types.
    Но все это, во-первых, сильно позже, чем в F#, во-вторых, беднее. Pattern matching без Discriminated unions & Record destruction — ну, лучше, чем ничего. Nullable reference types — неплохо, но Option лучше.
    Я бы сказал, что главная проблема F# — это то, что тяжело его "продать" сишарпистам.
    Но если вы все же решитесь изучить F# — втянуться будет легко.


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

    Поделиться публикацией
    Комментарии 325
      +11

      Очередной беребезчик, добро пожаловать в функциональный лагерь, у нас есть плюшки.

        +3

        Может кто-то пояснить нубу, как всё вот это ФП применить, ну хотя бы к такой вещи, как построение интерфейса? Вот я понимаю ООП: вот у нас класс кнопки, вот класс окна. Вот у класса окна метод: добавить кнопку, а вот у кнопки метод: нажать. Потому что пример с Report это круто, но тут понятно: имеем поток данных на входе и поток на выходе — посередине функция. Как ФП работает с UI (если работает, конечно)?

          –9
          Никак, потому что ФП не про UI, а про бекенд. Как раз таки UI — это ООП. Если у тебя в руках сова — не надо все превращать в глобус
            +3

            Но на ООП я смогу сделать и отчет, и поток обработать. А на ФП, выходит, не могу? То есть чтобы создать не консольную программку, а что-то посерьезнее, мне нужно два языка?

              +1
              Но на ООП я смогу сделать и отчет, и поток обработать. А на ФП, выходит, не могу? То есть чтобы создать не консольную программку, а что-то посерьезнее, мне нужно два языка?

              А много вы промышленных языков знаете, которые умеют ФП и не умеют ООП или наоборот?

                +1

                И в чём тогда преимущества F#, если со львиной долей задач он ничем не поможет?

                  +1
                  И в чём тогда преимущества F#, если со львиной долей задач он ничем не поможет?

                  В смысле? Я чот не понял наверное.
                  F# как раз поможет со всеми задачами, в которые, например, C# умеет.
                  И многие решает проще.

                    0

                    Выше по ветке написано, что


                    Никак, потому что ФП не про UI, а про бекенд. Как раз таки UI — это ООП. Если у тебя в руках сова — не надо все превращать в глобус

                    Если мне нужен UI не на веб-стеке, будет ли для меня выгода от использования F# выше чем затраты на его обслуживание?

                      +2
                      Во-первых, есть elmish xamarin. Там FP-UI. Я не говорю, что вам нужно его выбрать или что это единственный вариант, но примеры посмотреть уже можно.

                      Во-вторых, независимо от того, веб у вас или десктоп, в приложении есть бизнес-логика. Ее удобней делать на чистых функциях, как я и написал в статье. Потому что это стабильней и легче тестировать. Сам слой презентации вы можете описать на C#, если вам так больше нравится. Они с F# нормально уживаются даже в рамках одного солюшена.
                        0

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

                          +2

                          Сильно зависит от вашего опыта в работе с тем и другим. Я бы вот скорее сказал, что моему опыту это утверждение не соответствует: интероп обычно прозрачен и прост.

                        +1
                        Если мне нужен UI не на веб-стеке, будет ли для меня выгода от использования F# выше чем затраты на его обслуживание?

                        Я UI не занимаюсь вообще, поэтому это не ко мне :) Знаю что его на F# делают. Ссылки в каментах.


                        А если вообще — да, плюсы от использования F# могут перекрыть затраты на поиск разрабов F# / перевоспитание C#-истов. Но не для малого бизнеса имхо.

                          +2
                          Очень жаль. Может быть кто-то другой с опытом в это области всё же подскажет.
                          Про размер команды тоже понятно: с маленькой лучше не рыпаться. Жаль.
                            +1
                            А я вот не согласен с размером команды.
                            На мой взгляд, маленькая команда точно так же выиграет от использования ФП.
                          +4

                          Вы исходите из предпосылки, что F# — это только про ФП. А между тем там есть и ООП-штуки, и некоторые из них очень интересные — object expressions, например, которых временами не хватает в C#.

                      –1
                      Хаскель уже промышленный.

                      Если вы промышленность определяете как готовность тулчейна и наличие библиотек, конечно же. Распространённость — хреновый критерий. Coq наверняка уже давно сливает Rust'у по критерию распространённости, однако же его вполне можно назвать промышленным.
                      +5

                      Я прошу прощения, но F# в силу своих истоков является мультипарадигменным. Т.е вы можете в рамках F# комбинировать традиционные и новые подходы.

                        +2
                        Конечно, на F# можно разрабатывать не только серверную часть, но и UI. Обязательно посмотрите замечательный доклад от Zaid Ajaj про разработку scalable UI на F#+Elmish, очень познавательно: youtu.be/-Oc4xJivY78 Проще говоря, F# может всё, что могут и C#, и JS
                        0

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

                          +5

                          Скорее всего врут)

                        +4

                        Ну возьмите хотябы Реакт в качестве примера с единонаправленным потоком данных — UI — это чистая функция зависящая только он входных данных (в данном случае props, state и context) — результатом этой функции является HTML

                          0

                          Каждый компонент реакта — это класс как бы

                            +4

                            Да это класс (но может быть и чистая функция, с пропсами на входе), но классом он только является чтоб иметь доступ к некоторому внутреннему состоянию и метода лайфсайкла. Тем не менее, результатом является вызов render(), хоть пропсу и стейт не подаются в эту функцию явно.


                            в функциональном программировании вы тоже можете добится внутреннего состояния функции (например с помощью замыканий или монад).

                              +3

                              Не каждый: React поддерживает и функциональные компоненты.

                            +3
                            Почитайте про Elmish. Для примера можно взять этот проект
                              –2

                              Плюс одна специализированная сущность

                              +1
                              Как ФП работает с UI (если работает, конечно)?

                              Да вот сразу пример (с гифками):
                              https://github.com/Zaid-Ajaj/tabula-rasa

                                +4

                                В некоторых фронтенд фреймворках UI реализован в функциональном стиле.


                                Там есть иммутабельное хранилище состояния, из которого путем приложения функций с шаблонами прямо выводится текущее состояние интерфейса. Чтобы поменять интерфейс, опять же, чистые функции меняют состояние (точнее, создают новое).


                                Наличия семантических объектов, к слову, это не отрицает. Просто это не мутабельные объекты, а иммутабельные структуры данных.


                                Пример: связка React-Redux в мире JS.

                                  –2
                                  Пример: связка React-Redux в мире JS.

                                  В React-Redux состояние мутабельное и работа с ним ведется при помощи классического мутабельного сценария. Ничего иммутабельного там нет и не пахло даже.
                                  Устал уже это повторять.

                                    –3
                                    По-видимому, минусующие эксперты по ФП не видят разницу между immutable и readonly. И почему меня это совсем не удивляет?
                                      +1
                                      Устал уже это повторять.

                                      А объясните для тех, чьё знакомство с вёбом закончилось снежинками за курсором мыши в 2002-м, в чём там драма?

                                        +1

                                        Если коротко, есть глобальная переменная state, в которой лежит глобальный стейт приложения, когда ее надо обновить, то делается, условно, state = calculateNewState(state), странные люди по неизвестным мне причинам считают, что у них что-то иммутабельно там, функционально и все такое.

                                          0
                                          Ну разумеется, где-то такая переменная должна лежать! Без нее ничего кроме вычислителя формул не сделать на любом языке программирования. Вопрос только в том, в пользовательском коде она находится или в библиотечном. Если в библиотечном — то этот факт никак не мешает пользовательскому коду быть чистым функциональным.
                                            +3
                                            Вопрос только в том, в пользовательском коде она находится или в библиотечном.

                                            Вопрос в том, какое апи используется для работы с ней. В редаксе используется обычное процедурное апи, никакого функционального кода там просто нет. Пользователь библиотеки берет и совершенно процедурным образом вызывает процедуру setState(state) которая меняет стейт со вполне процедурной семантикой, еще и колбек может вторым аргументом принимать ("после того как поменяли стейт — сделайте еще вон ту штуку"). Функциональщина изо всех щелей прямл.

                                              0
                                              setState используется в React, а не в Redux. В redux используется более функциональный подход (хотя чтобы сделать его совсем чистым, нужны дополнительные телодвижения).

                                              В любом случае, исходно вы говорили про мутабельность — а вот это точно мимо. Что API react, что API redux построены на том, что объект, хранящий текущее состояние, неизменяем.
                                                +1
                                                setState используется в React, а не в Redux.

                                                Да, конечно. Сути не меняет — в Redux у вас будет dispatch(action) — тоже процедура :)


                                                Функциональным подход бы был, если бы dispatch возвращал новый стейт (или новый стор, в котором лежит новый стейт, допустим), и это было бы единственным способом данный новый стейт получить. По факту же диспатч меняет стейт внутри текущего стора.


                                                В любом случае, исходно вы говорили про мутабельность — а вот это точно мимо.

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

                                                  0

                                                  Софистика — это то чем занимаетесь вы, а иммутабельность — это свойство структуры данных, а не всей программы в целом.

                                                    0

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


                                                    Вы, конечно, всегда можете взять мутабельную структуру завернуть ее во враппер и потом менять целиком внутри враппера. Появится ли так иммутабельность? Ну конечно нет, это просто перекладывание из пустого в порожнее.

                                                      0

                                                      Пользовательская структура данных — state — иммутабельна. Что внутри у библиотечной — детали реализации.

                                                        0
                                                        Пользовательская структура данных — state — иммутабельна.

                                                        Не-не-не, слушайте. Пользователь работает не со стейтом, он работает со стором. Он на сторе вызывает getState и dispatch, это и есть апи редакса. Это и есть та самая структура, с которой он взаимодействует.


                                                        Вот если у вас есть, например, строка, вы берете и делаете к ней запрос подстроки str.substring(n, m) (аналог getState) или замену подстроки str.replace(n, s) (аналог dispatch) — иммутабельность результата substring и аргумента stringReplace не имеет никакого отношения к тому, что сама строка, к которой вы эти методы применяете, мутабельна. Аналогичная история с редаксом.


                                                        Именно стор является хранилищем состояния, как объект, предоставляющий апи для запросов и апдейтов. А то, что стор возвращает, когда вы у него требуете текущее состояние — это лишь результат запроса к хранилищу.


                                                        Опять же, если вы из бд будете получать иммутабельную коллекцию строк — можно ли сделать вывод, что ваша бд является иммутабельным хранилищем или что состояние, которое в бд хранится — иммутабельно? Нет! Это мутабельное хранилище с процедурным апи.

                                                          –1
                                                          Вот только строки в рамках общепринятых определений — иммутабельны. Но вы можете продолжать выдумывать свои определения.
                                                            +1
                                                            Вот только строки в рамках общепринятых определений — иммутабельны.

                                                            В рамках каких общепринятых определений? В сишке null-terminated string с каких пор иммутабельными стали?


                                                            Иммутабельным оно будет ровно тогда, когда операции вроде replace будут возвращать вам новую строку, а не модифицировать старую. dispatch же не возвращает новый стор — он модифицирует старый.


                                                            Если бы dispatch возвращал новый стор — redux был бы с иммутабельным апи, все верно. Но это не так.

                                                              0
                                                              Строки в javascript, java и c# в рамках общепринятых определений считаются иммутабельными. Несмотря на то, что чаще всего хранятся (о ужас!) в изменяемых переменных или объектах.
                                                                0
                                                                Строки в javascript, java и c# в рамках общепринятых определений считаются иммутабельными.

                                                                Да ради бога, но, еще раз, они иммутабельны потому, что когда вы выполняете операцию вида replace или похожую, то вам возвращается новая строка. А старая остается как была. Если же replace меняет текущую строку — то строки у вас мутабельные.
                                                                Так вот, dispatch в редаксе меняет текущий стор, не создает нового.

                                                              +1
                                                              Вот только строки в рамках общепринятых определений — иммутабельны. Но вы можете продолжать выдумывать свои определения.

                                                              И что? Иммутабельные строки можно изменять процедурно? Ну вот есть функция
                                                              dispatch(targetString)

                                                              Как функция должна быть написана, чтобы строка поменялась?
                                                                0

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

                                                                  0
                                                                  Точно так же как нельзя процедурно изменить иммутабельное состояние

                                                                  Но в редаксе я через диспатч меняю состояние. Вывод? Оно мутабельно.


                                                                  только заменить его новым.

                                                                  скажите, вы когда в бд запись обновляете — это изменение имеющегося состояния или создание нового? Как отличить два этих варианта по поведению?

                                                                    0
                                                                    Нет, вы через дистатч заменяете состояние новым. Старый объект состояния остается в неизменном виде.
                                                                      0
                                                                      Нет, вы через дистатч заменяете состояние новым. Старый объект состояния остается в неизменном виде.

                                                                      Какой старый объект? С чего вы взяли, что он вообще есть? Редакс вам не дает никаких гарантий по поводу того, как именно хранится состояние. Он дает гарантии лишь по поводу того, что вернет getState. Возможно, ваш объект формируется ровно в тот момент, когда вызван getState, а до этого ег ов принципе не существует. Конечно же он каждый раз будет новый. Точно так же как каждый раз у вас будет новой строкой результат ф-и substring. Но строки при этом как были мутабельные так ими и остаются, если replace работает мутабельно. Важно поведение replace, а не поведение substring, понимаете?


                                                                      Повторяю вопрос про бд — вы когда там данные обновляете, то вы их заменяете на новые или модифицируете существующие?

                                                                        0
                                                                        Но строки при этом как были мутабельные так ими и остаются, если replace работает мутабельно.

                                                                        Я бы в качестве примера привел массивы в JS. Да, у них есть куча иммутабельных методов. slice(), map(), но ведь есть и мутабельные, такие как sort() или splice(). И если мы пользуемся только иммутабельными, то да, мы массивами пользуемся словно иммутабельными. Но как только мы начинаем менять элементы в самом массиве — он перестает считаться иммутабельным, даже если до этого мы 10 раз воспользовались иммутабельными методами.

                                                                        const array = oldArray
                                                                          .map(/* ... */)
                                                                          .filter(/* ... */)
                                                                          .reduce(/* ... */);
                                                                        // Да, пока мы им пользуемся в ФП стиле
                                                                        
                                                                        array[5] = null;
                                                                        // Плевать, как мы раньше пользовались, теперь это обычная процедурщина, пускай и с элементами ФП.


                                                                        Девушке вполне достаточно один раз переспать с мужчиной, чтобы перестать быть девственницой, даже если до 16-ти она ни разу ни с кем не целовалась.
                                                                          0
                                                                          Э-э-э, нет. Редакс именно что дает гарантии что это будет новый объект, а старый останется на месте (при условии соблюдения контракта всеми редьюсерами).
                                                                            –1
                                                                            Э-э-э, нет. Редакс именно что дает гарантии что это будет новый объект, а старый останется на месте (при условии соблюдения контракта всеми редьюсерами).

                                                                            А я что сказал? Редакс вам дает гарантии того, что возвращается из getState, точно так же, как в случае строк, даются гарантии того, что возвращается из substring. Только это все не имеет отношения к иммутабельности. К иммутабельности имеет отношение то, как работает dispatch (aka replace). В случае иммутабельных строк replace возвращает новую строку, оставая неизменной старую, а в случае dispatch — вам возвращается что угодно в зависимости от упоротости миддлеваре и при этом старый стор оказывается измененным (в частности, если на старом сторе вызвать getState, то он вернет не то, что раньше).

                                                                              0
                                                                              Редакс вам дает гарантии того, что возвращается из getState

                                                                              Никаких гарантий Редакс не дает, это ведь процедурный фреймворк. Вот пример. Открываем доку thunk, берем их пример и смотрим, что возвращает getState().

                                                                              function incrementAsync() {
                                                                                return (dispatch, getState) => {
                                                                              	const state1 = getState();
                                                                              	
                                                                                  setTimeout(() => {
                                                                                    const state2 = getState();
                                                                                    dispatch(increment());
                                                                                  }, 1000);
                                                                                };
                                                                              }


                                                                              getState вроде как один, вроде как чистая функция, а возвращает совершенно разные стейты, если они поменялись за эту секунду.
                                                                                0
                                                                                Не вижу каким образом ваш пример демонстрирует отсутствие иммутабельности state1 или state2.
                                                                                  0
                                                                                  Ну, во-первых, state1 и state2 мутабельны по всей глубине в силу того, что JS — не ФП-язык.

                                                                                  Во-вторых, это пример процедурности — грязно мутируется глобальная переменная. И процедура getState() — не чистая функция.

                                                                                  Так зачем этот процедурный фреймворк называть функциональным?
                                                                                    0
                                                                                    Не вижу каким образом ваш пример демонстрирует отсутствие иммутабельности state1 или state2.

                                                                                    При чем тут state1/state2 если мы обсуждаем стор?

                                                                                      0
                                                                                      А зачем вы обсуждаете стор?
                                                                                        0

                                                                                        Потому что стор является хранилищем состояния в редаксе.
                                                                                        Напомню про исходный тезис:


                                                                                        В некоторых фронтенд фреймворках UI реализован в функциональном стиле.
                                                                                        Там (в Redux) есть иммутабельное хранилище состояния, из которого путем приложения функций с шаблонами прямо выводится текущее состояние интерфейса.

                                                                                        Это неверно, ничего иммутабельного в редаксе нет. Ничего функционального нет тоже.

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

                                                                        Стоп. Но я ведь вызываю процедуру dispatch и состояние меняется.

                                                                        Ну вот, допустим, хочу покрыть тестами. Как это выглядит в ФП:

                                                                        const newState = dispatch(data, oldState);
                                                                        // старый стейт остался старым
                                                                        checkIsOld(oldState); 
                                                                        // новый стейт стал новым
                                                                        checkIsNew(newState); 


                                                                        Как это делается в процедурном редаксе:

                                                                        
                                                                        // мне тут даже не надо передавать стейт, это грязная процедура!
                                                                        dispatch(data);
                                                                        // опа, поменялась глобальная переменная!
                                                                        checkIsOld(globalState); 


                                                                        То есть одна из немногих функций редакса, его core, который мы постоянно используем в клиентском коде:
                                                                        1. Зависит не только от аргументов
                                                                        2. Имеет серьезные побочные эффекты
                                                                        3. Является недетерминированно

                                                                        Теперь я открываю википедию, статью «Функциональное программирование» и читаю:

                                                                        понятия «функции» в императивном программировании заключается в том, что императивные функции могут опираться не только на аргументы, но и на состояние внешних по отношению к функции переменных, а также иметь побочные эффекты и менять состояние внешних переменных. Таким образом, в императивном программировании при вызове одной и той же функции с одинаковыми параметрами, но на разных этапах выполнения алгоритма, можно получить разные данные на выходе из-за влияния на функцию состояния переменных. А в функциональном языке при вызове функции с одними и теми же аргументами мы всегда получим одинаковый результат: выходные данные зависят только от входных

                                                                        И, если имеем хоть каплю критического мышления, чтобы снять с ушей маркетологическую лапшу, то понимаем, что Редакс — обычная процедурщина, которая косит под ФП, но противоречит его основным принципам.
                                                                          0

                                                                          Ну, модульные тесты как бы должны тестировать ваш код (редьюсеры), а не библиотечный (стор).


                                                                          Но даже если вы при тестировании редьюсера используете стор — ничего не мешает старый объект сохранить:


                                                                          const oldState = store.getState();
                                                                          dispatch(data);
                                                                          
                                                                          const newState = store.getState();
                                                                          
                                                                          // старый стейт остался старым
                                                                          checkIsOld(oldState); 
                                                                          
                                                                          // новый стейт стал новым
                                                                          checkIsNew(newState);
                                                                            0
                                                                            Но даже если вы при тестировании редьюсера

                                                                            А при чем ту тестирование редьюсера, если речь о тестировании кода, который работает с хранилищем?


                                                                            Но даже если вы при тестировании редьюсера используете стор — ничего не мешает старый объект сохранить:

                                                                            Вы не тот объект сохранили, надо:


                                                                            const oldStore = store;
                                                                            store.dispatch(data);
                                                                            const newStore = store;
                                                                            
                                                                            checkIsOld(oldStore.getState()); 
                                                                            checkIsNew(newStore.getState()); 
                                                                              0

                                                                              А при чем тут тестирование кода, который работает с хранилищем, когда я говорю о работе с состоянием?


                                                                              Вы не тот объект сохранили

                                                                              Нет, это вы не тот объект сохранили. Потому что иммутабельным является состояние, а не стор.

                                                                                0
                                                                                А при чем тут тестирование кода, который работает с хранилищем, когда я говорю о работе с состоянием?

                                                                                Каким состоянием? У вас есть хранилище и в нем хранятся данные. Вы эти данные можете запрашивать через getState(). getState() вам возвращает некоторую структуру. Опустим тот факт, что данная структура мутабельна, и зададимся вопросом — при чем тут вообще ее свойства?
                                                                                Еще раз, вы делаете запрос к бд, вам возвращается иммутабельная коллекция иммутабельных строк. Ваше состояние в бд иммутабельно? Да или нет?

                                                                              0
                                                                              Но даже если вы при тестировании редьюсера используете стор — ничего не мешает старый объект сохранить:

                                                                              Я понимаю как тестировать процедурный код, спасибо.
                                                                                0
                                                                                Та и не в тестах же ж дело. Я просто на примере доказал процедурность редакса.
                                                                                  0
                                                                                  Но обсуждается-то тут не процедурность редакса, а иммутабельность состояния.
                                                                                    0
                                                                                    Тут обсуждается процедурность редакса в первую очередь. И мутабельное изменение глобальной переменной как одно из проявлений этой процедурности
                                                                                      0

                                                                                      Не знаю что обсуждаете вы, но я спорю вот с этим:


                                                                                      В React-Redux состояние мутабельное
                                                                                        0
                                                                                        Ну так состояние мутабельное. Да, объекты в нем принято не мутировать, но само состояние глобально изменяется.
                                                                                          –1
                                                                                          let store = Redux.createStore( ()=> [1,2,3] )
                                                                                          
                                                                                          someWildFunc( store.getState() )
                                                                                          
                                                                                          console.log( store.getState() ) // [3,2,1]
                                                                                          
                                                                                          function someWildFunc(a) { return a.reverse() }

                                                                                          Состояние вы, конечно, можете не менять, но именно редакс вам никаких гарантий не даёт. Более того, он даже не узнает о том, что состояние где-то поменялось, что приведёт, странного вида глюкам, когда состояние в сторе одно, а реактивно зависимая от него вьюшка показывает другое. И сиди, ищи концы, где кто забыл вызвать slice перед reverse.


                                                                                          Иммутабельность — оно про невозможность изменений, а не про только лишь их отсутствие в конкретном месте в конкретное время.

                                                                                            0
                                                                                            Приведенный вами код не является корректным кодом с точки зрения redux.
                                                                                              0
                                                                                              Обоснуйте. Впрочем, вы сейчас подменяете тезис «состояние в редакс иммутабельное» на «состояние полученное из редакс нельзя без глубокого копирования передавать туда, где оно может быть изменено, иначе бо-бо».
                                                                                                –1
                                                                                                «состояние в редакс иммутабельное»

                                                                                                Мне непонятно, зачем вообще обсуждать, мутабелен или нет объект, который возвращается из getState()? Какое это отношение к делу имеет? Допустим, он мутабелен, но при этом getState() бы возвращал глубокую копию внутреннего _state. Что-то вообще бы изменилось, кроме перформанса и принципиальной невозможности сломать этот внутренний стейт, в отличии от имеющейся ситуации?

                                                                                0
                                                                                заменить

                                                                                Заменить где?

                                                                0
                                                                Зашёл оставить это комментарий после прочтения предыдущего вашего.

                                                                Если вы в нёдра рантайма отдаёте функцию State -> State, то всё чисто, ага. Если вы руками делает присваивание, то ничего функционального. Если, более того, в языке захардкожены правила вычисления только для этого присваивания как частный случай, то это совсем ппц.
                                                                  0
                                                                  Если вы в нёдра рантайма отдаёте функцию State -> State, то всё чисто, ага.

                                                                  Ну нет, если functionalSetState(f) = setState(f(getState())) то ничего чисто тут не будет. Вопрос в том, можем ли мы заметить то, что где-то что-то было мутировано (понятное дело в js нету способов это надежно обеспечить, по-этому речь о том что мы это не заметим, если сами себя ограничиваем и используем библиотеку идиоматическим способом).

                                                                    0
                                                                    Так если ваш язык чистый, и вы отдаёте функцию с упомянутой сигнатурой, то заметить, что что-то там мутируется, вы не сможете.

                                                                    Ну как в хаскеле, вы отдаёте набор IO-экшонов, которые абсолютно чистые (потому что это _описание_), а рантайм чё-то там потом мутирует и всё такое.
                                                                      0
                                                                      Так если ваш язык чистый, и вы отдаёте функцию с упомянутой сигнатурой, то заметить, что что-то там мутируется, вы не сможете.

                                                                      В данном случае мы можем, т.к. у нас есть точка входа, которая предоставляет апи (store), у которой мы просим состояние (getState()) и оно разное до и после store.dispatch()


                                                                      Ну как в хаскеле, вы отдаёте набор IO-экшонов, которые абсолютно чистые (потому что это описание), а рантайм чё-то там потом мутирует и всё такое.

                                                                      В хаскеле мы, формально, в ф-ю засовываем World и возвращаем новый World, а монадический интерфейс гарантирует, что старый World потрогать не удастся. По-этому мы и не можем выяснить, изменился старый World или нет. Тут у нас ситуация такая:


                                                                      const oldState = store.getState()
                                                                      store.dispatch(f) //передаем в стор чистую ф-ю
                                                                      const newState = store.getState()
                                                                      // oldState != newState, newState = f(oldState)

                                                                      в принципе такой диспатч ничем не отличается от:


                                                                      const oldState = store._state
                                                                      store._state = f(oldState)
                                                                      const newState = store._state
                                                                      // oldState != newState, newState = f(oldState)

                                                                      с-но диспатч внутри что и делает — применяет f к стейту и заменяет новым старый

                                                                    0
                                                                    Редьюсеры — это функции (State, Action) -> State.
                                                          –3
                                                          Пример: связка React-Redux в мире JS.

                                                          Меня так забавляет, когда люди процедурный фреймворк приводят в пример в качестве функционального.
                                                          +3
                                                          Лично для меня писать в функциональном стиле UI гораздо проще и приятнее чем в ООП стиле.
                                                          В комментариях советуют Elmish, но я бы порекомендовал взглянуть на Elm.
                                                          У него введение лучше. Возможно из него станет понятно как писать UI в функциональном стиле — An Introduction to Elm.
                                                            0
                                                            А как будет выглядеть стандартная реализация иерархии контролов, пусть даже древних финформз.
                                                            Или вы говорите о реализации бизнес логики в функциональном стиле?
                                                            +1

                                                            Посмотрите в сторону функционального реактивного программирования.

                                                              +1
                                                              Хмм, а что там сложного? Я может что ни так понял, но есть библиотека реализующая гуй, у нее есть api. В своем коде дергаем этот апи и все. Если абстрагироваться от F#, то один из самых ярких примеров это ТК, где gui пишется в функциональном стиле легко и просто.
                                                                +1

                                                                Для UI есть Functional Reactive Programming (FRP).
                                                                Вот здесь, например, весь UI и весь WebGL код написан на Haskell (скомпилирован в JS через ghcjs).


                                                                UPD: прошу прощения, не заметил что продублировал ответ других пользователей.

                                                                  +2
                                                                  Когда-то лет 7 назад слушал доклад ребят, которые на каком-то диалекте LISP web- разработкой занимались и были очень довольны. Такчто, думаю, возможно все…
                                                                    0
                                                                    Есть два стула...: Структуры и Функции. Например, структуры: Window, Button, Theme — это просто данные, анемичная модель. Функции(глобальные методы): AddChildToParent(button, window), ApplyThemeTo(button, theme), AdjustLayout(window), Show(window) и т.п.
                                                                      +1
                                                                      Вы рассказываете про оконные бибилиотеки 80-90х. Все преимущества такого подхода сводятся к «это было первое что пришло в голову». В качестве альтернативы можно посмотреть на HTML, просто структура данных.

                                                                      Кстати, описывать событие как метод который зовёт бибилиотека и а прикладной разработчик переопределяет — исключительно плохая идея. Это крайне затрудняет обработку сценариев о которых авторы библиотеки не подумали и отличный способ запутать построение интерфейса с реакцией на события. Например QT видели эту проблему еще в середине 90х, и несмотря на некоторую ангажированность языка сделали более функциональную систему сигналов и слотов. Пользуясь случаем хочу передать горячий привет авторам андроида :)
                                                                        0
                                                                        На самом деле так работают оконные менеджеры десктопных систем и никакое наследование или классы (не говоря уж про макросы и «чорную адовую» кодогенерацию для signal и slot) им не требуется. Внутри очереди сообщений и структуры данных. А вот прикладным программистам приходилось писать операторы switch на н-цать страниц. Классы с наследованием и виртуальными функциями просто облегчили эту задачу.
                                                                      +2

                                                                      Спасибо за еще одну статью о ФП на F#, последнее время они все чаще начали меня радовать(субьективно кажеться, что раньше реже такие материалы писали).


                                                                      Интересно было бы посмотреть на тесты производительности функционального и ооп подхода на .net платформе.

                                                                        +3

                                                                        Буквально сегодня добавил в проект расширение:


                                                                            public static class Extensions
                                                                            {
                                                                                public static T With<T>(this T @object, Action<T> action)
                                                                                {
                                                                                    action?.Invoke(@object);
                                                                                    return @object;
                                                                                }
                                                                            }

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

                                                                          +3
                                                                          Паттерны тут не при чем. Это method chaining

                                                                          А зачем вы делаете вызов через?..
                                                                            0
                                                                            Потому что action может быть null. Тут есть вариант, что я должен выбросить ArgumentNullException, но я так не делаю, потому-что считаю, раз у меня уже есть объект, а вместо метода, который должен его модифицировать передали null, то этот объект модифицировать не нужно. То есть:
                                                                            — Модифицировать как?
                                                                            — Никак
                                                                            Не модифицирую.
                                                                            0
                                                                            Возможно, вам придется по душе вот эта штука:
                                                                            github.com/nlkl/Optional
                                                                            0
                                                                            Автор вот пишет:
                                                                            но не хватало еще только в каждый тип добавлять string ErrorMessage & bool IsSuccess.

                                                                            Тем не менее, это вполне работающая практика. В golang так делают, в некоторых проектах в nodejs тоже принят такой стиль обработки ошибок. И этот подход можно использовать почти везде, даже в языках, где ООП нет вообще. Не самый удобный способ (напоминающий реализацию монады Maybe вручную), но работающий, простой, понятный и приводящий обработку ошибок к единообразному стилю.
                                                                              +6
                                                                              Тем не менее, это вполне работающая практика. В golang так делают, в некоторых проектах в nodejs тоже принят такой стиль обработки ошибок. И этот подход можно использовать почти везде, даже в языках, где ООП нет вообще.

                                                                              В Go не от хорошей жизни так. Как только в языке появляются человеческие типы-суммы об этом ужасе надо забывать. Зачем брать в пример неудачные реализации?

                                                                                –1
                                                                                Где так в golang делают? Либо я чего-то не понял, либо речь о чем-то другом. Возврат нескольких значений в функциях дает почти то самое optional — либо результат, либо ошибка. И не нужные никакие извращения со строчками и булевыми переменными.
                                                                                  0
                                                                                  Где так в golang делают?

                                                                                  Ну вот на Техтрэйне Алексей Акулович из ВК рассказывал, что эту идиому в Go часто используют:

                                                                                    +2
                                                                                    Возврат нескольких значений в функциях дает почти то самое optional — либо результат, либо ошибка.

                                                                                    Только опшонал — это монада. Можно писать в монадическом стиле (как в статье в конце, видимо, но я F# плохо читаю). В golang можно писать в монадическом стиле?

                                                                                    0
                                                                                    1. Реализация в ручную означает еще один способ ошибиться. Во всех программах что мне приходилось писать в них небыло недостатка.
                                                                                    2. «Простой» — нет. Он путает два разных объекта и два пути исполнения. Еще полезное упражнение представить что ваша предметная область аудит логов, в которых есть удачные и неудачные записи и сообщения об ошибках :)
                                                                                    3. «Понятный» — нет. Он даёт возможность создавать объекты с неоднозначным состоянием в которых заполнены и поля данных и поля ошибок. Нужно разрабатывать соглашение что мы с ними делаем, как ищем источник, его надо доносить до всей команды, держать в голове.
                                                                                    4. «Приводящий обработку ошибок к единообразному стилю» — во снах CTO возможно. В коде — это гарантия бардака.
                                                                                    +1

                                                                                    Immutability, Structural Equality, Structural Comparison…
                                                                                    Скоро завезут в C# records (с выходом восьмерки или десятки)
                                                                                    Документация
                                                                                    Обсуждение

                                                                                      +2
                                                                                      Скоро завезут в C# records (с выходом восьмерки)

                                                                                      Бабка на двое сказала. Только пропозал есть с мая. До сих пор в том же состоянии.

                                                                                      +7
                                                                                      Хотелось бы поднять один не технический, но методологический вопрос.
                                                                                      Вот выходит на хабре очередная статья по некоторой технологии, которая в какой-то мере считается элитной в силу своей нераспространённости или сложности.
                                                                                      Приходит некоторое количество новичков, которые вроде как готовы в это ввязаться и задают вопросы о том, чем указанная технология может быть полезна им для решения их повседневных задач. И получают ответы, что технология вообще не для них, не для их задач, а для обучения плаванью сферических слонов в вакууме, притом всё это обильно посыпается минусами спрашивающих (видимо, с мыслью «ну тупыыыыые!»). Какие выводы он сделает? Логичные: да нафиг ему нужна непонятная технология с токсичным сообществом? А ведь он был почти готов присоединиться.
                                                                                      Доколе? Доколе сообщества адептов «элитарных» технологий, к коим функциональщики себя бесспорно относят, будет публикация статей для почёсывания эга и пинание раздумывающих новичков?
                                                                                        +3
                                                                                        У F# на редкость дружелюбное сообщество. В соответствующем Телеграм чате новичкам всегда рады и на вопросы постоянно отвечают. И здесь вам тоже ответили, нужные ссылки привели, уточнить если что всегда можно. По-моему, здесь не в сообществе проблема, а вам очень хочется придраться.
                                                                                          0
                                                                                          Про телеграм чат я знаю и его почитываю, к нему претензий совсем нет. Однако см. выше есть тема про UI. Первый же вопрос — первый же минус (я не про себя)
                                                                                            +6

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


                                                                                            Впрочем, сам вопрос "как применять ФП" ужасно большой, сравним с "как применять ООП" или "как программировать". На него трудно дать ответ в комментарии. Это притягивает тех, кто ответ на собирается давать.

                                                                                              –1

                                                                                              Нейтральные ответы — это конечно хорошо, но кроме нейтральных ответов — человеку прилетел и вполне себе конкретный "минус", который, скажем так,"энтузиазм несколько поубавил". А ну как прийдёт кто и ещё до кучи карму гробанёт? Сидишь и думаешь: нуево нафиг вопросы задавать. А нет вопросов — нет и аплогетов.

                                                                                                +3

                                                                                                Простите, но минусы на Хабре анонимны и поставить его мог кто угодно, даже по ошибке.

                                                                                                  0
                                                                                                  В этом есть и определённая проблема…
                                                                                                  –2
                                                                                                  Второй раз убеждаюсь, что на хабре лучше обходить фанатиков стороной…
                                                                                          +4
                                                                                          В C# это обычно выглядит так:

                                                                                          int a = 0;
                                                                                          if(someCondition)
                                                                                          {
                                                                                              a = 1;
                                                                                          }
                                                                                          else
                                                                                          {
                                                                                              a = 2;
                                                                                          }

                                                                                          Вообще-то в C# есть (1) тернарный оператор, (2) контроль ветвей, не инициализирующих данные. Если в первой строке не делать инициализации, то компилятор код без else не пропустит. Если, конечно, данные эти кому-нибудь нужны.
                                                                                            –2
                                                                                            Вообще-то в C# есть (1) тернарный оператор

                                                                                            Ну такое. Это не компилируется:


                                                                                            var flag = true;
                                                                                            var _ = flag? Console.WriteLine("true") : Console.WriteLine("false");

                                                                                            Такое почему-то тоже


                                                                                            var a = 0;
                                                                                            var b = flag ? { Console.WriteLine("true");  a = 1; }
                                                                                                         : { Console.WriteLine("false"); a = 2; };

                                                                                            Так что тернарный оператор в C# конечно уступает expression-based if из F#

                                                                                              +5
                                                                                              А это тут при чём? О_о
                                                                                              Если сильно хочется, то
                                                                                              Console.WriteLine(flag ? "true" : "false")

                                                                                              Во втором примере вообще желание чего-то странного, я даже толком мысли не понял.
                                                                                                +2
                                                                                                Во втором примере вообще желание чего-то странного, я даже толком мысли не понял.

                                                                                                Это пример того что C# это всё же язык про стейтменты. Нельзя куда угодно засунуть какой хочешь экспрешн.
                                                                                                В if-стейтменте можно делать что хочешь (явные блоки { }), но это не выражение, оно ничего не возвращает (поэтому проверки типов у разных веток if нет и быть не может).


                                                                                                В ?-операторе ничего делать нельзя (явные блоки { } не поставишь), но оно обязано возвращать, а т.к. C# не умеет в unit/Void, то писать в ?-операторе unit/Void операции нельзя.

                                                                                                  –4
                                                                                                  Странно требовать от языка того, чего нет в его спецификации.
                                                                                                    +3

                                                                                                    Все пропозалы — это требования того, чего нет в спецификации. Если этого не требовать, язык перестанет развиваться.


                                                                                                    Все кто пишут пропозалы — странные?

                                                                                                      +3
                                                                                                      А давайте из языка уберём слово return и получим какой-нибудь Rust. А ещё можно вспомнить Pascal с его переменной Result, которой в любой момент можно присвоить значение, и оно будет возвращаемым из функции.
                                                                                                      Любой язык имеет свою семантику и странно требовать забить на всё и подстроить его семантику под хотелки приверженцев других семантических оборотов.
                                                                                                        +2
                                                                                                        Сколь помню, это не классический паскаль, в классическом надо присваивать имени функции.
                                                                                                          +2
                                                                                                          А ведь действительно…
                                                                                                          Давно это было, подзабылось (ну наконец-то).
                                                                                            +2
                                                                                            type Employee =
                                                                                            { Id: Guid
                                                                                              Name: string
                                                                                              Email: string
                                                                                              HasAccessToSomething: bool
                                                                                              HasAccessToSomethingElse: bool }

                                                                                            Вот теперь действительно нет ничего лишнего.

                                                                                            Двоеточия и знак равенства лишние, можно ведь усугубить (скажем, в каком-нибудь F##)
                                                                                            type Employee {
                                                                                              Guid Id
                                                                                              string Name
                                                                                              string Email
                                                                                              bool HasAccessToSomething
                                                                                              bool HasAccessToSomethingElse
                                                                                            }

                                                                                            Вот теперь действительно нет ничего лишнего, но думаю и дальше можно урезать.
                                                                                            Employee
                                                                                              G Id
                                                                                              s Name
                                                                                              s Email
                                                                                              b HasAccessToSomething
                                                                                              b HasAccessToSomethingElse
                                                                                            
                                                                                            

                                                                                            На правах шутки юмора :)
                                                                                              +4

                                                                                              Добавим чутка скобочек и вот он LISP!

                                                                                              –1
                                                                                              Здравствуй Pascal
                                                                                                +1

                                                                                                С сокращениями названий для типов F# может помочь :-)


                                                                                                type s = string
                                                                                                type b = bool
                                                                                                +2
                                                                                                Можно ещё один момент по приведённым примерам пояснить?
                                                                                                Вот есть у нас тип и значение:
                                                                                                type Employee =
                                                                                                { Id: Guid
                                                                                                  Name: string
                                                                                                  Email: string
                                                                                                  HasAccessToSomething: bool
                                                                                                  HasAccessToSomethingElse: bool }
                                                                                                
                                                                                                let employee =
                                                                                                    { Id = Guid.NewGuid()
                                                                                                      Name = "Peter"
                                                                                                      Email = "peter@gmail.com"
                                                                                                      Phone = "8(800)555-35-35"
                                                                                                      HasAccessToSomething = true
                                                                                                      HasAccessToSomethinElse = false}
                                                                                                

                                                                                                В каком месте здесь происходит сопоставление сущности с типом? Только вывод типа компилятором? Тогда почему этот код не может поломаться при неумелом рефакторинге?
                                                                                                  +1

                                                                                                  Здесь компилятор F# идёт вверх по коду и ищет первый подходящий объявленный record (в нём обязаны совпадать все поля). И неявно выводит тип Employee у идентификатора employee.


                                                                                                  Оно может поломаться если между объявлением
                                                                                                  type Employee =
                                                                                                  и созданием
                                                                                                  let employee = ...
                                                                                                  объявить другой рекорд с ровно теми же полями. Тогда компилятор неявно выведет этот самый другой тип у этой же переменной.


                                                                                                  Это конечно поломает код (строгая типизация жеж) и вы сразу заметите ошибку в IDE, так что проблем нет. Если такой шадоуинг типов не ломает билд, то значит предыдщий рекорд ничего полезного не делал :)

                                                                                                    0
                                                                                                    А если будет два типа, отличающиеся на одно поле, или два типа с одинаковым набором полей?
                                                                                                    type Celsius = 
                                                                                                    { Value : float }
                                                                                                    type Farenheit = 
                                                                                                    { Value : float }
                                                                                                    let v = {Value = 36.6 }
                                                                                                    

                                                                                                    Что тут будет?
                                                                                                      +2
                                                                                                      Что тут будет?

                                                                                                      Здесь выведется последний объявленный тип. Неоднозначность можно явно разрешить при необходимости.

                                                                                                      Для конкретно вашего случая (чтобы не путать единицы измерения) очень удобно использовать single case discriminated union:

                                                                                                      type CustomerId = CustomerId of int   // define a union type 
                                                                                                      type OrderId = OrderId of int         // define another union type 
                                                                                                      


                                                                                                      (Пример взяла отсюда)
                                                                                                        +4
                                                                                                        Хм, для единиц измерения есть специальная штука. Упустила слегка.
                                                                                                          +3
                                                                                                          Какая прикольная штука! Вот это точно надо брать на вооружение.
                                                                                                            0

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

                                                                                                              0
                                                                                                              (Минутка оффтопа)
                                                                                                              Интересно, а в математических пакетах типа Матлаба такое есть?
                                                                                                        0

                                                                                                        А зачем так сделали? Чтобы сэкономить на аннотациях типов для подобных топ-левел-байндингов?

                                                                                                      –2

                                                                                                      deleted

                                                                                                        0
                                                                                                        Python начиная с 3.5 позволяет использовать статическую типизацию, хотя конечно питон не хаскел.
                                                                                                          +3
                                                                                                          это не полноценная типизация, это аннотации, которые сейчас никак не используются интерпретатором
                                                                                                            0
                                                                                                            И? Что мешает запускать mypy? Наоборот это лучше чем «полноценная типизация» т.к. есть выбор, каждый решает сам нужна ли для его проекта статическая типизация.
                                                                                                            +2

                                                                                                            А эту статическую типизацию можно верифицировать, собственно, статически, и нет ли тут где-нибудь внезапного решения проблемы останова?

                                                                                                              0
                                                                                                              Ну так она для статической верификации и сделана, просто дополнительными тулзами вроде mypy.
                                                                                                              Про проблему останова пока ничего не могу сказать, не очень понял о чём речь.
                                                                                                            +17
                                                                                                            Хотя я очень люблю фшарп и ценю усилия по его популяризации, в этой статье есть одна серьезная проблема — фшарп преподносится как серебряная пуля, а в сравнении с конкурентами используется грязная риторика, которая напрочь подрывает все доверие к материалу:

                                                                                                            Добавьте вложенные ссылочные поля, и теперь ваш { get; } ничего не гарантирует — вы можете изменить поля этого поля.
                                                                                                            Фшарп тоже не гарантирует иммутабельность вложенных объектов, как и любой другой CLR-совместимый язык:
                                                                                                            type Test = { Values: int[] }
                                                                                                            let x = { Values = [| 1; 2; 3 |] }
                                                                                                            x.Values.[0] <- 5
                                                                                                            

                                                                                                            при случайно выбранном инте статистически более вероятно попасть в «исключительную» ситуацию
                                                                                                            Откуда тут вообще взялся случайно выбранный int? Это наглое жонглирование статистикой в пользу вашего утверждения.
                                                                                                            В C# это обычно выглядит так:
                                                                                                            int a = 0;
                                                                                                            if(someCondition)
                                                                                                            {
                                                                                                                a = 1;
                                                                                                            }
                                                                                                            else
                                                                                                            {
                                                                                                                a = 2;
                                                                                                            }
                                                                                                            
                                                                                                            И явно тип переменной указали, и скобки расставили, и про тернарный оператор условия тактично умолчали. Только обычно такое записывают как var a = someCondition ? 1 : 2.

                                                                                                            Ну и так далее в этом духе. Потенциальных пользователей такие толстые приемы скорее оттолкнут.
                                                                                                              –2

                                                                                                              Видимо, я неточно выразился.
                                                                                                              Во-первых, F# точно не является серебрянной пулей: система типов в хаскеле, например, мощнее, а в C# есть nameof, partial classes, которые делают его более удобным для генерации code behind. Я, например, не знаю как F# работает с WPF — не пробовал.
                                                                                                              Во-вторых, я использовал упрощенные примеры, просто потому что так проще писать статью.
                                                                                                              Да, в F# можно объявить обыкновенный мутабельный класс, и тогда компилятор не защитит это поле в рекорде или в DU. Мой поинт не в том, что F# покроет все магической защитой, а в том, что F# позволяет легко создавать и работать с неизменяемыми структурами, в отличие от C#. Да, массив все еще изменяемый, но из коробки в F# есть неизменяемые List, Set & Map.
                                                                                                              По поводу случайного инта — я, конечно, ни в коем случае не ожидаю, что кто-то будет случайным образом пихать аргументы в индексатор, в конце концов, проверка длины массива — одно из первых правил, которое выучивает юный программист. Я лишь добавил это как демонстрацию того, насколько функция далека от тотальности. Тем не менее, несмотря на то, что мы научнены жизнью и делать так не будем, факт остается фактом: функция написана так, что бОльшая часть диапазона входных параметров вызовет исключение.


                                                                                                              Что касается примера с if/else, я знаю про тернарный оператор, да. И про var, я без всякой задней мысли поставил там int, ошибся, сорян. Опять-таки, пример упрощен, и я там говорю про добавление веток в будущем. Можно сделать вложенные тернарные операторы, но я так делать не люблю из-за плохой читаемости.
                                                                                                              Давайте сделаем пример более боевым:


                                                                                                              let myResult =
                                                                                                                       if condition then
                                                                                                                           let a = myFunc arg1 arg2
                                                                                                                           let b = myFunc2 arg3
                                                                                                                           a + b
                                                                                                                       elif condition2 then
                                                                                                                           myFunc arg4 arg5
                                                                                                                       else
                                                                                                                           myFunc2 arg3         

                                                                                                              При большом желании можно это запихать в тернарные операторы, но обычно так не делают и теряют эту проверку ветвей.

                                                                                                                +2

                                                                                                                В такиз случаях заводится неинициализированная переменная myResult. При чтении из нее компилятор проверит что значение ей было присвоено во всех ветвях исполнения.

                                                                                                                  0

                                                                                                                  Можно еще по идее сделать Id монадку и записывать в аналогичном виде через LINQ, если сахар для where подойдет


                                                                                                                  ЗЫ: хотя не, не получится

                                                                                                                  +1
                                                                                                                  Я, например, не знаю как F# работает с WPF — не пробовал.

                                                                                                                  Работает прекрасно


                                                                                                                  Знакомство с Gjallarhorn.Bindable.WPF (F#) на примере выполнения тестового задания

                                                                                                                    +2
                                                                                                                    Давайте сделаем пример более боевым:

                                                                                                                    let myResult =
                                                                                                                             if condition then
                                                                                                                                 let a = myFunc arg1 arg2
                                                                                                                                 let b = myFunc2 arg3
                                                                                                                                 a + b
                                                                                                                             elif condition2 then
                                                                                                                                 myFunc arg4 arg5
                                                                                                                             else
                                                                                                                                 myFunc2 arg3

                                                                                                                    И он сразу становится кандидатом на рефакторинг даже в F#
                                                                                                                  –1
                                                                                                                  Соответствующий код на F# выглядит так:

                                                                                                                  type Employee =
                                                                                                                  { Id: Guid
                                                                                                                    Name: string
                                                                                                                    Email: string
                                                                                                                    HasAccessToSomething: bool
                                                                                                                    HasAccessToSomethingElse: bool }


                                                                                                                  А если захотите добавить проверку, к примеру что Email не null и не пустая строка?
                                                                                                                    +1

                                                                                                                    Разные способы есть. Если email по бизнес логике обязателен, то, например, можно вернуть ошибку с помощью DU еще до создания этого экземпляра, но мне нравится вот такой подход:


                                                                                                                    module Email =
                                                                                                                        type EmailAddress =
                                                                                                                            private
                                                                                                                            | ValidEmail of string
                                                                                                                            | InvalidEmail of string
                                                                                                                    
                                                                                                                        let ofString = function
                                                                                                                            | "validEmail" -> ValidEmail "validEmail"
                                                                                                                            | invalid -> InvalidEmail invalid 
                                                                                                                    
                                                                                                                        let (|ValidEmail|InvalidEmail|) = function
                                                                                                                            | ValidEmail email -> ValidEmail email
                                                                                                                            | InvalidEmail email -> InvalidEmail email
                                                                                                                    
                                                                                                                    open Email
                                                                                                                    
                                                                                                                    let invalid = Email.ofString "invalid"
                                                                                                                    let valid = Email.ofString "validEmail"
                                                                                                                    
                                                                                                                    match invalid with
                                                                                                                    | InvalidEmail invalid -> printfn "invalid was InvalidEmail %s" invalid
                                                                                                                    | ValidEmail valid -> printfn "invalid was ValidEmail %s" valid
                                                                                                                    
                                                                                                                    match valid with
                                                                                                                    | InvalidEmail invalid -> printfn "valid was InvalidEmail %s" invalid
                                                                                                                    | ValidEmail valid -> printfn "valid was ValidEmail %s" valid

                                                                                                                    Тут мы убрали возможность создавать мыльник напрямую, но оставили возможность матчить валиден ли он или нет.

                                                                                                                      +1
                                                                                                                      Ну вот тут, видимо, проблема и раскрывается. В C# свойства то и нужны для простой возможности добавить проверку/логирование etc., а иначе можно было бы использовать те-же readonly-поля.
                                                                                                                        –2
                                                                                                                        Вы добавляете логирование в свойства ДТО/моделей? А что вы предлагаете делать, когда мыльник пустой пытаются присвоить?
                                                                                                                          +4
                                                                                                                          Главное преимущество и цель системы типов F# в том, чтобы не допустить существование структур, содержащих некорректные значения. Making invalid states unpresentable, как пишет Scott Wlaschin в своей книге «Domain Modelling Made Functional». Вещи, которые в C# мы будем проверять и логировать, в грамотном F#-коде просто не смогут существовать.
                                                                                                                      +2
                                                                                                                      Плюшки и синтаксический сахар это хорошо, но половину из того что вы перечислили в С# уже есть и активно добавляются в последующих версиях языка.

                                                                                                                      По поводу функциональщины, у меня был обратный опыт. Поигравшись со скалой где-от год (написав небольшой проект а не тупо туториалы), я лишь понял насколько я люблю С# и импиративщину. Иммутабельность, чистые функции, отсутствие состояния это все прекрасно, но оно и в С# доступно и обильно используется. Монады это тоже хорошо на первый взгляд, на простых примерах, но стоить копнуть глубже, написать что-нибудь сложнее и все, complexity побеждает здравый смысл. Вообще не покидало ощущение что у меня постоянно связаны руки тогда как импиративщиа и в частности С# дают полную свободу действий.
                                                                                                                        +1

                                                                                                                        Полная свобода действий иллюзорна.


                                                                                                                        Можешь заставить программу делать все что хочешь, но не знаешь что ты он нее хочешь?
                                                                                                                        Потому что если знаешь — декларируй с помощью декларативного программирования. Главный плюс — ты автоматически доказываешь правильность ее работы.


                                                                                                                        Но есть и минус: иногда модель может быть слишком перегруженной для реальной программы. Тогда ты сделал очень много, но это никому не надо.


                                                                                                                        Короче говоря — надо балансировать.


                                                                                                                        Касательно же .net — как по мне, f# более выразителен. А "ООП"из него дергать просто.

                                                                                                                          +1
                                                                                                                          Главный плюс — ты автоматически доказываешь правильность ее работы.

                                                                                                                          Это не в F# и даже не в хаскеле.


                                                                                                                          То есть, да, well-typed programs don't go wrong, но вот делают ли они то, что вы на самом деле ожидаете, зависит, и типизируемость в этих языках доказательством не является.


                                                                                                                          А там, где является, замучаетесь пыль глотать. Жизнь боль, арифметика неполна, тайпчекеры мысли не читают.

                                                                                                                            0
                                                                                                                            Напомню, в комментарии на который вы отвечали речь шла не о ФП или типизации, а о декларативности.
                                                                                                                              0

                                                                                                                              Вы напомнили, но у меня всё равно не щёлкнуло, почему это что-то меняет.


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

                                                                                                                          +1

                                                                                                                          Зачем жить с половиной слабореализованных фич, если можно получить полный набор?
                                                                                                                          Кроме того, про DU в сишарпе вообще пока ничего не слышал. Expression-based он тоже вряд ли станет когда-нибудь, верно? Обе эти фичи сильно способствуют стабильности и самодокументируемости кода.

                                                                                                                          –10
                                                                                                                          Ну хорошо, наглыми передёргиваниями и жонглированием специально подобранных примеров ты убедишь пару безусых студентов попробовать F#. СЕБЕ-то ты зачем врёшь, клоун?
                                                                                                                            +2
                                                                                                                            Как сборщик мусора смотрит на все это? Как вообще с быстродействием у функциональной парадигмы под CLR?
                                                                                                                              0
                                                                                                                              immutable данные всегда приносили огромное кол-во копирования памяти.
                                                                                                                              Для скорости лучше писать просто на Си
                                                                                                                                +2
                                                                                                                                Последнее утверждение в наше время становится всё более спорным…
                                                                                                                                  +3

                                                                                                                                  В большинстве случаев спасает переиспользование объектов, т.к. все композитные структуры включают друг-друга по указателю, а не по значению. Сборщики мусора могут быть реализованы совсем по-другому, когда известно что вся память иммутабельна.
                                                                                                                                  Есть ещё всякие оптимизации типа линейные типов для минимизации расхода памяти (обсуждение для Haskell).

                                                                                                                                  0

                                                                                                                                  Генеративные сборщики мусора с nursing area, помещающейся в кеш и по факту выступающей аналогом стека, очень хорошо на это всё смотрят. Особенно на иммутабельность, ибо невозможно (пользователем, компиляторы умеют делать такие оптимизации, но они-то знают про это) проставить в старые данные ссылки на новые данные, что упрощает алгоритмы.


                                                                                                                                  Олсо, есть всякие оптимизации вроде compact regions (если лень читать, можно просто посмотреть график 7 на с. 9). Правда, не под CLR, но неважно.

                                                                                                                                  –2
                                                                                                                                  Откройте для себя Nemerle и F# уже не будет таким крутым :)
                                                                                                                                    +10

                                                                                                                                    Это очень хороший и интересный язык, но, кажется, он умер? Поддержки .NET Core нет, растительности нет, жизни нет :(

                                                                                                                                      0
                                                                                                                                      Увы, да. К сожалению бэкэнд компилятора сильно привязан к .NET Framework, а переписывать его слишком большой объём работы.
                                                                                                                                      0
                                                                                                                                      Бабуля разве жива?
                                                                                                                                      +3
                                                                                                                                      Powershell — всё сразу нужного типа и коротко :)
                                                                                                                                      $employee = [pscustomobject]@{
                                                                                                                                          Id    = [guid]::NewGuid()
                                                                                                                                          Name  = "Peter"
                                                                                                                                          Email = "peter@gmail.com"
                                                                                                                                          Phone = "8(800)555-35-35"
                                                                                                                                          HasAccessToSomething = $true
                                                                                                                                          HasAccessToSomethinElse = $false
                                                                                                                                      }

                                                                                                                                      > $employee.Name.GetType().ToString()
                                                                                                                                      System.String
                                                                                                                                      > $employee.Id.GetType().ToString()
                                                                                                                                      System.Guid
                                                                                                                                      > $employee.HasAccessToSomething.GetType().ToString()
                                                                                                                                      System.Boolean
                                                                                                                                      
                                                                                                                                        +2
                                                                                                                                        Тулинг, обилие библиотек и размер сообщества я сейчас в расчет не беру


                                                                                                                                        Не берёте потому, что иначе пришлось бы рассказать про печальный для F# расклад с сообществом, тулингом и библиотеками, что свело бы нет восторженные интонации статьи. А именно:

                                                                                                                                        — библиотек F# для типичных задач бизнеса практически нет, а те что есть маргинальные one man поделки, которые стрёмно использовать. Вот такой вот суперский язык F#, на котором почему то ни кто не хочет или не может написать ничего полезного. Но язык суперский. (поэтому юзаются С#-повские библиотеки, сводящие на нет все плюшки, описанные автором. Поскольку ни о каком ФП стиле и DSL в них естественно речи не идёт). Я уже молчу о таких прелестях, как депрекейтнутые версии либ для более старых версий net — у меня ни один проект F# под net 4 не собирается без ручной правки зависимостей

                                                                                                                                        — туллинг так же отвратительный. Все .net-овские либы и фреймворки (asp, wpf, wcf, win, uwp, .NET Native) написаны для C# и VB, F# можно разве что с боку прикрутить. Для сборки проектов и управления зависимостями рекомендованы маргинальные утилиты, которые ни каким нормальным ide естественно не поддерживаются.

                                                                                                                                        Внимание вопрос — а кому в 18-м году нужен язык сам по себе, без тулинга, библиотек и большого дружелюбного сообщества? Очевидно мазохистам и бездельникам, не решающим ни каких практических задач.

                                                                                                                                        Далее, говоря о преимуществах ФП, автор забыл отметить недостатки:
                                                                                                                                        — проблемы эффективности функциональных и неизменяемых структур данных
                                                                                                                                        — неоправданный рост когнитивной нагрузки на чтение и понимание кода.
                                                                                                                                        — функциональный код невозможно нормально отлаживать
                                                                                                                                          0
                                                                                                                                          проблемы эффективности функциональных и неизменяемых структур данных

                                                                                                                                          В самом худшем случае можно функционально и чисто писать в ST.


                                                                                                                                          Или вспомнить про всякие там repa и accelerate.


                                                                                                                                          неоправданный рост когнитивной нагрузки на чтение и понимание кода

                                                                                                                                          Он всё равно меньше нагрузки из-за самой бизнес-логики (как и с правильным ООП).


                                                                                                                                          Тут, впрочем, можно было бы довольно долго обсуждать, насколько легко писать правильный ООП-код или правильный ФП-код (ИМХО второе легче), но то такое.


                                                                                                                                          функциональный код невозможно нормально отлаживать

                                                                                                                                          А в чём у вас проблемы возникают?

                                                                                                                                            –2
                                                                                                                                            А в чём у вас проблемы возникают?

                                                                                                                                            А в том, что программа на F# — это последовательность инструкций, как и программа на С#. В C# любая инструкция доступна из отладчика, на ней можно ставить точку останова и заводить выполнение внутрь её тела. В F# благодаря таким прекрасным вещам как вычислительные выражения, цитирование, каррирование, кастомные операторы и проч. способы написать офигеть какой короткий и декларативный код — отладчик бессилен (на самом деле это не проблема, потому что F# практически ни где в реальной жизни не используется, соответственно и отладчик не нужен). Плюс в C# есть такая полезная опция, как реверсинг на лету и go-to-definition в библиотечный код, который так же можно дебажить. Программист на F# нервно курит в сторонке или рассказывает что дебагер нинужен
                                                                                                                                              +5
                                                                                                                                              отладчик бессилен

                                                                                                                                              Эту чушь от тебя я уже опровергал в какой-то из прошлых статей про F#, где ты рассказывал то же самое.


                                                                                                                                              Даже ссылку оставлю, освежить тебе память.


                                                                                                                                              F# практически ни где в реальной жизни не используется, соответственно и отладчик не нужен

                                                                                                                                              А ещё можно зажмуриться и повторять себе — F# не существует. Поможет.


                                                                                                                                              Плюс в C# есть такая полезная опция, как реверсинг на лету и go-to-definition в библиотечный код, который так же можно дебажить. Программист на F# нервно курит в сторонке или рассказывает что дебагер нинужен

                                                                                                                                              Декомпиляторов из IL в F# нет, это правда. Или я таких не знаю.
                                                                                                                                              Нервно не курю, дебагером пользуюсь.

                                                                                                                                                –1
                                                                                                                                                Ваша истерика и хамоватый переход на «ты» неуместны. Вы бы лучше себя освежили, приняв успокоительное, и прочли написанное выше на свежую голову в уравновешенном состоянии. У меня ни где не сказано, что отладчика в F# нет в принципе или что он физически не может зайти внутрь функции — но пример ваш доказывает лишь это и ни чего более. Спорить с тем, что я не утверждал — глупо.
                                                                                                                                                Речь о том, что промежуточное значение контейнера в цепочках вычислений с оператором |> в отладчике увидеть нельзя, в том числе и вашем примере — значение, которое получает Seq.iter скрыто от всех — и программиста, и отладчика.
                                                                                                                                                Вот ещё пример точки останова, которая никогда не сработает
                                                                                                                                                query{
                                                                                                                                                        for c in ctx.Main.ProductType do 
                                                                                                                                                            if c.GasName = "O₂" || c.GasName = "NO₂"  then
                                                                                                                                                                yield c.ProductTypeName.Value // здесь брекпойнт ставится но не работает
                                                                                                                                                    } |> Seq.iter (printfn "%s")       

                                                                                                                                                А ещё можно зажмуриться и повторять себе — F# не существует. Поможет.

                                                                                                                                                К вашим способам рефлексировать ни каких вопросов нет, жмурьтесь и повторяйте дальше. А мне достаточно сюда заглянуть чтобы сделать вывод что F# ни кому не нужен — из всех языков гитхаба только Elixir, fortran, haxe, racket и logos более мёртвые чем F#. Или коллег поспрашивать как они к F#-у относятся, может быть кто-то пишет на нём что-то полезное (нет, ни кто не пишет)
                                                                                                                                                  0
                                                                                                                                                  А мне достаточно сюда заглянуть чтобы сделать вывод что F# ни кому не нужен
                                                                                                                                                  Вы сильно переоцениваете значимость ваших оправданий :)

                                                                                                                                                  Но раз уж про это зашла речь, вот вам альтернативное объяснение отсутствию F# в хит-парадах — дело в том, что как кода на F#, так и усилий на его поддержку требуется сильно меньше. Отсюда и отсутствие видимой «движухи». Которая, кстати, не обязательно безусловное добро — см. JavaScript :)
                                                                                                                                                    –1
                                                                                                                                                    Вы сильно переоцениваете значимость ваших оправданий

                                                                                                                                                    что вы несёте? вы это написали точно на трезвую голову и в своём уме?

                                                                                                                                                    альтернативное объяснение отсутствию F# в хит-парадах


                                                                                                                                                    С каких это пор (или после какого стакана) очевидные вещи требуют объяснения?
                                                                                                                                            0
                                                                                                                                            Для сборки проектов и управления зависимостями рекомендованы маргинальные утилиты, которые ни каким нормальным ide естественно не поддерживаются.

                                                                                                                                            Вы имеете ввиду FAKE и Paket?

                                                                                                                                              +3

                                                                                                                                              Когда мне говорят, например, что после перехода на F#


                                                                                                                                              • кол-во кода сократилось на 60%
                                                                                                                                              • производительность в таких-то сервисах упала на 4-7%
                                                                                                                                              • кол-во аллокаций выросло на 15%
                                                                                                                                              • время реализации новых фич упало с 1 месяца до 1-2 недель, да плюс кол-во багов уменьшилось


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



                                                                                                                                              Когда я читаю комментарии, подобные вашим, я вижу сплошную субъективщину и эмоции.


                                                                                                                                              • Библиотек у F# нет? Giraffe, Hopac, Rezoom.SQL, FsharpLu.Json Json/Xml/Csv type providers и так далее. С чего это они маргинальные? Если проект поддерживает 1-2 человека, это не значит, что он плох. Может быть, просто язык настолько хорош, что вам не нужна армия мейнтейнеров? А может быть, библиотека не так уж сложна? Или автор дьявольски умен?
                                                                                                                                              • Тулинг отвратительный? Это ложь. Тулинг хуже, чем у C#, но не настолько, чтобы скулить об этом под каждой статьей и уж тем более не настолько, чтобы заниматься промышленной разработкой было нерентабельно.
                                                                                                                                              • Все либы написаны под сишарп? Это ложь, ссылки выше.
                                                                                                                                              • F# в эти либы можно только сбоку воткнуть? С чего бы? F# умеет ООП не хуже C#, да и ООП вообще тут не при чем, используй себе и ОРМ, и автомаппер, и ASP.NET, если не хочешь жираф, суаве или сатурн. Код бизнес-логики это вообще не затрагивает.
                                                                                                                                                Сборка проектов маргинальными утилитами? Это вообще блин о чем? Проекты собираются и классическими средствами ровно так же, как и C#. К слову, на нашем C# проекте используется FAKE.
                                                                                                                                              • Функциональные структуры данных не такие эффективные? Да у нас тут преждевременный оптимизатор в треде. В C# коде чаще всего фигурируют IEnumerable<> & List<T>. Самая частая операция с ними — foreach. Давайте, расскажите мне, как форыч по листу в миллион раз эффективней форыча по связному списку.
                                                                                                                                                Далее, поиск по функциональной мапе медленнее, чем по хеш таблице, да. Только кто заметит эту разницу на словаре, в котором 10-20 элементов?
                                                                                                                                                А вот там, где действительно нужна высокая скорость работы, можно использовать императивные коллекции, никто не мешает это делать. Получается, в F# у вас есть выбор между функциональными и императивными коллекциями, а в C# нет. Ну, если не считать Collections.Immutable, который все ругают.
                                                                                                                                              • Неоправданный рост когнитивной нагрузки при чтении ФП кода? Это чистая субъективщина, не подкрепленная вообще ничем. Когнитивная нагрузка при чтении ваших бездоказательных утверждений для меня выше, чем при чтении качественного ФП кода.
                                                                                                                                              • ФП код невозможно дебажить? Хорошо, что я не знал этого, когда дебажил ФП код. Слава богу, приходится его дебажить гораздо реже, чем ООП, иначе невозможное пришлось бы творить слишком часто.
                                                                                                                                                0
                                                                                                                                                кол-во кода сократилось на 60%
                                                                                                                                                я вижу четкую картину, конкретные цифры и понимаю, чего стоит ожидать от перехода на этот язык. Я вижу объективные утверждения.

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

                                                                                                                                                  +1
                                                                                                                                                  F#, конечно, не хаскель, а C# — не C++, но опыт с сокращением количества кода почти на порядок между хаскелем и плюсами у меня есть.
                                                                                                                                                    –1
                                                                                                                                                    F#, конечно, не хаскель, а C# — не C++, но опыт с сокращением количества кода почти на порядок между хаскелем и плюсами у меня есть.

                                                                                                                                                    Ну такое сравнение это примерно как не миллион, а тысячу, не в лотерею — а в карты, и не выиграл — а проиграл.
                                                                                                                                                    Ну и да, код мерили как? Размер зип-архива?

                                                                                                                                                      +3

                                                                                                                                                      Странно что у вас такие сомнения по этому заявлению.
                                                                                                                                                      Переход на более высокий уровень абстракции убирает часть кода (потому что мы, очевидно, от него абстрагируемся).
                                                                                                                                                      Переход с Assembly на C++
                                                                                                                                                      Переход с C++ на Хаскель


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


                                                                                                                                                      Этот опыт переписывания не такой обширный потому что в последствии понимаешь что проще сразу на F# писать чем потом переписывать.

                                                                                                                                                        –3
                                                                                                                                                        Переход на более высокий уровень абстракции убирает часть кода (потому что мы, очевидно, от него абстрагируемся).

                                                                                                                                                        В случае С# -> F# нет никакого перехода. F# имеет некоторое количество синтаксического сахара, и на это все.


                                                                                                                                                        Конечно, если у вас весь код — это объявление кучи discriminated unions, то тут получится выгадать ~30%, а логика примерно одинаково выглядит в обоих языках, т.к. примитивы одинаковые.

                                                                                                                                                          0
                                                                                                                                                          F# имеет некоторое количество синтаксического сахара, и на это все.

                                                                                                                                                          Так можно про любой язык сказать: Haskell это просто набор синтаксического сахара над Асмом (или Лиспом). Это ж всё Тьюринг полные языки и при должном старании и времени все абстракции одного могут быть выражены в другом.


                                                                                                                                                          Называйте как хотите, код на F# в разы меньше кода на C#. Благодаря ли синтаксическому сахару, ML синтаксису или каким-то абстракциям (экспрешны, алгебраические типы, карирование и всё такое прочее).

                                                                                                                                                            0
                                                                                                                                                            Так можно про любой язык сказать:

                                                                                                                                                            Нельзя.


                                                                                                                                                            Называйте как хотите, код на F# в разы меньше кода на C#.

                                                                                                                                                            Ну не в разы. процентов 10-30%, думаю, можно на практике срезать. Чем больше проект — тем меньше эффект, конечно же.

                                                                                                                                                              +1
                                                                                                                                                              Ну не в разы. процентов 10-30%, думаю, можно на практике срезать. Чем больше проект — тем меньше эффект, конечно же.

                                                                                                                                                              Если просто 1 в 1 переписать на F# (прям в C# стиле, со всеми этими интерфейсами, наследованием и пр) уже будет больше 30%


                                                                                                                                                              Скрины слайдов:
                                                                                                                                                              https://imgur.com/a/Ghxapve


                                                                                                                                                              Где C# перенял из F# (LINQ, async/await, expression-bodied methods/properties etc), уже выглядит более-менее одинаково.


                                                                                                                                                              А если начать использовать F# на полную (да хотя бы DU объявить на 3 строчки вместо портянки из наследуемых классов) там сокращение кода уже на порядок.

                                                                                                                                                                –2
                                                                                                                                                                Если просто 1 в 1 переписать на F# (прям в C# стиле, со всеми этими интерфейсами, наследованием и пр) уже будет больше 30%

                                                                                                                                                                Как показывает практика, обычно в таких сравнениях берется няшный идиоматический код на F# и сравнивается с диким ужасом на C#. А если код на C# переписать из дикого ужаса в адекватном виде — то ВНЕЗАПНО разница падает на порядок.


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

                                                                                                                                                                  0
                                                                                                                                                                  Как показывает практика, обычно в таких сравнениях берется няшный идиоматический код на F# и сравнивается с диким ужасом на C#. А если код на C# переписать из дикого ужаса в адекватном виде — то ВНЕЗАПНО разница падает на порядок.

                                                                                                                                                                  Я ж слайды привел. Что там ужасного в С#? Обычный такой C# справа. И обычный такой F# слева (на самом деле не очень обычный, это просто калька кода из C#). Даже expression-bodied юзаются где можно

                                                                                                                                                                    0
                                                                                                                                                                    Я ж слайды привел.

                                                                                                                                                                    На ваших слайдах 90% "экономии" — это фигурные скобки. То есть вообще не экономия. С-но если даже по строкам считать — там и получается ~30%, но если считать нормально, по зипу, то число резко упадет. Это, еще раз, если таковую экономию в принципе имеет смысл счиать (не имеет, конечно же).

                                                                                                                                                                      0
                                                                                                                                                                      На ваших слайдах 90% "экономии" — это фигурные скобки. То есть вообще не экономия. С-но если даже по строкам считать — там и получается ~30%, но если считать нормально, по зипу, то число резко упадет. Это, еще раз, если таковую экономию в принципе имеет смысл счиать (не имеет, конечно же).

                                                                                                                                                                      Окей, перепишите мне 1 в 1 на С# пожалуйста эти 117 (включая пустые) строчек:
                                                                                                                                                                      https://gist.github.com/Szer/fe0579aa7f5d780c3eecec3000c64447


                                                                                                                                                                      IUserService реализовывать не надо.
                                                                                                                                                                      Естественно с сохранением типизации реквестов/респонсов чтобы я по