Как передать аргументы конструктора функции в OCaml?

У меня есть тип с конструкторами, которые принимают много аргументов. При сопоставлении с образцом конструктора этого типа я хотел бы пересылать аргументы в виде пакета (может быть, кортежа?), без необходимости перепечатывать их все.

Это код, который я хотел бы написать:

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.

Похоже, там невозможно сделать то, что я прошу (по хорошим или плохим причинам). Должен ли я принять ответ, в котором говорится, что это невозможно?

Dorian 17.07.2024 09:57

Как говорили другие, FromInt (a, b) не кортеж. Внутри это поле с тегом и двумя значениями. Интересно, можно ли FromInt of int * int создать неупакованный кортеж каким-либо образом, который позволит сделать его совместимым по типу с немаркированными кортежами? Но это, вероятно, не сработает, и вам понадобится Obj.magic, чтобы система типов не вызывала раздражения.

Goswin von Brederlow 18.07.2024 16:19
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
58
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

В настоящее время у вас есть два отдельных значения в конструкторе 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.

Dorian 16.07.2024 10:35
Ответ принят как подходящий

Я думаю, что здесь существует пара заблуждений относительно того, как работает 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. Я пытаюсь решить повторяющийся шаблон, в котором у меня есть посетитель для типа, который имеет множество конструкторов, и функция, обрабатывающая каждый конструктор. И мне бы хотелось избежать повторения аргументов конструктора функции обработки.

Dorian 16.07.2024 16:47

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

Chris 16.07.2024 17:43

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

Допустим, все ваши посетители могут возвращать этот общий тип результата:

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]

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

Другие вопросы по теме