Каковы преимущества встроенной неизменности F# над C#?

  1. Я слышал, что F# имеет встроенную поддержку неизменяемости, но как насчет того, что не может быть реплицировано на C#? Что вы получаете от F# неизменяемых данных, которые вы не получаете от c# неизменяемых данных?

  2. также В F#, нет ли способа создать изменяемые данные? Все ли неизменно?

  3. Если вы используете как C#, так и F# в приложении, можете ли вы изменить неизменяемые данные F# в C#? Или вам просто нужно создать новые типы C#, которые используют неизменяемые данные F# и заменяет ссылки, которые указывают на эти данные?

7 ответов


  1. способ работы F# упрощает работу с неизменяемыми данными, но в этом отношении нет ничего особенного, что не может быть сделано в C#. Однако он не может обеспечить аккуратный синтаксис.

  2. F# поддерживает изменяемые данные с mutable ключевое слово. F# не является чисто функциональным языком. Он поддерживает императивные конструкции, такие как петли, а также функциональные конструкции.

  3. нет. Неизменяемые типы действительно неизменяемы. Система типов .NET общий доступ на обоих языках. Вы также не можете изменить неизменяемые данные с C# (за исключением отражения и хаков памяти, конечно).


Mehrdad уже ответил на все ваши вопросы, но просто Подробнее о #1 - разница заключается в количестве кода, который вам нужно написать. Например, вот определение и пример использования точки для чтения в C#:

class Point
{
    private readonly int x, y;

    public int X { get { return x; } }

    public int Y { get { return y; } }

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

var p = new Point(1, 2);

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

В F# вы просто напишете:

type Point = { x: int; y: int }
let p = { x = 1; y = 2 }

и если вы хотел быть изменяемы:

type Point = { mutable x: int; mutable y: int }
let p = { x = 1; y = 2 }
p.x <- 3
p.y <- 4

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

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

let value1 = 42; // immutable
let mutable value2 = value1; // mutable
value2 <- 13;  

запрет трюков отражения, как только данные создаются в F# it является неизменным независимо от языка, на котором вы его потребляете.


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

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

технически seaking, вы можете написать ту же неизменяемую структуру данных как в C#, так и в F#. Проблема в том, что F# поощряет вас к этому, в то время как запись истинных неизменяемых данных strctures в C# немного болезненна. Это означает, что при написании кода F# вы, скорее всего, напишете неизменяемые структуры (потому что это проще!) и вы будете использовать изменчивость (что также возможно в F#) только тогда, когда вам это действительно нужно (это сильнее!)

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

type MyRect = { X : int; Y : int; Width : int; Height : int }
let r = { X = 0; Y = 0; Width = 200; Height = 100 }

// You'll often need to create clone with one field changed:
let rMoved1 = { X = 123; Y = r.Y; Width = r.Width; Height = r.Height }

// The same thing can be written using 'with' keyword:
let rMoved2 = { r with  X = 123 }

вы также можете написать то же самое на C#, но вам нужно будет объявить неизменяемую структуру данных с помощью метода WithX который возвращает клон с измененным X поле, WithY, WithWidth, etc... Вот неполный пример:

class MyRect {
  readonly int x, y, width, height;

  MyRect WithX(int newX) {
    return new MyRect(newX, y, width, height);
  }
  // etc...
}

это много работы без какой-либо помощи от компилятора.


Я думаю,что вы смотрите на это с слишком подробной точки зрения функции для функции.

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

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

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

редактировать чтобы ответить на комментарий:

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

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

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

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


что вы получаете от F # immutable данные, которые вы не получаете от C# неизменные данные?

F# позволяет легко создавать модифицированные копии неизменяемых данных, это очень утомительно и отнимает много времени от C#. Подумайте о создании записи в F#:

type Person = { FirstName: string; MiddleName: string; LastName: string }
let me = { FirstName = "Robert"; MiddleName = "Frederick"; LastName = "Pickering" }

скажем, я хотел изменить поле фамилии, в F# я могу сделать это, сделав копию, используя ключевое слово "with":

let me' = { me with LastName = "Townson" }

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

другие соответствующие преимущества, который нужно сделать с собраниями. Например, C# поддерживает неизменяемую коллекцию ReadOnlyCollection. Однако каждый раз, когда вы хотите добавить новый элемент в эту коллекцию необходимо сделать копию всей коллекции. F# build in list позволяет просто добавить новый элемент в конец списка, не делая его копию, т. е. новый результирующий список фактически поделится большей частью старого списка. т. е.:

let myList = [3; 2; 1]
let myList' =  4 :: myList

при создании "myList", весь" myList "сохраняется без изменений и" 4 "добавляется к его голове, чтобы создать"myList".


что вы получаете от F# неизменяемых данных, которые вы не получаете от c# неизменяемых данных?

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

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

эти идеи объясняются лучше, чем я когда-либо мог в Эпохальная статья Джона Хьюза Почему Функциональное Программирование Имеет Значение. Джон объясняет, как удаление способности мутировать на самом деле добавляет выразительную силу языку. Читайте газету!