Как я уже упоминал в комментарии, чтение https://fsharpforfunandprofit.com/posts/discriminated-unions/ будет очень полезно для вас.Но позвольте мне оказать вам некоторую быструю помощь, чтобы освободить вас и начать решать ваши насущные проблемы.Вы на правильном пути, вы просто немного боретесь с синтаксисом (и приоритет оператора, который является частью синтаксиса).
Сначала загрузите документацию приоритет MSDN оператора в другой вкладке, пока вы читаете остальную часть этого ответа.Вы захотите взглянуть на это позже, но сначала я объясню тонкость того, как F # обрабатывает дискриминированные союзы, которые вы, вероятно, еще не поняли.
Когда вы определяете дискриминированный тип объединения, такой как poly
имя Poly
действует как конструктор для типа.В F # конструкторы являются функциями.Поэтому, когда вы пишете Poly (something)
, синтаксический анализатор F # интерпретирует это как «принять значение (something)
и передать его функции с именем Poly
».Здесь функция Poly
не является той, которую вы должны были явно определить;это было неявно определено как часть вашего определения типа.Чтобы действительно это понять, рассмотрим следующий пример:
type Example =
| Number of int
| Text of string
5 // This has type int
Number 5 // This has type Example
Number // This has type (int -> Example), i.e. a function
"foo" // This has type string
Text "foo" // This has type Example
Text // This has type (string -> Example), i.e. a function
Теперь посмотрите на список приоритетов операторов, который вы загрузили на другой вкладке.Самый низкий приоритет находится вверху таблицы, а самый высокий приоритет внизу;другими словами, чем ниже что-то находится на столе, тем более «плотно» оно связывает.Как видите, приложение функции (f x
, вызов f
с параметром x
) связывает очень плотно, более плотно, чем оператор ::
.Поэтому, когда вы пишете f a::b
, это не читается как f (a::b)
, а скорее как (f a)::b
.Другими словами, f a::b
читается как "Item b
- это список некоторого типа, который мы будем называть T
, а вызов функции f a
создает элемент типа T
, который должен идти передсписок b
".Если вместо этого вы имели в виду «взять список, сформированный путем помещения элемента a
в начало списка b
, а затем вызвать f
с результирующим списком», то для этого нужны скобки: вы должны написать f (a::b)
, чтобы получитьэто значение.
Поэтому, когда вы пишете Poly a::af
, это интерпретируется как (Poly a)::af
, что означает «Вот список. Первый элемент - это Poly a
, что означает, что a
является (float * int) list
. Остальная часть списка будет называться af
".И поскольку значение, которое вы передаете в него, составляет не список, а скорее poly
тип, это несоответствие типов.(Обратите внимание, что элементы типа poly
содержат списки, но сами они не являются списками ).Вам нужно было написать Poly (a::af)
, что означало бы «Вот элемент типа poly
, содержащий список. Этот список должен быть разбит на заголовок a
, а остальные - af
."
Я надеюсь, что это помогло, а не запутало воду дальше.Если вы не поняли ничего из этого, дайте мне знать, и я постараюсь прояснить ситуацию.
PS Еще один момент синтаксиса, который вы, возможно, захотите узнать: F # дает вам много способов сообщить об ошибкеусловие (например, пустой список в этом задании), но ваш профессор попросил вас использовать exception EmptyList
, если дан неверный ввод.Это означает, что он ожидает, что ваш код «сгенерирует» или «вызовет» исключение, когда вы столкнетесь с ошибкой.В C # термин «бросить», но в F # термин «повышать», и синтаксис выглядит следующим образом:
if someErrorCondition then
raise EmptyList
// Or ...
match listThatShouldNotBeEmpty with
| [] -> raise EmptyList
| head::rest -> // Do something with head, etc.
Это должно решить следующий вопрос, который вам нужно задать.: -)
Обновление 2 : Вы отредактировали свой вопрос, чтобы прояснить другую проблему, которая возникает, когда ваша рекурсивная функция сводится к пустому списку в качестве базового варианта - но вашпрофессор попросил вас считать пустой список недействительным.Есть два способа решить это.Сначала я расскажу о более сложном, а затем о более легком.
Более сложный способ решения этой проблемы состоит в том, чтобы иметь две отдельные функции: «внешнюю» и «внутреннюю» для каждой из функций, которые вас попросили определить.В каждом случае «внешний» проверяет, является ли ввод пустым списком, и выдает исключение, если это так.Если входные данные не являются пустым списком, то они передают входные данные «внутренней» функции, которая выполняет рекурсивный алгоритм (и НЕ считает пустой список ошибкой).Таким образом, «внешняя» функция в основном выполняет только проверку ошибок, а «внутренняя» функция выполняет всю работу.Это ОЧЕНЬ распространенный подход в профессиональном программировании, где вся ваша проверка ошибок выполняется на «краях» вашего кода, тогда как «внутренний» код никогда не должен иметь дело с ошибками.Поэтому лучше узнать о нем, но в вашем конкретном случае я думаю, что он сложнее, чем вам нужно.
Более простое решение - переписать ваши функции, чтобы рассматривать список из одного элемента в качестве базового варианта,так что ваши рекурсивные функции никогда не идут до пустого списка.Тогда вы всегда можете считать пустой список ошибкой.Поскольку это домашнее задание, я не приведу вам пример, основанный на вашем реальном коде, а скорее пример, основанный на простом упражнении «возьмите сумму из списка целых чисел», где пустой список будет считаться ошибкой:
let rec sumNonEmptyList (input : int list) : int =
match input with
| [] -> raise EmptyList
| [x] -> x
| x::rest -> x + sumNonEmptyList rest
Синтаксис [x]
в выражении совпадения означает «Это соответствует списку с точно одним элементом в нем и присваивает имя x
значению этого элемента».В вашем случае вы, вероятно, сопоставите значение Poly []
, чтобы вызвать исключение, Poly [a]
в качестве базового варианта и Poly (a::af)
в качестве случая "более чем одного элемента".(Это та самая подсказка, которую, я думаю, я должен дать вам; вы научитесь лучше, если сами поработаете над остальными).