Чому не існує типового класу для функцій?


17

У навчальній проблемі, з якою я заплутався, я зрозумів, що мені потрібен типовий клас для функцій із застосуванням, складанням тощо. Причини ...

  1. Це може бути зручно ставитись до подання функції, як до самої функції, так що застосування функції неявно використовує інтерпретатор, а складання функцій отримує новий опис.

  2. Після того, як у вас буде тип класу для функцій, ви можете отримати похідні класи для спеціальних функцій - у моєму випадку я хочу перевернутих функцій.

Наприклад, функції, які застосовують цілісні зсуви, можуть бути представлені ADT, що містить ціле число. Застосування цих функцій означає лише додавання цілого числа. Склад реалізується шляхом додавання загорнутих цілих чисел. Зворотна функція має ціле число заперечене. Функція ідентичності завершує нуль. Постійну функцію неможливо надати, оскільки для неї немає відповідного представлення.

Звичайно, не потрібно писати речі так, як ніби це значення були справжніми функціями Haskell, але як тільки я виникла ідея, я подумав, що така бібліотека повинна вже існувати і, можливо, навіть використовуючи стандартні написання. Але я не можу знайти такий тип класу в бібліотеці Haskell.

Я знайшов модуль Data.Function , але немає класу типу - лише деякі загальні функції, які також доступні в Prelude.

Отже - чому не існує типового класу для функцій? Це "просто тому, що немає" чи "тому, що це не так корисно, як ви думаєте"? Чи, можливо, є принципова проблема з ідеєю?

Найбільша можлива проблема, про яку я думав до цього часу, полягає в тому, що компілятор, ймовірно, повинен застосувати спеціальне застосування програми для фактичних функцій, щоб уникнути циклічної проблеми - для застосування цієї функції мені потрібно застосувати функцію додатка функції, і для цього мені потрібно викликати функцію додатка функції, і для цього ...

Більше підказок

Приклад коду, щоб показати, на що я прагну ...

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}

--  In my first version, Doable only had the one argument f. This version
--  seemed to be needed to support the UndoableOffset type.
--
--  It seems to work, but it also seems strange. In particular,
--  the composition function - a and b are in the class, but c isn't,
--  yet there's nothing special about c compared with a and b.
class Doable f a b where
  fwdApply :: f a b -> a -> b
  compDoable :: f b c -> f a b -> f a c

--  In the first version, I only needed a constraint for
--  Doable f a b, but either version makes sense.
class (Doable f a b, Doable f b a) => Undoable f a b where
  bwd      :: f a b -> f b a

  bwdApply :: f a b -> b -> a
  bwdApply f b = fwdApply (bwd f) b

--  Original ADT - just making sure I could wrap a pair of functions
--  and there were no really daft mistakes.
data UndoableFn a b = UFN { getFwd :: a -> b, getBwd :: b -> a }

instance Doable UndoableFn a b where
  fwdApply = getFwd
  compDoable f g = UFN ((getFwd f) . (getFwd g)) ((getBwd g) . (getBwd f))

instance Undoable UndoableFn a b where
  bwd f    = UFN (getBwd f) (getFwd f)
  bwdApply = getBwd

--  Making this one work led to all the extensions. This representation
--  can only represent certain functions. I seem to need the typeclass
--  arguments, but also to need to restrict which cases can happen, hence
--  the GADT. A GADT with only one constructor still seems odd. Perhaps
--  surprisingly, this type isn't just a toy (except that the whole thing's
--  a toy really) - it's one real case I need for the exercise. Still a
--  simple special case though.
data UndoableOffset a b where
  UOFF :: Int -> UndoableOffset Int Int

instance Doable UndoableOffset Int Int where
  fwdApply (UOFF x) y = y+x
  compDoable (UOFF x) (UOFF y) = UOFF (x+y)

instance Undoable UndoableOffset Int Int where
  bwdApply (UOFF x) y = y-x
  bwd (UOFF x) = UOFF (-x)

--  Some value-constructing functions
--  (-x) isn't shorthand for subtraction - whoops.
undoableAdd :: Int -> UndoableFn Int Int
undoableAdd x = UFN (+x) (\y -> y-x)

