Haskell один из быстроразвивающихся функциональных языков программирования. Возможно это один из тех функциональных языков которому вы хотите отдать предпочтение практического использования в ближайшее время.
Этот топик для тех, кто хотел бы опробовать Haskell на деле, но имеет горы полезного C и C++ кода с которым требуется считаться.
Переход на другой язык программирвания всегда сопряжён с желанием сохранить возможность свободного использования предыдущих наработок, библиотек и т.д. Для этих целей в Haskell есть библиотека Foreign.C которая реализует механизм интерфейса с функциями других языков (Foreign Function Interface — FFI).
Рассмотрим подробнее как это происходит.
Подключение C-кода через Foreign.C
Пусть у нас есть некоторый исходный код:
hello.h
—
void hello(char* name);
hello.c
—
#include "stdio.h"
#include "hello.h"
void hello(char* name)
{
printf("Hello %s!n", name);
}
Мы хотим что бы функция hello была доступна из некоторого модуля Hello следующим образом:
Main.hs
—
module Main where
import Hello
main =
Hello.hello "World"
Для этого нужно:
- создать файл Hello.hs;
- подключить Haskell FFI через прагму {-# LANGUAGE ForeignFunctionInterface #-};
- добавить все необходимые модули из Foreign.C, в нашем случае это Foreign.C.String;
- описать сигнатуру нужных нам функций из файла-заголовка hello.h;
- обернуть при необходимости FFI-функции что бы избавиться от Foreign.C типов.
После чего модуль Hello.hs должен выглядеть примерно так:
Hello.hs
—
{-# LANGUAGE ForeignFunctionInterface #-}
module Hello where
import Foreign.C
import Foreign.C.String
foreign import ccall "hello" hello_ffi :: CString -> IO ()
hello :: String -> IO ()
hello name = hello_ffi =<< newCString name
Теперь наш код готов. Обычно при компиляции GHC требуется только *.hs файлы. Если мы попробуем провернуть такую штуку с нашим проектом, то получим ошибку:
$ ghc -o main Main.hs Hello.hs
Linking main ...
Hello.o: In function `s11h_info':
(.text+0x8e): undefined reference to `hello'
collect2: ld returned 1 exit status
Что бы такого не происходило нужно внимательно следить — доступны ли все транслируемые единицы (либо их результирующие объектные файлы) для GHC:
$ ghc -o main Main.hs Hello.hs hello.c
Linking main ...
Окей, теперь можно проверить программу:
$ ./main
Hello World!
Итак, наш модуль сработал корректно — слово World было передано в нашу библиотеку, и выведено на экран через printf.
Теперь как ответственные разработчики мы обязаны сделать cabal-проект.
Добавление C-исходников в cabal-проект
Сначала сгенерируем стандартный проект командой:
$ cabal init
Должно получиться что-то подобное:
name: hello-example
version: 0.1.0.0
synopsis: Example of cabal package with ffi
build-type: Simple
cabal-version: >=1.8
executable hello-example
main-is: Main.hs
build-depends: base ==4.5.*
Теперь достаточно добавить c-sources: hello.c, т.е. получить:
name: hello-example
version: 0.1.0.0
synopsis: Example of cabal package with ffi
build-type: Simple
cabal-version: >=1.8
executable hello-example
main-is: Main.hs
c-sources: hello.c
build-depends: base ==4.5.*
Можно проверить работоспособность cabal-пакета:
$ cabal configure
$ cabal build
$ cabal install
Теперь наша программа должна быть всегда доступна из командной строки:
$ hello-example
Hello World!
Особенности подключения C++ кода
К сожалению, при обращении с C++ наши возможности ограничены той же библиотекой Foreign.C, поэтому проще всего приводить все интерфейсы к C-совместимому виду.
Для примера заменим hello.c на hello.cpp реализованный через iostream:
hello.cpp
—
#include <iostream>
#include "hello.h"
void hello(char* name)
{
std::cout << "Hello " << name << "!" << std::endl;
}
Что бы получать корретные объектные файлы следует обрамлять экспортируемые функции extern-конструкцией:
hello.h
—
#ifdef __cplusplus
extern "C"
{
#endif
void hello(char* name);
#ifdef __cplusplus
}
#endif
Теперь если мы попробуем скомпилировать проект, то получим много-много однотипных ошибок:
$ ghc -o main Main.hs Hello.hs hello.cpp
cc1plus: warning: command line option ‘-Wimplicit’ is valid for C/ObjC but not for C++ [enabled by default]
Linking main ...
hello.o: In function `hello':
hello.cpp:(.text+0xf): undefined reference to `std::cout'
.........................................................
collect2: ld returned 1 exit status
Ошибки компоновки происходят из-за того что требуется явно указывать линковку со стандартной библиотекой, для gcc на linux обычно это библиотека -lstdc++.
$ ghc -o main Main.hs Hello.hs hello.cpp -lstdc++
cc1plus: warning: command line option ‘-Wimplicit’ is valid for C/ObjC but not for C++ [enabled by default]
Linking main ...
После этого можно подготовить cabal-проект. Кроме указания c-source для C++ требуется указывать ещё и extra-libraries:
name: hello-example
version: 0.1.0.0
synopsis: Example of cabal package with ffi
build-type: Simple
cabal-version: >=1.8
executable hello-example
main-is: Main.hs
c-sources: hello.cpp
extra-libraries: stdc++
build-depends: base ==4.5.*
Иногда даже при явном указании stdc++ проблемы с линковкой могут всё равно оставаться. В этом случае следует указывать ещё и --make опцию, несмотря на то что это избыточно (о чём вам и сообщит сборщик):
name: hello-example
version: 0.1.0.0
synopsis: Example of cabal package with ffi
build-type: Simple
cabal-version: >=1.8
executable hello-example
main-is: Main.hs
c-sources: hello.cpp
extra-libraries: stdc++
ghc-options: --make
build-depends: base ==4.5.*
Остальную информацию об особенностях и тонкостях работы с Haskell FFI можно почерпнуть с этой подборки ссылок.
Автор: pinocchio964