Вопрос или проблема
У меня есть несколько функций на Haskell с различными типами.
Мне нужно хранить их в некотором неоднородном списке, поэтому я использую что-то вроде Data.Dynamic.
Я хочу брать функции из этого списка и комбинировать их, чтобы получить новую функцию с другим типом.
Например, у меня есть функции addDoubles :: Double -> Double -> Double
и fromInt :: Int -> Double
, тогда я мог бы составить функцию, такую как composedFn = (. fromInt) . addDoubles . fromInt
, и это должно быть сохранено как Dynamic с типом Int -> Int -> Double
.
Я испытываю трудности с тем, чтобы GHC поверил, что все типы правильные.
Любая помощь очень ценится 🙂
Как я уже сказал, я пробовал Data.Dynamic …
{-# LANGUAGE GADTs #-}
import Data.Dynamic
import Data.Data
fromInt :: Int -> Double
fromInt = fromIntegral
addDubs :: Double -> Double -> Double
addDubs = (+)
-- Я хочу хранить множество функций в какой-то коллекции, все с разными типами
myfuncs :: [Dynamic]
myfuncs = [toDyn fromInt, toDyn addDubs]
-- Я хочу иметь возможность комбинировать эти функции, чтобы получить новые типы
partialApplyFns2Fn :: (a -> b) -> (c -> d) -> (d -> b -> e) -> (c -> a -> e)
partialApplyFns2Fn r l f = (. r) . f . l
combineHard :: Dynamic -> Dynamic -> Dynamic -> Maybe Dynamic
combineHard d1 d2 d3 = do
f1 <- fromDynamic d1 :: Maybe (Int -> Double)
f2 <- fromDynamic d2 :: Maybe (Int -> Double)
f3 <- fromDynamic d3 :: Maybe (Double -> Double -> Double)
return $ toDyn ((. f1) . f3 . f2)
Это работает, однако я не хочу жестко задавать эти типы. В другой части кода я буду знать эти типы и также TypeRep комбинированной функции, поэтому было бы идеально сделать что-то вроде этого …
combineDynams :: (TypeRep, Dynamic) -> (TypeRep, Dynamic) -> (TypeRep, Dynamic) -> TypeRep -> Maybe Dynamic
combineDynams (tr1, f1) (tr2, f2) (tr3, f3) targetTypeRep = do
f1' <- fromDynamic f1 :: Maybe tr1
f2' <- fromDynamic f2 :: Maybe tr2
f3' <- fromDynamic f3 :: Maybe tr3
return $ toDyn (partialApplyFns2Fn f1' f2' f3')
но это очевидно не работает.
Я также пробовал GADTs с приведением типов, но с похожими проблемами.
data AnyFunc where
AnyUnary :: (Typeable a, Typeable b) => (a -> b) -> AnyFunc
AnyBinary :: (Typeable c, Typeable d, Typeable e) => (c -> d -> e) -> AnyFunc
-- Функция, которая проверяет совместимость типов во время выполнения
anyFuncMaker :: AnyFunc -> AnyFunc -> AnyFunc -> Maybe AnyFunc
anyFuncMaker (AnyUnary l) (AnyUnary r) (AnyBinary f) =
case cast f of
Just f' -> Just $ AnyBinary (partialApplyFns2Fn l r f')
Nothing -> Nothing -- Несоответствие типов
Ошибка типов для секции GADT:
Переменная типа ‘e0’ неоднозначна
• В выражении: cast f
В выражении:
case cast f of
Just f' -> Just $ AnyBinary (partialApplyFns2Fn l r f')
Nothing -> Nothing
В уравнении для ‘anyFuncMaker’:
anyFuncMaker (AnyUnary l) (AnyUnary r) (AnyBinary f)
= case cast f of
Just f' -> Just $ AnyBinary (partialApplyFns2Fn l r f')
Nothing -> Nothing
Если я включаю AllowAmbigousTypes:
• Не удалось вывести (Typeable e0), возникая из использования ‘cast’
из контекста: (Typeable a, Typeable b)
ограниченного паттерном с конструктором:
AnyUnary :: forall a b.
(Typeable a, Typeable b) =>
(a -> b) -> AnyFunc,
в уравнении для ‘anyFuncMaker’
или из: (Typeable a1, Typeable b1)
ограниченного паттерном с конструктором:
AnyUnary :: forall a b.
(Typeable a, Typeable b) =>
(a -> b) -> AnyFunc,
в уравнении для ‘anyFuncMaker’
Для кода Data.Dynamic
Нет экземпляра для (Typeable tr1)
возникая из использования ‘fromDynamic’
• В операторе ‘do’: f1' <- fromDynamic f1 :: Maybe tr1
В выражении:
do f1' <- fromDynamic f1 :: Maybe tr1
f2' <- fromDynamic f2 :: Maybe tr2
f3' <- fromDynamic f3 :: Maybe tr3
return $ toDyn (partialApplyFns2Fn f1' f2' f3')
В уравнении для ‘combineDynams’:
combineDynams (tr1, f1) (tr2, f2) (tr3, f3) targetTypeRep
= do f1' <- fromDynamic f1 :: Maybe tr1
f2' <- fromDynamic f2 :: Maybe tr2
f3' <- fromDynamic f3 :: Maybe tr3
но я также предполагаю, что :: Maybe tr1
и так далее — полная ерунда!
В случае вашего решения на основе GADT я получаю ошибку:
• Не удалось вывести ‘Typeable e0’, возникая из использования ‘cast’
из контекста: (Typeable a, Typeable b)
Кажется, что cast
требует немного больше руководства для конечного результирующего типа двухаргументной функции. GHC не уверен, что когда мы применяем приведение типов к двухаргументной функции, результирующий тип такой же, как у оригинальной функции.
Нам нужно “связать” тип, чтобы на него ссылаться. Мы можем сделать это с помощью этой неуклюжей вспомогательной функции:
anyFuncMaker' :: AnyFunc -> AnyFunc -> AnyFunc -> Maybe AnyFunc
anyFuncMaker' (AnyUnary l) (AnyUnary r) (AnyBinary f) = AnyBinary <$> annoyingAux l r f
where
annoyingAux
:: forall a b c d d' b' e .
(Typeable a, Typeable b, Typeable c, Typeable d, Typeable d', Typeable b', Typeable e)
=> (a -> b) -> (c -> d) -> (d' -> b' -> e) -> Maybe (c -> a -> e)
annoyingAux l' r' g =
case cast g of
Just g' -> Just $ (partialApplyFns2Fn l' r' g')
Nothing -> Nothing
anyFuncMaker' _ _ _ = Nothing
Возможно, есть более простой способ определить вспомогательную функцию, но я не уверен. Вместо вспомогательной функции мы можем упростить определение, используя абстракции типов в паттернах (GHC 9.2 и выше):
anyFuncMaker'' :: AnyFunc -> AnyFunc -> AnyFunc -> Maybe AnyFunc
anyFuncMaker'' (AnyUnary @_ @b l) (AnyUnary @_ @d r) (AnyBinary @_ @_ @e f) =
case cast f of
Just (f' :: d -> b -> e) -> Just $ AnyBinary (partialApplyFns2Fn l r f')
Nothing -> Nothing
anyFuncMaker'' _ _ _ = Nothing
Ответ или решение
Вопрос, который вы подняли, касается динамической обработки функций с использованием модуля Data.Dynamic
в Haskell, а также обработки типов с помощью GADTs. Давайте разберем, как можно решить вашу задачу, чтобы сделать её более обобщенной без жесткой привязки к типам.
Шаг 1: Использование Data.Dynamic
Вы уже создали базовую структуру с использованием Data.Dynamic
, где можно хранить функции. Также у вас есть основная функция partialApplyFns2Fn
, которая комбинирует функции, но проблема в том, что необходимо динамически обрабатывать типы. Ваше решение с combineDynams
не работает из-за неопределенности типов в контексте Maybe tr1
.
Прежде чем идти дальше, давайте рассмотрим, как можно использовать динамическое программирование функции с помощью GADTs.
Шаг 2: Правильная работа с GADTs
Ваше определение GADT для функций (AnyFunc) хорошо, но вы столкнулись с проблемами привязки типов. Чтобы помочь GHC понять правильные типы, вам нужно явным образом указать их в момент использования cast
. Вот как можно изменить функцию anyFuncMaker
, чтобы сделать ее более гибкой и избежать ошибок привязки типов:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeOperators #-}
import Data.Dynamic
import Data.Typeable
data AnyFunc where
AnyUnary :: (Typeable a, Typeable b) => (a -> b) -> AnyFunc
AnyBinary :: (Typeable c, Typeable d, Typeable e) => (c -> d -> e) -> AnyFunc
partialApplyFns2Fn :: (a -> b) -> (c -> d) -> (d -> b -> e) -> (c -> a -> e)
partialApplyFns2Fn r l f = (f .) . r
-- Функция, которая будет обрабатывать динамически типизируемые функции
anyFuncMaker :: AnyFunc -> AnyFunc -> AnyFunc -> Maybe AnyFunc
anyFuncMaker (AnyUnary l) (AnyUnary r) (AnyBinary f) =
case cast f of
Just f' -> Just $ AnyBinary (partialApplyFns2Fn l r f')
Nothing -> Nothing
anyFuncMaker _ _ _ = Nothing
Шаг 3: Динамическая компоновка функций
Ваша задача состоит в том, чтобы динамически объединять функции и возвращать новый динамический тип. Вы можете создать функцию, которая будет принимать динамические функции и их типы, а затем выполнять объединение:
combineDynams :: (Typeable a1, Typeable a2, Typeable b, Typeable c) =>
(TypeRep, Dynamic) ->
(TypeRep, Dynamic) ->
(TypeRep, Dynamic) ->
(TypeRep, Dynamic) ->
Maybe Dynamic
combineDynams (tr1, f1) (tr2, f2) (_, f3) (targetTypeRep) = do
f1' <- fromDynamic f1 :: Maybe (a1 -> b)
f2' <- fromDynamic f2 :: Maybe (a2 -> c)
f3' <- fromDynamic f3 :: Maybe (c -> b -> d)
return $ toDyn (partialApplyFns2Fn f1' f2' f3')
Проблема будет заключаться в том, чтобы правильно установить соответствие типов. Обязательно укажите соответствующие типы для Dynamic
, чтобы они были совместимы.
Заключение
Таким образом, ключ к решению вашей проблемы заключается в правильной динамической обработке типов и использовании GADTs для обобщения функций. Вы можете использовать Typeable
для работы с различными типами, обеспечивая целостность ваших операций. Обратите внимание на то, чтобы GHC мог четко распознать и обрабатывать типы на этапе компиляции и что согласование типов приводит к успешному выполнению программы без ошибок динамической типизации.