Подтвердить что ты не робот

Использование типов Haskell более высокого порядка в С#

Как я могу использовать и вызывать функции Haskell с сигнатурами типа более высокого порядка из С# (DLLImport), например...

double :: (Int -> Int) -> Int -> Int -- higher order function

typeClassFunc :: ... -> Maybe Int    -- type classes

data MyData = Foo | Bar              -- user data type
dataFunc :: ... -> MyData

Какова соответствующая подпись типа в С#?

[DllImport ("libHSDLLTest")]
private static extern ??? foo( ??? );

Дополнительно (потому что это может быть проще): Как я могу использовать "неизвестные" типы Haskell внутри С#, поэтому я могу, по крайней мере, их обойти, без С#, зная какой-либо конкретный тип? Самая важная функциональность, которую мне нужно знать, это передать класс типа (например, Monad или Arrow).

Я уже знаю как скомпилировать библиотеку Haskell в DLL и использовать в С#, но только для функций первого порядка. Я также знаю qaru.site/info/192155/..., Почему GHC доступен для .NET и hs-dotnet, где я не нашел ЛЮБОЙ документации и образцов (для С# в Haskell направление).

4b9b3361

Ответ 1

Я расскажу здесь о своем комментарии к сообщению FUZxxl.
Приведенные вами примеры возможны с помощью FFI. Как только вы экспортируете свои функции с помощью FFI, вы можете, как вы уже разобрались, скомпилировать программу в DLL.

.NET был разработан с целью легко взаимодействовать с C, С++, COM и т.д. Это означает, что как только вы сможете скомпилировать свои функции в DLL, вы можете назвать это (относительно) легко из.СЕТЬ. Как я уже упоминал ранее в своем другом сообщении, с которым вы связались, помните, какое соглашение о вызове вы указываете при экспорте своих функций. Стандартом в .NET является stdcall, в то время как (наиболее) примеры экспорта Haskell FFI с использованием ccall.

До сих пор единственным ограничением, которое я нашел в том, что может быть экспортировано FFI, является polymorphic types или типы, которые не применяются полностью. например ничего, кроме вида * (вы не можете экспортировать Maybe, но вы можете экспортировать Maybe Int, например).

Я написал инструмент Hs2lib, который будет автоматически охватывать и экспортировать любую из функций, которые у вас есть в вашем примере. Он также имеет возможность генерировать код unsafe С#, который делает его в значительной степени "подключи и играй". Причина, по которой я выбрал небезопасный код, заключается в том, что с ними проще обрабатывать указатели, что в свою очередь упрощает процесс сортировки для данных.

Чтобы быть полным, я подробно расскажу, как инструмент обрабатывает ваши примеры и как я планирую обработку полиморфных типов.

  • Функции более высокого порядка

