Этот пример кода:

type recordA = { X: string; }
type recordB = { X: string; }

let modifyX newX record = { record with X = newX }

let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2" 
let modifiedRecordB = {recordB.X = "X"} |> modifyX "X2" 

Результаты в:

  let modifyX newX record = { record with X = newX }
  --------------------------^^^^^^^^^^^^^^^^^^^^^^^^

stdin(4,27): warning FS0667: The field labels and expected type of this record expression or pattern do not uniquely determine a corresponding record type

  let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2" 
  -------------------------------------------^^^^^^^^^^^^

stdin(6,44): error FS0001: Type mismatch. Expecting a
    recordA -> 'a    
but given a
    recordB -> recordB    
The type 'recordA' does not match the type 'recordB'

Я ожидаю, что modifiedRecordA будет эквивалентно {recordA.X = "X2"}, а modifiedRecordB будет эквивалентно {recordB.X = "X2"}, но, похоже, это не работает.

  1. Почему это просто не выводит и не возвращает соответствующий тип записи на основе типа параметра?
  2. Что я могу сделать, чтобы это сработало?
2
Screndib 6 Мар 2015 в 00:02

3 ответа

Лучший ответ

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

type Foo = Foo with
    static member ($) (Foo, record) = fun newX -> { record with recordA.X = newX }
    static member ($) (Foo, record) = fun newX -> { record with recordB.X = newX }

let inline modifyX newX record = (Foo $ record) newX

let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2" 
let modifiedRecordB = {recordB.X = "X"} |> modifyX "X2"

Конструкции, передающие типы, для которых не существует перегрузки, не компилируются.

type recordC = { X: string; }
let modifiedRecordC = {recordC.X = "X"} |> modifyX "X2" 
// Error    No overloads match for method 'op_Dollar' ...
// Possible overload ... The type 'recordC' is not compatible with the type 'recordB'
// Possible overload ... The type 'recordC' is not compatible with the type 'recordA'

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

1
kaefer 6 Мар 2015 в 20:40

Проблема в том, что компилятор определяет тип modifyX на основе использования. Насколько я понимаю, это происходит снизу вверх, поэтому предполагается, что тип будет val modifyX : newX:string -> record:recordB -> recordB. Это, конечно, затем приводит к ошибке типа при попытке использовать это с записью типа recordA. Предупреждение сообщает вам, что, хотя он выбирает тип, существует другой тип с такими же полями, поэтому все, что может сделать компилятор, - это наилучшим образом угадать, какой тип вы имели в виду. Возможно, удастся добиться того, что вы пытаетесь сделать, с помощью встроенных функций, но я не уверен, как это может работать навскидку.

1
N_A 5 Мар 2015 в 21:25

Функция modifyX неверна. Вы не можете использовать термин X в определении и указывать X на разные поля.

Раздел 6.3.6 спецификации F #

Каждая метка поля должна разрешаться в поле Fi в одном типе записи R, все поля которой доступны.

При передаче recordA и recordB в modifyX X не определяется однозначно как поле одного типа.

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

3
Jared Simpson 6 Мар 2015 в 03:31