У меня есть тип с конструкторами, которые принимают много аргументов. При сопоставлении с образцом конструктора этого типа я хотел бы пересылать аргументы в виде пакета (может быть, кортежа?), без необходимости перепечатывать их все.
Это код, который я хотел бы написать:
type foo_t =
| FromInt of int * int
| FromFloat of float
let sum_pair (a, b) =
a + b
;;
let f foo =
match foo with
| FromInt (a, b) as pair -> sum_pair pair
| FromFloat _ -> 0
;;
Проблема здесь в том, что pair
набирается как foo_t
вместо int * int
. Но sum_pair
следует ожидать пару целых чисел, а не foo_t
.
Как говорили другие, FromInt (a, b)
не кортеж. Внутри это поле с тегом и двумя значениями. Интересно, можно ли FromInt of int * int
создать неупакованный кортеж каким-либо образом, который позволит сделать его совместимым по типу с немаркированными кортежами? Но это, вероятно, не сработает, и вам понадобится Obj.magic, чтобы система типов не вызывала раздражения.
В настоящее время у вас есть два отдельных значения в конструкторе FromInt
. Если вы не можете изменить определение, вам нужно будет самостоятельно обернуть a и b в кортеж перед вызовом sum_pair
:
| FromInt (a, b) -> sum_pair (a, b)
Другой вариант — сохранить кортеж в FromInt
(обратите внимание на круглые скобки):
type foo_t =
| FromInt of (int * int)
| FromFloat of float
При этом вы можете напрямую вызвать sum_pair:
| FromInt pair -> sum_pair pair
Спасибо. В идеале я бы хотел избежать вашего первого предложения, поскольку не хочу все перепечатывать. Второе предложение тоже не работает, поскольку я не могу изменить тип foo_t
.
Я думаю, что здесь существует пара заблуждений относительно того, как работает OCaml.
Самый простой вариант: as
привязывает имя только к объекту, расположенному слева. Скорее, это жадность. Он связывает имя со всем, что находится слева от него.
Например.
# match (1, (2, 3)) with (a, (b, c) as d) -> d;;
- : int * (int * int) = (1, (2, 3))
По сравнению с использованием круглых скобок для устранения неоднозначности и конкретного ограничения того, что as
является обязательным.
# match (1, (2, 3)) with (a, ((b, c) as d)) -> d;;
- : int * int = (2, 3)
Во-вторых, набор аргументов конструктора не является кортежем, если мы явно не сделаем его таковым. Обратите внимание: хотя использование конструктора выглядит как приложение-функция, это не так.
Ваш конструктор FromInt
принимает не один аргумент, который представляет собой кортеж значений, а два аргумента. Тот факт, что это выглядит так, как будто он принимает один аргумент кортежа, является побочным эффектом синтаксиса OCaml.
Как отмечает Догберт, вы можете явно заставить его принимать один кортеж, используя круглые скобки при определении конструктора. На этом этапе конструктор принимает один аргумент, который представляет собой кортеж, содержащий несколько значений.
Спасибо за ваш ответ. К сожалению, я не могу изменить конструктор FromInt
. Я пытаюсь решить повторяющийся шаблон, в котором у меня есть посетитель для типа, который имеет множество конструкторов, и функция, обрабатывающая каждый конструктор. И мне бы хотелось избежать повторения аргументов конструктора функции обработки.
Возможно, для решения этой проблемы существует волшебство ppx, но я ничего не знаю о базовом языке.
Насколько я знаю, это невозможно. В зависимости от того, насколько вы готовы реорганизовать существующий код, одним из способов реализации шаблона посетителя является извлечение подписи каждого посетителя как отдельного типа.
Допустим, все ваши посетители могут возвращать этот общий тип результата:
type res = I of int | F of float
Затем вы определяете каждый возможный тип аргумента посетителя и функцию посетителя, которая использует этот аргумент (на практике вы будете взаимодействовать с посетителями с помощью модуля):
type a = int * int
let visit_a (x, y) =
I (x + y)
type b = float
let visit_b u =
F (u *. 2.)
type c = int * float
let visit_c (x, u) =
I (x * (Int.of_float u))
Каждый возможный набор аргументов функции сгруппирован по типу:
type abc =
| A of a
| B of b
| C of c
Также можно представить вариант D of a
, здесь не обязательно связь один-к-одному.
И вашей диспетчерской функции не нужно деструктурировать каждый тип по отдельности, она знает только, что есть аргументы, которые нужно передать функциям:
let dispatch = function
| A x -> visit_a x
| B x -> visit_b x
| C x -> visit_c x
Например:
# [dispatch (A (5,6));
dispatch (B 1.5);
dispatch (C (3, 9.5))];;
- : res list = [I 11; F 3.; I 27]
Это похоже на то, что говорилось в других ответах, но я думаю, что пример немного более полезен, чтобы показать, как отделить отдельные типы от варианта.
Похоже, там невозможно сделать то, что я прошу (по хорошим или плохим причинам). Должен ли я принять ответ, в котором говорится, что это невозможно?