Haskell: реальная реализация монада IO на другом языке?
Как действительно реализована монада IO? В смысле, какова будет фактическая реализация функции main
?
Как я могу назвать функцию haskell (IO) с другого языка, и мне в этом случае нужно поддерживать IO сам?
Использует ли main
действия ввода-вывода (лениво) в качестве ссылок, а затем вызывает их?
Или это работа переводчика, когда он нашел действия на своем пути, которые они могут назвать?
Или может быть что-то еще?
Есть ли хорошая реализация монада IO на разных языках, которая может помочь глубоко понять, что происходит в основной функции?
Edit:
Такой hGetContents
меня смущает, и я не знаю, как действительно выполняется IO.
Хорошо, скажем, у меня очень простой чистый интерпретатор Haskell, у которого, к сожалению, нет поддержки IO, и для любопытства я хочу добавить к нему эти действия IO (трюки также t23). Трудно получить его от GHC, Hugs или других.
Ответы
Ответ 1
Вот пример того, как можно реализовать монаду IO в Java:
package so.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import static so.io.IOMonad.*;
import static so.io.ConsoleIO.*;
/**
* This is a type containing no data -- corresponds to () in Haskell.
*/
class Unit {
public final static Unit VALUE = new Unit();
}
/**
* This type represents a function from A to R
*/
interface Function<A,R> {
public R apply(A argument);
}
/**
* This type represents an action, yielding type R
*/
interface IO<R> {
/**
* Warning! May have arbitrary side-effects!
*/
R unsafePerformIO();
}
/**
* This class, internally impure, provides pure interface for action sequencing (aka Monad)
*/
class IOMonad {
static <T> IO<T> pure(final T value) {
return new IO<T>() {
@Override
public T unsafePerformIO() {
return value;
}
};
}
static <T> IO<T> join(final IO<IO<T>> action) {
return new IO<T>(){
@Override
public T unsafePerformIO() {
return action.unsafePerformIO().unsafePerformIO();
}
};
}
static <A,B> IO<B> fmap(final Function<A,B> func, final IO<A> action) {
return new IO<B>(){
@Override
public B unsafePerformIO() {
return func.apply(action.unsafePerformIO());
}
};
}
static <A,B> IO<B> bind(IO<A> action, Function<A, IO<B>> func) {
return join(fmap(func, action));
}
}
/**
* This class, internally impure, provides pure interface for interaction with stdin and stdout
*/
class ConsoleIO {
static IO<Unit> putStrLn(final String line) {
return new IO<Unit>() {
@Override
public Unit unsafePerformIO() {
System.out.println(line);
return Unit.VALUE;
}
};
};
// Java does not have first-class functions, thus this:
final static Function<String, IO<Unit>> putStrLn = new Function<String, IO<Unit>>() {
@Override
public IO<Unit> apply(String argument) {
return putStrLn(argument);
}
};
final static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static IO<String> getLine = new IO<String>() {
@Override
public String unsafePerformIO() {
try {
return in.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
};
}
/**
* The program composed out of IO actions in a purely functional manner.
*/
class Main {
/**
* A variant of bind, which discards the bound value.
*/
static IO<Unit> bind_(final IO<Unit> a, final IO<Unit> b) {
return bind(a, new Function<Unit, IO<Unit>>(){
@Override
public IO<Unit> apply(Unit argument) {
return b;
}
});
}
/**
* The greeting action -- asks the user for his name and then prints a greeting
*/
final static IO<Unit> greet =
bind_(putStrLn("Enter your name:"),
bind(getLine, new Function<String, IO<Unit>>(){
@Override
public IO<Unit> apply(String argument) {
return putStrLn("Hello, " + argument + "!");
}
}));
/**
* A simple echo action -- reads a line, prints it back
*/
final static IO<Unit> echo = bind(getLine, putStrLn);
/**
* A function taking some action and producing the same action run repeatedly forever (modulo Qaru :D)
*/
static IO<Unit> loop(final IO<Unit> action) {
return bind(action, new Function<Unit, IO<Unit>>(){
@Override
public IO<Unit> apply(Unit argument) {
return loop(action);
}
});
}
/**
* The action corresponding to the whole program
*/
final static IO<Unit> main = bind_(greet, bind_(putStrLn("Entering the echo loop."),loop(echo)));
}
/**
* The runtime system, doing impure stuff to actually run our program.
*/
public class RTS {
public static void main(String[] args) {
Main.main.unsafePerformIO();
}
}
Это среда выполнения, реализующая интерфейс для ввода-вывода консоли вместе с небольшой чисто функциональной программой, которая приветствует пользователя, а затем запускает цикл эха.
Невозможно реализовать небезопасную часть в Haskell, потому что Haskell является чисто функциональным языком. Он всегда применяется на объектах более низкого уровня.
Ответ 2
Если вы хотите понять реализацию монады IO, это очень хорошо описано в удостоенной наград бумаге Фила Вадлера и Саймона Пейтона Джонса, которые были теми, кто выяснил, как использовать монады для ввода/вывода в чистый язык. Бумага Императивное функциональное программирование и находится на обоих сайтах авторов.
Ответ 3
С помощью Java 8 Lambdas вы можете взять код из ответа Rotsor выше, удалить класс Function, поскольку Java 8 предоставляет FunctionalInterface, делает то же самое и удаляет анонимный крутильный класс, чтобы получить более красивый выглядящий код:
package so.io;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.function.Function;
import static so.io.IOMonad.*;
import static so.io.ConsoleIO.*;
/**
* This is a type containing no data -- corresponds to () in Haskell.
*/
class Unit {
// -- Unit$
public final static Unit VALUE = new Unit();
private Unit() {
}
}
/** This type represents an action, yielding type R */
@FunctionalInterface
interface IO<R> {
/** Warning! May have arbitrary side-effects! */
R unsafePerformIO();
}
/**
* This, internally impure, provides pure interface for action sequencing (aka
* Monad)
*/
interface IOMonad {
// -- IOMonad$
static <T> IO<T> pure(final T value) {
return () -> value;
}
static <T> IO<T> join(final IO<IO<T>> action) {
return () -> action.unsafePerformIO().unsafePerformIO();
}
static <A, B> IO<B> fmap(final Function<A, B> func, final IO<A> action) {
return () -> func.apply(action.unsafePerformIO());
}
static <A, B> IO<B> bind(IO<A> action, Function<A, IO<B>> func) {
return join(fmap(func, action));
}
}
/**
* This, internally impure, provides pure interface for interaction with stdin
* and stdout
*/
interface ConsoleIO {
// -- ConsoleIO$
static IO<Unit> putStrLn(final String line) {
return () -> {
System.out.println(line);
return Unit.VALUE;
};
};
final static Function<String, IO<Unit>> putStrLn = arg -> putStrLn(arg);
final static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static IO<String> getLine = () -> {
try {
return in.readLine();
}
catch (IOException e) {
throw new RuntimeException(e);
}
};
}
/** The program composed out of IO actions in a purely functional manner. */
interface Main {
// -- Main$
/** A variant of bind, which discards the bound value. */
static IO<Unit> bind_(final IO<Unit> a, final IO<Unit> b) {
return bind(a, arg -> b);
}
/**
* The greeting action -- asks the user for his name and then prints
* greeting
*/
final static IO<Unit> greet = bind_(putStrLn("Enter your name:"),
bind(getLine, arg -> putStrLn("Hello, " + arg + "!")));
/** A simple echo action -- reads a line, prints it back */
final static IO<Unit> echo = bind(getLine, putStrLn);
/**
* A function taking some action and producing the same action run repeatedly
* forever (modulo Qaru :D)
*/
static IO<Unit> loop(final IO<Unit> action) {
return bind(action, arg -> loop(action));
}
/** The action corresponding to the whole program */
final static IO<Unit> main = bind_(greet, bind_(putStrLn("Entering the echo loop."), loop(echo)));
}
/** The runtime system, doing impure stuff to actually run our program. */
public interface RTS {
// -- RTS$
public static void main(String[] args) {
Main.main.unsafePerformIO();
}
}
Обратите внимание, что я также изменил статические методы, объявленные классом, для статических методов, объявленных интерфейсом. Зачем? Нет особой причины, просто чтобы вы могли в Java 8.
Ответ 4
Монада IO
в основном реализуется как трансформатор состояния (аналогично State
) со специальным токеном RealWorld
. Каждая операция ввода-вывода зависит от этого токена и передает его, когда он заканчивается. unsafeInterleaveIO
вводит второй токен, так что новая операция ввода-вывода может запускаться, а другая еще выполняет свою работу.
Обычно вам не нужно заботиться о реализации. Если вы хотите называть IO-функции с других языков, GHC заботится об удалении обертки IO. Рассмотрим этот небольшой фрагмент:
printInt :: Int -> IO ()
printInt int = do putStr "The argument is: "
print int
foreign export ccall printInt :: Int -> IO ()
Это генерирует символ для вызова printInt
из C. Функция становится:
extern void printInt(HsInt a1);
Где HsInt
- это просто (в зависимости от вашей платформы) typedef
d int
. Итак, вы видите, что монада IO
полностью удалена.
Ответ 5
Я оставлю вопрос об осуществлении IO другим людям, которые знают немного больше. (Хотя я укажу, что, как я уверен, они тоже будут, что реальный вопрос заключается не в том, "как IO реализован в Haskell?", А скорее "Как внедрено IO в GHC?" Или "Как реализовано IO в Hugs?" и т.д. Я предполагаю, что реализации сильно различаются.) Однако этот вопрос:
как вызвать функцию haskell (IO) с другого языка и мне в этом случае нужно поддерживать IO сам?
... будет подробно рассмотрен в спецификации FFI.
Ответ 6
Ниже приведена фактическая реализация IO
в GHC 7.10.
Тип IO
по существу является государственной монадой в типе State# RealWorld
(определенной в GHC.Types
):
{- |
A value of type @'IO' [email protected] is a computation which, when performed,
does some I\/O before returning a value of type @[email protected]
There is really only one way to \"perform\" an I\/O action: bind it to
@[email protected] in your program. When your program is run, the I\/O will
be performed. It isn't possible to perform I\/O from an arbitrary
function, unless that function is itself in the 'IO' monad and called
at some point, directly or indirectly, from @[email protected]
'IO' is a monad, so 'IO' actions can be combined using either the do-notation
or the '>>' and '>>=' operations from the 'Monad' class.
-}
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))
Монада IO
является строгой, потому что bindIO
определяется case
совпадением (определенным в GHC.Base
):
instance Monad IO where
{-# INLINE return #-}
{-# INLINE (>>) #-}
{-# INLINE (>>=) #-}
m >> k = m >>= \ _ -> k
return = returnIO
(>>=) = bindIO
fail s = failIO s
returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)
bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
Эта реализация обсуждается в блоге Эдварда Янга.
Ответ 7
Фактически "IO a" является просто "() → a" на нечистом языке (функции могут иметь побочный эффект). Скажем, вы хотите реализовать IO в SML:
structure Io : MONAD =
struct
type 'a t = unit -> 'a
return x = fn () => x
fun (ma >>= g) () = let a = ma ()
in g a ()
executeIo ma = ma ()
end