Распаковать F # однократный размеченный тип кортежа объединения

Мы можем развернуть тип, например type Address = Address of string, используя функцию развертывания, например

let unwrapAddress (Address a) = a
let addr = Address "sdf"
let str = unwrapAddress addr

поэтому str будет иметь тип string, но если есть тип, подобный этому подходу не будет:

type Composite = Composite of integer:int * someStr:string
let unwrap (Composite c) = c

приведет к ошибке

let unwrap (Composite c) = c;;
------------^^^^^^^^^^^
error FS0019: This constructor is applied to 1 argument(s) but expects 2

Могу ли я как-нибудь развернуть составные типы в простой кортеж?


person Shishkin Pavel    schedule 15.06.2017    source источник
comment
ну это как в личку, так что можно написать: let unwrap (Composite (i, s)) = i, s   -  person FoggyFinder    schedule 15.06.2017
comment
Я исправил сообщение об ошибке в вашем вопросе. Вы, вероятно, получили другой, потому что забыли запустить определение типа в FSI. Но хороший вопрос! Я не знал об этом крайнем случае.   -  person TheQuickBrownFox    schedule 16.06.2017


Ответы (3)


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

let (a,b) = (1,2)

let (x,_) = (4,5)

Еще две интересные вещи, которые стоит попробовать:

let (head::tail) = [1;2;3;4]

FSI отвечает предупреждением FS0025: В этом выражении совпадает неполный шаблон. Например, значение «[]» может указывать на случай, не охватываемый шаблоном (ами).

«Это правда», - рассуждаете вы вслух. «Я должен выразить это как совпадение и включить пустой список как возможность». Лучше превратить такие предупреждения в полностью достоверные ошибки (см .: предупреждать как ошибку, например, --warnaserror +: 25). Не игнорируйте их. Разрешите их по привычке или с помощью метода компилятора. В одном случае нет двусмысленности, так что продолжайте писать код.

Более полезным и интересным является синтаксис соответствия на l.h.s. назначения функции. Это круто. Для содержательных функций вы можете распаковать содержимое внутри, а затем выполнить операцию с внутренними компонентами за один шаг.

let f (Composite(x,y)) = sprintf "Composite(%i,%s)" x y

f (Composite(1,"one"))

> val it : string = "Composite(1,one)"

О вашем коде:

type Address = Address of string //using unwrapping function like

let unwrapAddress (Address a) = a
let addr = Address "sdf"
let str = unwrapAddress addr

type Composite = Composite of integer:int * someStr:string
let unwrap (Composite(c,_)) = c
let cval = Composite(1,"blah")
unwrap cval

Обходной путь:

let xy = Composite(1,"abc") |> function (Composite(x,y))->(x,y)

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

let (|Composite|) = function | Composite(x,y)->(x,y)

let unwrap (Composite(x)) = x

let unwrap2 (Composite(x,y)) = (x,y)

... не строго декомпозиция через единичный случай DU, а декомпозиция через единичный случай Активный шаблон

наконец, вы можете присоединить метод к структуре Composite ...

module Composite = 
  let unwrap = function | Composite(x,y)->(x,y)

Одно из лучших обсуждений использования этой техники - здесь

Также обратите внимание на сигнатуру, которую дает нам развёртка: функция, которая принимает Composite (выделено курсивом) и возвращает int (выделено жирным шрифтом).

Подпись - val unwrap: Composite -> int

person sgtz    schedule 15.06.2017
comment
"These all work for me.". Код на самом деле не компилируется (хотя и с другой ошибкой компиляции, чем та, о которой сообщалось), поэтому на самом деле это не отвечает на вопрос. Есть некоторое поведение сопоставления с образцом, которое я нашел довольно неожиданным, что объясняется в ответе Томаса. - person TheQuickBrownFox; 16.06.2017
comment
@TheQuickBrownFox: Конечно. Я прочитал этот вопрос как разложить в стиле кортежа, отсюда и обзор основ. Возможно, это было первоначальным намерением? В любом случае, ваша редакция вопроса проясняет проблему. Я пропустил это, так как думал, что все будет хорошо. Было бы неплохо привести это в порядок на стороне компилятора. Это уже было отправлено как запрос на github? Хорошо бы это все запечатлеть. - person sgtz; 16.06.2017
comment
@TheQuickBrownFox: в духе исходного вопроса (названного элементами DU) я добавил распознаватель Active Pattern в качестве обходного пути. Спасибо за указание на это. - person sgtz; 16.06.2017

Вы определили тип как размеченное в одном регистре объединение с именованными полями:

type Composite = Composite of integer:int * someStr:string

При таком определении поля случая объединения не являются простым кортежем. Они обрабатываются особым образом, и, например, имена используются как имена свойств в скомпилированном коде. Сопоставление с образцом не превращает элементы в кортеж автоматически, поэтому вам нужно развернуть их отдельно:

let unwrap (Composite(i, s)) = i, s

Однако вы также можете определить одностороннее объединение, где поле является обычным кортежем. (Обратите внимание, что вам нужны круглые скобки вокруг типа кортежа - в противном случае он также будет обрабатываться особым образом, за исключением того, что элементы будут скомпилированы как Item1 и Item2.)

type Composite = Composite of (int * string)

С этим определением ваша функция unwrap будет работать нормально и извлекать значение кортежа:

let unwrap (Composite c) = c

Вы также можете использовать вложенный шаблон для получения числа и строки, как в предыдущем случае:

let unwrap (Composite(i, s)) = i, s

Тот факт, что это ведет себя по-разному в зависимости от того, пишете ли вы A of (T1 * T2) или пишете A of T1 * T2, немного тонок - эти два, вероятно, нужно различать, чтобы компилятор знал, компилировать ли поля как два отдельных поля или как одно поле типа System.Tuple<T1, T2>. Я не могу представить себе другого случая, в котором разница имела бы значение.

person Tomas Petricek    schedule 16.06.2017

В вашем случае вы можете написать:

type Composite = Composite of int * string 

let unwrap (Composite (a, b)) = a, b

что соответствует:

let unwrap x = 
    match x with
    | Composite (a, b) -> a, b

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

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

type Composite = Composite of int * string | JustString of string

let unwrapString (Composite (_, s) | JustString s) = s

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

let f (a, b, c) = ...

или даже более любопытно:

let f () = ...

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

person scrwtp    schedule 16.06.2017