Записи F#: опасно, только для ограниченного использования или хорошо используемая функциональность?
Итак, я записался в моем путешествии F#, и сначала они кажутся довольно опасными. Сначала это казалось умным:
type Card = { Name : string;
Phone : string;
Ok : bool }
let cardA = { Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
Идея в том, что cardA - это шаблон, соответствующий Card. Не говоря уже об упрощенном сопоставлении с образцом здесь:
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# запускает все по порядку.
Теперь это может быть невозможной ситуацией, поскольку никогда не может быть вероятности того, что одна и та же подпись будет принимать два типа, но это возможно.
Является ли запись чем-то, что имеет только ограниченное использование, или я просто слишком обдумывал это?
3 ответа
Они не опасны и предназначены не только для ограниченного использования.
Я думаю, что очень редко у вас будет два типа с одинаковыми членами. Но если вы столкнетесь с такой ситуацией, вы можете определить тип записи, который вы хотите использовать:
let cardA = { Card.Name = "Alf" ; Phone = "(206) 555-0157" ; Ok = false }
Записи очень полезны для создания (в основном) неизменяемых структур данных. И тот факт, что вы можете легко создать копию только с некоторыми измененными полями, тоже великолепен:
let cardB = { cardA with Ok = true }
Я согласен, поля записи как члены включающего модуля / пространства имен поначалу кажутся странными, исходя из более традиционных языков ОО. Но 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 }
Для того, чтобы вы оценили то, что предоставляет 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# запись вообще не ограничена. Он предоставляет множество полезных функций, включая сопоставление с образцом, неизменность по умолчанию, структурное равенство и т. Д.