undoableMul :: Int -> UndoableFn Int Int
undoableMul x = UFN (*x) (`div` x)

--  With UndoableFn, it's possible to define an invertible function
--  that isn't invertible - to break the laws. To prevent that, need
--  the UFN constructor to be private (and all public ops to preserve
--  the laws). undoableMul is already not always invertible.
validate :: Undoable f a b => Eq a => f a b -> a -> Bool
validate f x = (bwdApply f (fwdApply f x)) == x

--  Validating a multiply-by-zero invertible function shows the flaw
--  in the validate-function plan. Must try harder.
main = do putStrLn . show $ validate (undoableAdd 3) 5
          putStrLn . show $ validate (undoableMul 3) 5
          --putStrLn . show $ validate (undoableMul 0) 5
          fb1 <- return $ UOFF 5
          fb2 <- return $ UOFF 7
          fb3 <- return $ compDoable fb1 fb2
          putStrLn $ "fwdApply fb1  3 = " ++ (show $ fwdApply fb1  3)
          putStrLn $ "bwdApply fb1  8 = " ++ (show $ bwdApply fb1  8)
          putStrLn $ "fwdApply fb3  2 = " ++ (show $ fwdApply fb3  2)
          putStrLn $ "bwdApply fb3 14 = " ++ (show $ bwdApply fb3 14)

Додаток передбачає певну уніфікацію, де уніфіковані значення не рівні, але пов'язані через ті неперевернуті функції - логіку стилю Prolog, але з a = f(b)обмеженнями, а не a = b. Більшість композицій буде результатом оптимізації структури пошуку об'єднання. Потреба в інверсах повинна бути очевидною.

Якщо жоден елемент в уніфікованому наборі не має точного значення, то певний предмет можна кількісно оцінити лише щодо іншого предмета в цьому уніфікованому наборі. Ось чому я не хочу використовувати "реальні" функції - обчислення цих відносних значень. Я міг би скинути весь аспект функції і просто мати абсолютні та відносні величини - мені, мабуть, потрібні лише числа / вектори і (+)- але мій внутрішній космонавт-астронавт хоче його розваги.

Єдиний спосіб, коли я знову розриваю посилання, - це зворотний трекінг, і все чисто - пошук об'єднання буде зроблено за допомогою клавіш в якості IntMap"покажчиків". У мене є просте з'єднання, яке працює, але оскільки я ще не додав обертові функції, тут немає сенсу перераховувати його.

Причини я не можу використовувати додатки, монаду, стрілку тощо

Основні операції, які мені потрібні для класу абстракції функцій, - це застосування та склад. Це звучить знайомо - наприклад Applicative (<*>), Monad (>>=)і Arrow (>>>)всі функції композиції. Однак типи, які реалізують абстракцію функції в моєму випадку, будуть містити деяку структуру даних, яка представляє функцію, але яка не є (і не може містити) функцію і яка може представляти лише деякий обмежений набір функцій.

Як я вже згадував у поясненні коду, іноді я можу кількісно оцінити лише один елемент відносно іншого, оскільки жоден елемент у "об'єднаному" кластері не має точного значення. Я хочу мати змогу отримати уявлення про цю функцію, яка, як правило, буде складом декількох наданих функцій (підхід до спільного предка в союзі / пошуку дерева) та декількох зворотних функцій (повернення назад до іншої пункт).

Простий випадок - коли вихідні "функції" обмежуються цілочисловим зміщенням "функцій", я хочу, щоб складений результат був "цілочисельним зміщенням" функції "- додати компонентні компенсації. Це велика частина того, чому функція композиції повинна бути в класі, а також у функції застосування.

Це означає , що я не можу забезпечити операції pure, returnабо arrдля моїх типів, тому я не можу використовувати Applicative, Monadабо Arrow.

Це не провал цих типів - це невідповідність абстракцій. Я хочу отримати абстракцію простої чистої функції. Наприклад, немає побічних ефектів, і немає необхідності будувати зручні позначення для послідовності та складання функцій, відмінних від еквівалента стандарту (.), Який стосується всіх функцій.

