Записи F #: Опасные, только для ограниченного использования или хорошо используемые функции?

Итак, я получил запись в своем путешествии по F #, и сначала они кажутся довольно опасными. Сначала это казалось умным:

type Card = { Name  : string;
          Phone : string;
          Ok    : bool }

let cardA = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }

Идея, что cardA сопоставляется с Картой. Не говоря уже об упрощенном сопоставлении шаблонов:

let withTrueOk =
  list 
  |> Seq.filter
    (function 
      | { Ok = true} -> true
      | _ -> false
  )

Проблема:

type Card = { Name  : string;
          Phone : string;
          Ok    : bool }

type CardTwo = { Name  : string;
          Phone : string;
          Ok    : bool }

let cardA = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }

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

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

Является ли запись того, что имеет ограниченное использование, или я просто думаю об этом?

Ответ 1

Они не опасны, и они предназначены не только для ограниченного использования.

Я считаю очень редким, что у вас будет два типа с теми же членами. Но если вы столкнулись с такой ситуацией, вы можете определить тип записи, которую хотите использовать:

let cardA = { Card.Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }

Записи очень полезны для создания (в основном) неизменяемых структур данных. И тот факт, что вы можете легко создать копию только с некоторыми измененными полями, тоже замечательный:

let cardB = { cardA with Ok = true }

Ответ 2

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

  • идентичны
  • имеют отношение подмножества/надмножества

Первый случай никогда не должен происходить. Последний может быть разрешен записью B с полем записи A.

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

type Card =
  { Name : string
    Phone: string
    Ok : bool }

type CardTwo =
  { Name : string
    Phone: string
    Age : int }

let card = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
let cardTwo = { Name = "Alf" ; Phone = "(206) 555-0157" ; Age = 21 }

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

let readCard card = 
  match card with
  | { Ok = false } -> () //OK
  | { Age = 21 } -> ()   //ERROR: 'card' already inferred as Card, but pattern implies CardTwo

Кстати, ваш сценарий легко фиксируется аннотацией типа:

let cardA : Card = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }

Ответ 3

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

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

type Card = { Name: string;
          Phone: string;
          Ok: bool }

type CardSmall = { Address: string;
          Ok: bool }

let withTrueOk list =
  list 
  |> Seq.filter (function 
                 | { Card.Ok = true} -> true (* ambiguity could happen here *)
                 | _ -> false)

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