Абстрагирование Input-Output (IO) для библиотек
При написании библиотеки, работающей так или иначе с вводом-выводом, операционной системой и т.д., вы должны стремиться ее делать кросс-платформенной (если того позволяет сущность библиотеки), а также (и самое важное) осуществить возможность выполнение в разных среда (aka с разными библиотека ввода-вывода).
Что это значит и для чего оно нужно. Например, вы пишите библиотеку для работы с каким-нибудь внешним аппаратном интерфейсом, вы станете использовать классические блокирующие функции для ввода-вывода, и оно вполне себе будет прекрасно, но все до тех пор, пока вы или ваш пользователь не захочет воспользоваться этой библиотекой в программе, использующий, допустим, библиотеку Lwt для асинхронного программирования, а ваши блокирующие функции станут камнем преткновения, благодаря захордкожанным операциям ввода-вывода.
Это очень актуально также для реализации клиентов для API сервисов — дать возможность пользователю выбирать нужный ему HTTP-клиент.
Смотрите также
- Platform-depend select — пишите разный код для разных платформ с разным набором функций, обеспечивая переносимость и поддержку платформы;
Абстрагирование через инверсию зависимостей
Смотрите по топику
Такую практику вы можете встретить в библиотеке Cohttp, что может быть избыточно для вашего проекта.
В тип-модуле IO описывается вся часть, связанная с работой над вводом-выводом и другими системными вещами. Обратите внимание на то, что это прозрачные абстракции, никаких типов обверток, исключительно статическая подстановка типов, как увидеть далее.
(* s.ml *)
module type IO = sig
type +'a t
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
val (>|=) : 'a t -> ('a -> 'b) -> 'b t
val return : 'a -> 'a t
type in_channel
val read_line : in_channel -> string t
endЧерез функтор Make мы можем внедрять зависимость на этой базе генерировать целевой код библиотеке.
(* lib.ml *)
module Make (IO : S.IO) = struct
open IO
let read_two_lines ic =
read_line ic >>= fun first_line ->
read_line ic >|= fun second_line ->
(first_line, second_line)
endКонечная имплементация и получения библиотеке под конкретную среду, в этом случае для Lwt. Обратите внимание на типы, у нас нет никаких наших IO или in_channel заместо них у нас оригинальные aka нативные для Lwt типы.
(* lib_lwt.ml *)
include Lib.Make (struct
include Lwt
include Lwt_io
type in_channel = input_channel
end)
(* val read_two_lines : Lwt_io.input Lwt_io.channel -> (string * string) Lwt.t *)Тоже самое, но для Stdlib.
(* lib_unix.ml *)
include Lib.Make (struct
type 'a t = 'a
let (>>=) x f = f x
and (>|=) x f = f x
and return = Fun.id
include Stdlib
let read_line = input_line
end)
(* val read_two_lines : in_channel -> string * string *)Разбивайте логику и пишите конкретной код
Самый очевидный способ — выделяйте общие части, вроде типов, а для конкретных сред пишите корректный код.
Смотрите по топику
библиотеку serialport как пример, где и как можно пойти на компромисс.