Состав данных. Динамические функции в Haskell

Вопрос или проблема

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

Оцените материал
Добавить комментарий

Капча загружается...