При экспорте функций более высокого порядка функцию нужно слегка изменить. Аргументы более высокого порядка должны стать элементами FunPtr. В основном они рассматриваются как явные указатели функций (или делегаты в С#), а именно то, как более высокая упорядоченность обычно выполняется на императивных языках.
Предполагая, что мы преобразуем Int в CInt, тип double преобразуется из

(Int -> Int) -> Int -> Int

в

FunPtr (CInt -> CInt) -> CInt -> IO CInt

Эти типы генерируются для функции-обертки (doubleA в этом случае), которая экспортируется вместо double. Функции обертки отображаются между экспортируемыми значениями и ожидаемыми входными значениями для исходной функции. IO необходим, потому что построение a FunPtr не является чистой операцией.
Следует помнить, что единственный способ построить или разыменовать FunPtr - это статическое создание импорта, которое инструктирует GHC создавать для этого заглушки.

foreign import stdcall "wrapper" mkFunPtr  :: (Cint -> CInt) -> IO (FunPtr (CInt -> CInt))
foreign import stdcall "dynamic" dynFunPtr :: FunPtr (CInt -> CInt) -> CInt -> CInt

Функция "обертка позволяет нам создать FunPtr и " динамический " FunPtr, позволяющий поклоняться одному.

В С# мы объявляем ввод как IntPtr, а затем используем вспомогательную функцию Marshaller Marshal.GetDelegateForFunctionPointer для создания указателя функции, который мы можем вызвать, или обратную функцию для создания IntPtr из указателя функции.

Также помните, что вызывающее соглашение функции, передаваемой как аргумент FunPtr, должно соответствовать вызывающему соглашению функции, к которой передается аргумент. Другими словами, передача &foo в bar требует, чтобы foo и bar имели одно и то же соглашение о вызовах.

  • Пользовательские типы данных

Экспорт пользовательского типа данных на самом деле довольно прямолинейный. Для каждого типа данных, который необходимо экспортировать, для этого типа должен быть создан экземпляр Storable. В этих экземплярах указывается информация для сортировки, которую требуется GHC, чтобы иметь возможность экспортировать/импортировать этот тип. Помимо прочего, вам нужно будет определить типы size и alignment типа, а также как читать/записывать указателю значения типа. Я частично использую Hsc2hs для этой задачи (следовательно, макросы C в файле).

newtypes или datatypes с помощью только конструктора один. Они становятся плоской структурой, поскольку существует только одна возможная альтернатива при построении/разрушении этих типов. Типы с несколькими конструкторами становятся объединением (структура с атрибутом Layout, установленным на Explicit в С#). Однако нам также необходимо включить перечисление, чтобы определить, какая конструкция используется.

в общем случае тип данных Single, определенный как

data Single = Single  { sint   ::  Int
                      , schar  ::  Char
                      }

создает следующий Storable экземпляр

instance Storable Single where
    sizeOf    _ = 8
    alignment _ = #alignment Single_t

    poke ptr (Single a1 a2) = do
        a1x <- toNative a1 :: IO CInt
        (#poke Single_t, sint) ptr a1x
        a2x <- toNative a2 :: IO CWchar
        (#poke Single_t, schar) ptr a2x

    peek ptr = do 
        a1' <- (#peek Single_t, sint) ptr :: IO CInt
        a2' <- (#peek Single_t, schar) ptr :: IO CWchar
        x1 <- fromNative a1' :: IO Int
        x2 <- fromNative a2' :: IO Char
        return $ Single x1 x2

и C struct

typedef struct Single Single_t;

struct Single {
     int sint;
     wchar_t schar;
} ;

Функция foo :: Int -> Single будет экспортироваться как foo :: CInt -> Ptr Single Хотя тип данных с несколькими конструкторами

data Multi  = Demi  {  mints    ::  [Int]
                    ,  mstring  ::  String
                    }
            | Semi  {  semi :: [Single]
                    }

генерирует следующий код C:

enum ListMulti {cMultiDemi, cMultiSemi};

typedef struct Multi Multi_t;
typedef struct Demi Demi_t;
typedef struct Semi Semi_t;

struct Multi {
    enum ListMulti tag;
    union MultiUnion* elt;
} ;

struct Demi {
     int* mints;
     int mints_Size;
     wchar_t* mstring;
} ;

struct Semi {
     Single_t** semi;
     int semi_Size;
} ;

union MultiUnion {
    struct Demi var_Demi;
    struct Semi var_Semi;
} ;

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

  • Применяемые типы

Мой трассировщик зависимостей будет испускать для типа Maybe Int зависимость от типа Int и Maybe. Это означает, что при генерации экземпляра Storable для Maybe Int голова выглядит как

instance Storable Int => Storable (Maybe Int) where

То есть, поскольку существует экземпляр Storable для аргументов приложения, сам тип также может быть экспортирован.

Так как Maybe a определяется как имеющий полиморфный аргумент Just a, при создании структур теряется некоторая информация о типе. Структуры будут содержать аргумент void*, который вы должны вручную преобразовать в нужный тип. Альтернатива была слишком громоздкой, на мой взгляд, которая также заключалась в создании специализированных структур. Например. struct MaybeInt. Но количество специализированных структур, которые могут быть созданы из нормального модуля, может быстро взорваться таким образом. (может добавить это как флаг позже).

Чтобы облегчить эту потерю информации, мой инструмент будет экспортировать любую документацию Haddock, найденную для функции, поскольку комментарии в сгенерированном включении. Он также поместит оригинальную подпись типа Haskell в комментарий. Затем IDE представит их как часть своего Intellisense (компиляция кода).

Как и во всех этих примерах, я обошел код для сторон .NET. Если вас интересует, что вы можете просто просмотреть вывод Hs2lib.

Существует несколько других типов, требующих специального лечения. В частности, Lists и Tuples.

  • В списках необходимо передать размер массива, из которого нужно вывести маршалл, поскольку мы взаимодействуем с неуправляемыми языками, где размер массивов неявно известен. Обратно, когда мы возвращаем список, нам также нужно вернуть размер списка.
  • Кортежи - это специальная сборка в типах. Чтобы экспортировать их, мы должны сначала сопоставить их с "нормальным" типом данных и экспортировать их. В инструменте это делается до 8-ти кортежей.

    • Полиморфные типы

Проблема с полиморфными типами e.g. map :: (a -> b) -> [a] -> [b] заключается в том, что size из a и b не знают. То есть, нет возможности зарезервировать пространство для аргументов и вернуть значение, поскольку мы не знаем, что это такое. Я планирую поддержать это, разрешив вам указать возможные значения для a и b и создать специализированную функцию-обертку для этих типов. В другом размере на императивном языке я бы использовал overloading, чтобы представить типы, которые вы выбрали для пользователя.

Что касается классов, предположение о открытом мире Haskell обычно является проблемой (например, экземпляр может быть добавлен в любое время). Однако во время компиляции доступен только статически известный список экземпляров. Я намерен предложить вариант, который автоматически экспортирует как можно больше специализированных экземпляров, используя этот список. например export (+) экспортирует специализированную функцию для всех известных экземпляров Num во время компиляции (например, Int, double и т.д.).

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

Надеюсь, это поможет, и я надеюсь, что это было не слишком долго.

Обновление. Там была какая-то большая проблема, которую я недавно обнаружил. Мы должны помнить, что тип String в .NET является неизменным. Поэтому, когда маршаллер отправляет его с кодом Haskell, мы получаем CWString копию оригинала. У нас есть, чтобы освободить это. Когда GC выполняется в С#, это не повлияет на CWString, которая является копией.

Однако проблема заключается в том, что когда мы освобождаем ее в коде Haskell, мы не можем использовать freeCWString. Указатель не был выделен с помощью C (msvcrt.dll) alloc. Есть три способа (которые я знаю), чтобы решить эту проблему.

  • используйте char * в коде С# вместо String при вызове функции Haskell. Затем у вас есть указатель на свободный, когда вы вызываете return, или инициализируете функцию с помощью fixed.
  • импортировать CoTaskMemFree в Haskell и освободить указатель в Haskell
  • используйте StringBuilder вместо String. Я не совсем уверен в этом, но идея в том, что, поскольку StringBuilder реализован как собственный указатель, Marshaller просто передает этот указатель на ваш код Haskell (который также может его обновить). Когда GC выполняется после возврата вызова, StringBuilder должен быть освобожден.

Ответ 2

Вы пытались экспортировать функции через FFI? Это позволяет вам создать более C-образный интерфейс для функций. Я сомневаюсь, что функции Haskell можно напрямую вызвать из С#. Дополнительную информацию см. В документе. (Ссылка выше).

После выполнения некоторых тестов, я думаю, что вообще невозможно экспортировать функции и функции высокого порядка с параметрами типа через FFI. [Citation needed]

Ответ 3

Хорошо, благодаря FUZxxl, решение, которое он разработал для "неизвестных типов". Храните данные в Haskell MVar в контексте IO и обменивайтесь с С# на Haskell функциями первого порядка. Это может быть решением, по крайней мере, для простых ситуаций.