Объединить m-арную функцию с n-арной функцией в одну (m+n)-арную функцию, возвращающую пару их результатов

Я понятия не имею, насколько полезным будет это приложение, но мне стало любопытно из-за этого ответа C++ на мой вопрос.

Итак, учитывая, скажем, троичную f и двоичную g, например.

f x y z = x + 10*y + 100*z
g x y = x + 10*y

как я могу получить функцию h, чтобы выполнялось следующее?

h f g 1 2 3 4 5 == (321, 54)

Ясно могу определить

h f g = \x y z v w -> (f x y z, g v w)

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

Для этого конкретного примера (m == 3 && n == 2) pointfree.io показывает, что результат становится нечитаемым:

h = flip . ((flip . ((flip . (((.) . (.) . (,)) .)) .)) .)

но мне все еще любопытно, существует ли что-то еще.

Ради интереса я попытался механически применить комбинаторную логику для вывода формулы, но пока только для конкретного случая m == 3 && n == 2:

b = (.)  -- bluebird (names from "To Mock a Mockingbird")
c = flip -- cardinal (names from "To Mock a Mockingbird")
p = (,)
f x y z = x + 10*y + 100*z
g x y = x + 10*y
{-
p(fxyz)(gvw) = ?xyzvw, with ? in terms of p, f, g and known birds
(p(fxyz))(gvw)
B(B(p(fxyz)))gvw
CBg(B(p(fxyz)))vw
B(CBg)B(p(fxyz))vw
B(B(CBg)B)p(fxyz)vw
B(B(B(CBg)B)p)(fxy)zvw
B(B(B(B(CBg)B)p))(fx)yzvw
B(B(B(B(B(CBg)B)p)))fxyzvw
B(B(B(B(B(CBB)(CB)g)p)))fxyzvw
B(B(B(BB(B(CBB)(CB))gp)))fxyzvw
B(B(BB(BB(B(CBB)(CB))g)p))fxyzvw
B(B(B(BB)(BB(B(CBB)(CB)))gp))fxyzvw
B(BB(B(BB)(BB(B(CBB)(CB)))g)p)fxyzvw
BB(BB(B(BB)(BB(B(CBB)(CB)))g))pfxyzvw
B(BB)(B(BB)(B(BB)(BB(B(CBB)(CB)))))gpfxyzvw
C(C(B(BB)(B(BB)(B(BB)(BB(B(CBB)(CB))))))p)fgxyzvw
-}
h = c (c (b (b b) (b (b b) (b (b b) (b b (b (c b b) (c b)))))) p)
h f g 1 2 3 4 5 == (321, 54) -- True

в Haskell нет m-ичных функций: каждая функция принимает ровно один параметр.

Willem Van Onsem 03.04.2023 22:16

Да, @WillemVanOnsem, но я думаю, ты понимаешь, о чем я.

Enlico 03.04.2023 23:54

основная проблема заключается в том, что, например, в зависимости от экземпляра класса типов что-то может быть функцией или нет. Так что нельзя узнать, например, через синтаксис, сколько там параметров. Тот факт, что функция имеет только один параметр, — это не «деталь реализации», это достаточно фундаментально.

Willem Van Onsem 03.04.2023 23:57

Возьмем, к примеру, printf, которая в каком-то смысле является «вариативной» функцией, потому что через класс типов результатом может быть либо String, IO String, либо функция, которая сопоставляется с каким-то другим элементом: hackage.haskell.org/package/base -4.18.0.0/документы/…

Willem Van Onsem 04.04.2023 00:27

Обычным «категориальным» подходом было бы использование uncurry, чтобы преобразовать «n-арную» функцию в функцию, принимающую большой кортеж, а затем использовать что-то вроде h1 &&& h2 вместе с некоторыми curry (и проекциями для отбрасывания ненужных входных данных). Примерно так можно определить семантику лямбда-исчисления простого типа в декартовой замкнутой категории.

chi 05.04.2023 17:24

@chi, но uncurry не имеет только одного аргумента, так что у меня все еще будет проблема, сколько раз его применять, не так ли?

Enlico 05.04.2023 17:34

@Enlico Да, вам нужно использовать «правильное» карри, которое зависит от арности. Как объясняют приведенные ниже ответы, не может быть действительно независимого от арности решения.

chi 05.04.2023 22:58
Стоит ли изучать 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
7
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как указывает Виллем Ван Онсем в комментариях, проблема с вашим вопросом заключается в том, что технически каждая функция в Haskell принимает ровно один параметр. Я знаю, что вы имеете в виду, и я знаю, что вы хотите сделать, но нет причин, по которым (+) не может быть просто функцией с одним аргументом, которая возвращает функцию, а не функцию с двумя аргументами. В самом деле, если мы определим экземпляр Num для чего-то вроде Int -> Int, то вполне возможно, что (+) является функцией с тремя аргументами!