Я міг би екземпляр Category. Я впевнений, що всі мої функціональні речі зможуть надати ідентичність, хоча мені, мабуть, це і не потрібно. Але оскільки Categoryне підтримує додаток, мені все одно потрібен похідний клас все одно, щоб додати цю операцію.


2
Назвіть мене божевільним, але коли я думаю про типовий клас, як ви описуєте, це для застосування та складання тощо. Я думаю про прикладних функторів, які функції. Можливо, це типовий клас, про який ти думаєш?
Джиммі Хоффа

1
Я не думаю, що Applicativeце цілком правильно - він вимагає, щоб значення були обернені так само, як і функції, тоді як я хочу лише обернути функції, а загорнуті функції справді є функціями, тоді як моїх обернутих функцій зазвичай не буде (у найзагальніший випадок, це AST, що описують функції). Там, де <*>є тип f (a -> b) -> f a -> f b, я хочу, щоб оператор додатків з типом g a b -> a -> bде aі bвказав домен та кодоміну обгорнутої функції, але те, що знаходиться всередині обгортки, не є (обов'язково) реальною функцією. На Стрілках - можливо, я буду дивитись.
Steve314

3
якщо ви хочете зворотне, чи не означає це групу?
jk.

1
@jk. чудовий момент, розуміючи, що є багато чого, що потрібно прочитати про інверсії функцій, що може привести ОП до пошуку того, що він шукає. Ось цікаве читання з цієї теми. Але функція google для haskell, зворотна, дає безліч цікавого вмісту для читання. Можливо, він просто хоче Data.Group
Джиммі Хоффа

2
@ Steve314 Я вважав, що функції зі складом є моноїдною категорією. Вони є моноїдом, якщо домен і кодомен завжди однакові.
Тім Сегейн

Відповіді:


14

Ну, я не знаю жодної ідеї, яка продає себе як такі, що представляють "функцію". Але є кілька, які наближаються

Категорії

Якщо у вас проста концепція функції, що має тотожність та склад, то у вас є категорія.

class Category c where
  id :: c a a
  (.) :: c b c -> c a b -> c a c

Недолік полягає в тому, що ви не можете створити хороший екземпляр категорії з набором об'єктів ( a, bі c). Я думаю, ви можете створити спеціальний клас категорій.

Стрілки

Якщо ваші функції мають поняття продуктів і можуть вводити довільні функції, то стрілки для вас

 class Arrow a where
   arr :: (b -> c) -> a b c
   first :: a b c -> a (b, d) (c, d)
   second :: a b c -> a (d, b) (d, c)

ArrowApply має поняття застосування, яке виглядає важливим для того, що ви хочете.

Додатки

У додатках є ваше поняття програми, я використовував їх у AST для представлення функціональної програми.

class Functor f => Applicative f where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f b -> f c

Є багато інших ідей. Але загальною темою є створення деякої структури даних, що представляє вашу функцію, і ніж передавання її функції інтерпретації.

Це також скільки вільних монадів працює. Я б запропонував тикати на них, якщо ви почуваєтесь сміливими, вони є потужним інструментом для речей, які ви пропонуєте, і по суті дозволяють вам створити структуру даних, використовуючи doнотацію, а потім згорнути її на сторону, що впливає на обчислення з різними функціями . Але вся краса полягає в тому, що ці функції просто функціонують над структурою даних і насправді не знають, як ви все це зробили. Це те, що я запропонував би для вашого прикладу перекладача.


Категорія, здається, не вистачає додатків - ($). Стрілки на перший погляд виглядають як надмірна надмірна кількість, але все ще ArrowApplyзвучить багатообіцяюче - до тих пір, поки мені не потрібно нічого надавати, я не можу. +1 на даний момент, з більшою кількістю перевірок.
Steve314

3
@ Steve314 Категоріям не вистачає додатків, але монадам не вистачає універсального способу їх запуску, це не означає, що вони не корисні
Daniel Gratzer

Існує загальна причина, чому я не можу використовувати Applicativeабо Arrow(або Monad) - я не можу перетворити нормальну функцію (загалом), оскільки значення мого типу представляють функцію, але представлені даними, і не підтримують довільні функції, якщо якщо був спосіб перекладу. Це означає , що я не можу надати pure, arrабо returnдля примірників. BTW - ці класи корисні, але я не можу використовувати їх для цієї мети. Arrowце не "масовий перенакір" - це було помилкове враження від останнього разу, коли я намагався прочитати папір, коли я не був готовий зрозуміти це.
Steve314

@ Steve314 Ідея забезпечити інтерфейс монади для збирання даних - це те, для чого використовуються безкоштовні монади, перевірте їх
Daniel Gratzer

Я переглянув відео з Haskell Exchange 2013 - Андрес Лех, безумовно, це добре пояснює, хоча мені все ж, мабуть, потрібно переглянути його ще раз, пограти з технікою тощо. Хоча я не впевнений, що це потрібно тут. Моєю метою є абстрагування функції за допомогою представлення, яке не є функцією (але яке має функцію інтерпретатора). Мені не потрібна абстракція побічних ефектів, і я не потребую чистого позначення для послідовних операцій. Щойно ця функція абстрагування використовується, програми та композиції будуть виконуватись одночасно всередині алгоритму в іншій бібліотеці.
Steve314

2

Як ви зазначаєте, головна проблема використання тут Applicative полягає в тому, що немає розумного визначення для pure. Значить, Applyбув винайдений. Принаймні, це моє розуміння цього.

На жаль, у мене немає прикладів прикладів таких випадків Applyтакож немає Applicative. Стверджується, що це справедливо IntMap, але я не маю поняття, чому. Так само я не знаю, чи визнає ваш приклад - зміщення цілих чисел - Applyекземпляр.


це читається більше як коментар, див. Як відповісти
gnat

Вибачте. Це більш-менш моя перша відповідь коли-небудь.
користувач185657

Як ви пропонуєте покращити відповідь?
користувач185657

розглянути редагувати ИНГА читачів довідки побачити , як ваші адреси відповіді поставлено питання, «чому не є для функцій класу типів? Є чи це" тільки тому , що це не "або" тому що це не так корисно , як ви думаєте "? Або , може бути , є основна проблема з ідеєю? "
гнат

1
Я сподіваюся, що це краще
користувач185657

1

Крім згаданих Category, Arrowі Applicative:

Я також виявив Data.LambdaConal Elliott:

Деякі класи-функціональні класи, що мають лямбда-побудову

Звичайно, виглядає цікаво, але важко зрозуміти без прикладів ...

Приклади

Приклади можна знайти на вікі-сторінці про відчутні значення (телебачення), які, здається, є однією з речей, що спричинили створення TypeComposeбібліотеки; див. вхідні дані та результати, що оцінюються функціями .

Ідея телевізійної бібліотеки полягає в тому, щоб відображати значення Haskell (включаючи функції) відчутно.

Щоб дотримуватися правила StackOverflow про не публікування оголених lonks, я копіюю кілька бітів нижче, які повинні дати уявлення про ці речі:

Перший приклад гласить:

apples, bananas :: CInput Int
apples  = iTitle "apples"  defaultIn
bananas = iTitle "bananas" defaultIn

shoppingO :: COutput (Int -> Int -> Int)
shoppingO = oTitle "shopping list" $
            oLambda apples (oLambda bananas total)

shopping :: CTV (Int -> Int -> Int)
shopping = tv shoppingO (+)

що дає при запуску як runIO shopping(див. там, щоб отримати більше коментарів, графічних інтерфейсів та інших прикладів):

shopping list: apples: 8
bananas: 5
total: 13

як це вирішує питання? див. Як відповісти
gnat

@gnat Я думав, що визначення Data.Lambdaкласів дають класи для функціональних речей (про що вимагали) ... Я не знав, як ці речі використовувати. Я це трохи вивчив. Напевно, вони не дають абстракції для застосування функції.
imz - Іван Захарящев
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.