С другой стороны, только потому, что количество аргументов нельзя вывести, это не означает, что вы обречены. Если вы согласны дать GHC несколько подсказок, вы можете делать все, что хотите, на уровне типа. Рассмотрим следующее:

data Nat = Zero | Succ Nat
type One = Succ Zero
type Two = Succ One
type Three = Succ Two
type Four = Succ Three

class NFun m n f g where
  type family FunT m n f g
  h :: f -> g -> FunT m n f g

instance NFun m n x y => NFun (Succ m) n (a -> x) y where
  type FunT (Succ m) n (a -> x) y = a -> FunT m n x y
  h f g a = h @m @n (f a) g

instance NFun Zero n x y => NFun Zero (Succ n) x (a -> y) where
  type FunT Zero (Succ n) x (a -> y) = a -> FunT Zero n x y
  h f g a = h @Zero @n f (g a)

instance NFun Zero Zero x y where
  type FunT Zero Zero x y = (x,y)
  h = (,)

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

Здесь я определил класс типов NFun, который принимает два числовых типа, указывающих, сколько аргументов будет принимать функция. Первый экземпляр предоставляет аргументы первой функции, а второй экземпляр предоставляет аргументы второй функции. Последний экземпляр объединяет результаты.

Вы можете использовать его следующим образом:

f x y z = x + 10*y + 100*z
g x y = x + 10*y

h @Three @Two f g 1 2 3 4 5 == (321, 54)

Можете ли вы показать, что если мы определим экземпляр Num для чего-то вроде Int -> Int, тогда вполне возможно, что (+) является функцией с 3 аргументами! означает?

Enlico 04.04.2023 08:01

@Enlico: (+) имеет тип Num a => a -> a -> a. Если я каким-то образом определяю instance Num ((->) b b), то есть функция (+) :: (b -> b) -> (b -> b) -> (b -> b) или (+) :: (b -> b) -> (b -> b) -> b -> b, функция, которая таким образом принимает две функции в качестве параметра и возвращает функцию, которая, таким образом, принимает третий параметр.

Willem Van Onsem 04.04.2023 12:58

Для конкретного случая могу определить instance Num (a -> Integer) where (+) = liftA2 (+); fromInteger = const. Тогда 3 + 4 — это функция, которая принимает значение и производит 7. Другими словами, есть случай, когда (+) является функцией трех аргументов.

DDub 05.04.2023 21:39
Ответ принят как подходящий

Если функция не является конкретным типом, арность функции (если не учитывать тот факт, что, строго говоря, каждая функция имеет арность, равную единице), может быть переменной. Действительно, хороший пример — printf::PrintfType r => String -> r.

Это кажется простой функцией. Но есть проблема: r здесь может быть любой экземпляр PrintfType, а у нас в качестве экземпляров:

instance PrintfType a ~ () => PrintfType (IO a) where
    -- ...

instance IsChar c => PrintfType [c] where
    -- ...

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    -- ...

Первые два не так интересны. Последний есть. Действительно, это означает, что он может возвращать другую функцию.

Это означает, что можно использовать printf как:

printf "foo"             :: String
printf "foo %i" 14       :: String
printf "foo %i %i" 14 25 :: String

Они реализовали своего рода вариативную функцию, в которой количество параметров зависит от того, как вы используете printf.

Хотя я не совсем доволен printf, поскольку он не очень «безопасен» по сравнению со многими другими функциями Haskell, он демонстрирует, как можно создавать функции с переменным числом переменных. Например, можно передать слишком много или слишком мало параметров, и %i не гарантируется получение целочисленного или числового значения, поэтому есть лучшие способы форматирования строк.

Но независимо от того, количество параметров не зависит от самой функции, поэтому вы можете вывести это не с точки зрения функции, а с точки зрения того, как она используется. Поэтому, если только типы не являются конкретными в функции, если используются классы типов, уже не так просто определить арность функции и, следовательно, «слить» две функции вместе.

Что вы имеете в виду, говоря, что это не очень «безопасно» по сравнению со многими другими функциями Haskell? Что в этом небезопасного? Если долго отвечать, могу задать еще один специальный вопрос. Если у вас нет ссылки на существующую.

Enlico 20.04.2023 09:12

@Enlico: не сказано, что количество параметров в строке совпадает с количеством параметров или что типы совпадают, например, у printf "foo %s %s bar" "qux" есть дополнительный %s или printf "foo" 42, или вы можете передать строку, отформатированную с помощью %i .

Willem Van Onsem 20.04.2023 09:25

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