Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 1995 сообщений
Cообщения с меткой

машина времени - Самое интересное в блогах

Следующие 30  »
lipa_fv

ПОКА ГОРИТ СВЕЧА...

Четверг, 18 Августа 2016 г. 14:32 (ссылка)


Не знаю, может это возрастное, но мне так захотелось послушать именно эту песню. Есть в ней что-то ностальгическое...





Метки:   Комментарии (1)КомментироватьВ цитатник или сообщество
Angel767

Машина времени "Он был старше ее"

Вторник, 09 Августа 2016 г. 19:15 (ссылка)
Слушать этот музыкальный файл

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
lira_lara

25 июня 1969 года на свет появилась рок-группа «МАШИНА ВРЕМЕНИ».

Суббота, 25 Июня 2016 г. 14:53 (ссылка)


25 июня 1969 года на свет появилась рок-группа «МАШИНА ВРЕМЕНИ». 





 Машина Времени - рок-группа, один из пионеров в рок-музыке СССР, основанная в СССР в 1969 Андреем Макаревичем и Александром Кутиковым, к которым позднее присоединился Евгений Маргулис. Из-за большого количества композиторов жанр группы эклектичен, используя элементы классического рока, рок-н-ролла, блюза, бардовской песни.







Группа под названием «The Kids» была создана Андреем Макаревичем в 1968 году из одноклассников. Своё первое выступление ансамбль дал, когда в школу приехал ВИА Атланты и дал молодым музыкантам недолго попрактиковаться на их аппаратуре. Название «Машины Времени» появилось в 1969, в 1975 было изменено на единственное число — «Машина Времени», которым и остаётся до сих пор. Состав остаётся нестабильным, а коллектив — самодеятельным. На концертах группа исполняет кавер-версии песен The Beatles и свои песни на английском, написанные в подражание.



В начале 70-х в группу входят: Андрей Макаревич (бас-гитара, вокал), Александр Кутиков (гитара), Сергей Кавагое (ударные), остальные участники постоянно меняются. В 1975 из-за ссоры с Кавагое группу покидает Кутиков, который уходит в группу Високосное Лето. Его заменяет Евгений Маргулис, которому Макаревич передаёт обязанности басиста и отныне играет только на соло-гитаре. Маргулис также начинает писать песни для группы с уклоном в блюз.





 



Читать далее...
Метки:   Комментарии (1)КомментироватьВ цитатник или сообщество
Valkiriya76

Машина времени или все-таки НЛО?

Суббота, 04 Июня 2016 г. 11:56 (ссылка)

Это цитата сообщения Мари_Мэри Оригинальное сообщение

В Австрии найден древний артефакт похожий на сотовый телефон





 4497432_s83467658 (608x700, 223Kb)



Археологи в австрийском городе Зальцбург нашли поистине сенсационную находку. Это глиняный слепок возрастом 800 лет, который до мельчайших деталей напоминает современный мобильный телефон. Правда надписи на кнопках сделаны клинописью.



Ученые предполагают, что этот предмет попал в Австрию из Месопотамии. Для чего он предназначался исследователи пока не разобрались, но блогеры уже решили, что это является доказательством существования машины времени.

Читать далее...
Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Как написать монаду IO на C# (не) без помощи параллельной вселенной и машины времени

Среда, 04 Мая 2016 г. 13:41 (ссылка)

В жизни нередко бывают ситуации когда нужно просто сесть и сделать какое-то дело, не забивая себе голову вопросами вроде "а что это даст?", "а кому это нужно?" и т. п. Написание монады IO — безусловно именно такой случай. Поэтому под катом находится рассказ о том как написать монаду IO на C#, без малейших попыток объяснить зачем это делать.



Баянистая картинка, отражающая суть статьи




Интерпретации монады IO



Есть два способа осмысления (интепретации) монады IO.



С первой интерпретацией можно ознакомиться, например, в замечательной статье IO inside. Хотя это и не декларировано явно, в этой статье IO рассматривается как способ обмануть компилятор Haskell, впихнуть вызовы «грязных» нативных функций в чистый код благодаря возможности формально обращаться с ними как с чистыми из-за добавления в сигнатуру фиктивных элементов.



Вторую же интерпретацию можно понять, заметив, что для других монад (например, Get из модуля Data.Binary) имеются функции типа m a -> a (для Get это функция runGet), то есть существует возможность вытащить значение из монады. Для IO же такой функции нет, и единственный способ её выполнить — вернуть из функции main нативному рантайму. То есть IO — это список действий (сценарий), и задача чистого кода этот список действий сформировать, но не выполнять.



Ни одна из этих интерпретаций не помогает транслировать понятие IO на C#: в первом случае мы замечаем, что в C# и так нет никакой трудности вызвать «грязный» код из любого места, а во втором что в C# все функции и программа целиком — это и есть список действий, а точка с запятой есть ни что иное как монадный оператор >>=.



Очевидно, что проблема в уникальности IO: в то время как другие монады являются синтаксическим сахаром, ввод-вывод есть такая операция, которую затруднительно осуществить, оставаясь в рамках чистого кода. И если найти способ написать реально чистый (без монад, без прямого или косвенного вызова нативных функций) код, осуществляющий ввод-вывод, то IO предстанет просто синтаксическим сахаром для этих чистых функций, объектом того же рода, что и монада Maybe. Ну а уж Maybe на C# не писал только ленивый.



Чистые функции ввода-вывода на Haskell. Принцип действия



Пусть у нас (в рамках некоторого языка программирования) есть функция beep, которая возвращет число 7 и выводит на экран сообщение «Beep!» и другая функция returnBeep, которая просто возвращает число 7. Что можно сказать о чистоте этих функций?



На первый взгляд кажется что returnBeep чистая, а beep — нет: чистая функция — это функция, не дающая побочных эффектов, а в последнем случае побочный эффект явно имеется.



Однако, при использовании чистой функции returnBeep в программе её надо вычислить, и побочные эффекты при вычислении также будут присутствовать, как минимум в виде рассеиваемого компьютером тепла. Но значит ли это, что функция returnBeep перестала быть чистой из-за этого? Ответить на этот вопрос можно по-разному и — так же как и в случае вопроса «пересекаются ли параллельные прямые?» — любой из ответов можно принять за аксиому и построить на этом непротиворечивую теорию, позволяющую создать модель некоторой части окружающего мира. Так что мы примем как аксиому, что особенности реализации вычислителя и, в частности, создаваемые им при вычислении функции побочные эффекты не влияют на чистоту вычисляемой функции.



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



В соответствии с вышесказанным, нет ничего невозможного в том, чтобы при исполнении программы, состоящей только из чистых функций, выводилось то, что должно быть выведено и считывалось то, что должно быть считано — то есть чтобы компьютер вёл себя так, как будто у нашей программы есть побочный эффект, хотя его нет. Более того, обеспечение такой активности перекладывается с наших плеч — плеч прикладного программиста — на плечи разработчиков исполнителя; то есть на совокупные виртуальные плечи разработчиков микропроцессора, компилятора, рантайма Haskell и всего такого.



Что остаётся сделать в коде программы — это убедиться что нужные буквы на экран вывелись и узнать какие ввелись. И вот для этого нам и пригодятся машина времени и параллельная вселенная.



Идея состоит в том, чтобы чтобы засериализить Вселенную (всё, что было, есть и будет), затем закодировать (для удобства) в base64 и включить в нашу программу в виде захардкоженной строковой константы. На самом деле, я не уверен как в необходимости указанного набора (машина времени + параллельная вселенная), так и в его достаточности. Однако, без параллельной вселенной, по-моему, обойтись не получится, так как засериализить вселенную, находясь при этом в самой этой вселенной будет, пожалуй, посложнее чем написать quin на POSIX shell.



Конечно, для того чтобы программа на Haskell (являющаяся чистой функцией) могла вытащить из константы Вселенной информацию, относящуюся к конкретному запуску (инстансу) этой программы, на вход она должна принимать некий идентификатор этого инстанса. И она действительно его принимает — в виде значения типа RealWorld (см. уже упоминавшуюся статью IO inside). Прямая работа со значениями этого типа в Haskell невозможна, но нетрудно превратить значение типа RealWorld в значение типа Integer, String или любого другого, используя доступные стандартные функции. Конкретный тип и способ преобразования зависит от кодировки константы Вселенной и реализации функций ввода-вывода.



Действуя указанным образом, не составит труда создать библиотеку чистых функций ввода-вывода. Сигнатуры функций могут быть, например, такими (ограничимся для простоты простейшим консольным вводом-выводом строк):



getOutText :: AppInstance -> IOIndex -> Maybe String
getInText :: AppInstance -> IOIndex -> Maybe String


Функция getOutText принимает инстанс приложения и номер текста в диалоге юзера и программы и возвращает соответствующий выведенный компьютером текст либо Nothing если входные параметры некорректны. Результат Nothing возвращается, например, если переданный номер соответствует введённому юзером, а не выведенному компьютером тексту. Так, если указанный инстанс программы ничего не выводил, то для любого значения номера должно возвращаться Nothing. Функция getInText принимает такие же аргументы и возвращает соответствующий введённый юзером текст либо Nothing.



В действительности оказывается, что вместо функции getOutText удобнее пользоваться более ограниченной, но достаточной для практических надобностей функцией isOutTextEquals со следующей сигнатурой и семантикой:



isOutTextEquals :: String -> AppInstance -> IOIndex -> Bool
isOutTextEquals text inst index = getOutText inst index == Just text


Чистые функции ввода-вывода на Haskell. Модельная реализация



Несмотря на то, что довести описанные выше идеи до рабочего кода для нашей Вселенной затруднительно, можно проделать это для простой модельной вселенной.



Итак, пусть существует некоторая вселенная, в которой живёт некоторое существо по имени noname. Noname учится в вузе на специальности computer science, и ему для сдачи госэкзамена по программированию требуется написать проект — консольную программу, которая спрашивает у пользователя имя, считывает ответ и выводит приветствие:

What is your name?

Вася

Hi, Вася!



Единственный язык программирования в этой вселенной — Haskell, причём без встроенной поддержки монады IO. Заготовка программы, созданная noname, выглядит так:



Код
module Main_ where
import Control.Monad
import Data.Vector (Vector, (!?))
import qualified Data.ByteString.Lazy.UTF8 as U
import Data.ByteString.Base64.Lazy

worldBase64 :: String
worldBase64
= "V29ybGQge2FwcEluc3RhbmNlcyA9IFtbSU9PcGVyYXRpb24gSU9Xcml0ZSAiV2hhdCBpcyB5b3Vy"
++ "IG5hbWU/CiIsSU9PcGVyYXRpb24gSU9SZWFkICJub25hbWUiLElPT3BlcmF0aW9uIElPV3JpdGUg"
++ "IkhpLCBub25hbWUhCiJdLFtJT09wZXJhdGlvbiBJT1dyaXRlICJXaGF0IGlzIHlvdXIgbmFtZT8K"
++ "IixJT09wZXJhdGlvbiBJT1JlYWQgIlwxMDQyXDEwNzJcMTA4OVwxMTAzIixJT09wZXJhdGlvbiBJ"
++ "T1dyaXRlICJIaSwgXDEwNDJcMTA3MlwxMDg5XDExMDMhCiJdLFtJT09wZXJhdGlvbiBJT1dyaXRl"
++ "ICJXaGF0IGlzIHlvdXIgbmFtZT8KIixJT09wZXJhdGlvbiBJT1JlYWQgIlwxMDU0XDEwODNcMTEw"
++ "MyIsSU9PcGVyYXRpb24gSU9Xcml0ZSAiSGksIFwxMDU0XDEwODNcMTEwMyEKIl1dfQo="

type AppInstance = Int
type IOIndex = Int
data IOAction = IORead | IOWrite deriving (Eq, Show, Read)
data IOOperation = IOOperation IOAction String deriving (Show, Read)
data World = World { appInstances :: Vector (Vector IOOperation) } deriving (Show, Read)

world :: World
world = read $ U.toString $ decodeLenient $ U.fromString worldBase64

getInOutText :: IOAction -> AppInstance -> IOIndex -> Maybe String
getInOutText action app i = do
IOOperation actual_action result <- (!? i) <=< (!? app) $ appInstances world
if actual_action == action then return result else Nothing

getInText :: AppInstance -> IOIndex -> Maybe String
getInText = getInOutText IORead

getOutText :: AppInstance -> IOIndex -> Maybe String
getOutText = getInOutText IOWrite

isOutTextEquals :: String -> AppInstance -> IOIndex -> Bool
isOutTextEquals text inst index = getOutText inst index == Just text

_main :: AppInstance -> Maybe String
_main app = do
let question = "What is your name?\n"
_ <- if isOutTextEquals question app 0 then return () else Nothing
name <- getInText app 1
let greeting = "Hi, " ++ name ++ "!\n"
_ <- if isOutTextEquals greeting app 2 then return () else Nothing
return $ question ++ name ++ "\n" ++ greeting




По историческим причинам главные функция и модуль в этой вселенной называются, соответственно, Main_ и _main, и, как можно видеть, функция _main имеет тип AppInstance -> Maybe String. В реализации noname _main возвращает протокол диалога — это не требуется условиями задачи, но может быть полезно в целях отладки.



Noname проверил рабоспособность программы, запустив её и введя своё имя — и программа вроде бы сработала как надо — запросила имя и в ответ на «noname» выдала «Hi, noname!»



(Как вы можете заметить в программе нет никаких явных указаний что именно надо выводить на экран и в какие моменты ждать ввода пользователя, так что факт работоспособности этой программы весьма показателен в смысле демонстрации мощи эвристики, заложенной в стандартной райнтайм Haskell вселенной noname.)



Однако, несмотря на то, что программа сработала верно при тестовом первом запуске, у noname нет уверенности, что программа и дальше будет работать: ведь «константу вселенной» (worldBase64) он вписал тестовую, поскольку настоящую он не знает. Поэтому noname разработал машину времени (оригинальной конструкции, с встроенным довольно мощным подавителем парадоксов) и вышел на связь с нашей Вселенной, передав листинг программы и чертежи машины времени в обмен на обещание предоставить ему точную константу его вселенной (точнее, интересующей его части) — нам, мол, отсюда виднее, чем ему изнутри.



Запустить эту иновселенскую программу как она есть, понятно, не получится. Но если снабдить её следующим стартовым модулем, то как-то она работать будет:



Код
module Main where

import System.Environment
import Data.Vector ((!?))
import qualified Data.Vector as V hiding ((!?))
import Main_

main :: IO ()
main = do
args <- V.fromList <$> getArgs
case _main =<< read <$> (args !? 0) of
Just text -> putStr text
Nothing -> putStrLn "Error!"




(Полный проект лежит на гитхабе: pure-io. Кстати если вы всё ещё не знаете, как управляться с инструментом сборки проекта и управления зависимостями stack, который пришёл на смену cabal, то вот хорошая статья: Прощай, cabal. Здравствуй, stack!.)



Конечно, мы не увидим «выводимых» сообщений, равно как и не сможем ввести имя, потому что у нас нет умного иновселенского райнтайма — лишь запись соответствующего диалога, возвращаемая функцией _main, будет нам доступна. Передавать номер запуска (AppInstance) также придётся вручную — аргументом командной строки, но этого достаточно, чтобы понять и смоделировать функционирование этой программы в её реальном окружении.



Поняв принцип программы и построив машину времени по предоставленным чертежам, несложно убедиться, что подправлять константу wordBase64 не требуется: программа будет запущена всего три раза (считая первый тестовый запуск), и все три раза её будет запускать автор, вводя те самые имена, которые он с самого начала закодировал в тексте программы!



Разобравшись таким образом с принципом действия чистого немонадного ввода-вывода и предоставив братскую помощь иновселенцу noname, перейдём от модельной вселенной к реальной и от Haskell к C#.



Чистые функции ввода-вывода на C#. Интерфейс



Из-за наличия механизма эксепшенов C#-функция, возвращающая X, по факту возвращает Either Exception X; в частности, void-функции «возвращают» Either Exception (). (К слову, в Haskell ситуация схожа, и условное определение тамошнего стандартного IO с учётом наличия эксепшенов выглядит не как type IO a = RealWorld -> (RealWorld, a), а скорее как type IO a = RealWorld -> (RealWorld, Either SomeException a).).



Учитывая означенную ситуацию с эксепшенами, выберем такие заготовки сигнатур наших чистых функций ввода-вывода:



static void AssertOutTextEquals(string text, AppInstance inst, int index);
static string GetInText(AppInstance inst, int index);


Функция AssertOutTextEquals — это та же isOutTextEquals, только вместо True она возвращает void без экспшена, а вместо False кидает эксепшен. Аналогично, функция GetInText либо возвращает ненулевую строку либо кидает эксепшен.



Эти функции — чистые, поэтому можно было бы выразить их через другие чистые функции (как это было показано в предыдущем разделе), но вместо этого мы пойдём другим путём. А именно, создадим эквивалентную нативную реализацию — благодаря этому нам не понадобятся параллельные вселенные и машины времени.



Посмотрим, нельзя ли как-то ещё отрефакторить и улучшить внешний синтаксис этих функций, не нарушая идеологии. Можно сделать их нестатическими членами AppInstance, а также использовать вместо void что-то более идиоматическое (для функционального кода):



public sealed class None {
public static None _ { get { return null; } }
None() { }
}
public sealed class AppInstance {
public None AssertOutTextEquals(string text, int index);
publi string GetInText(int index);
}


Использование None вместо void позволит избежать ненужного дублирования кода и облегчит написание собственно монады.



Далее, можно заметить, что первая функция является функциональным эквивалентом Console.Write(string), а вторая — Console.ReadLine(). Помимо этих в классе Console есть ещё много полезных функций ввода и вывода и, используя linq expressions, мы можем обобщить наши чистые функции так, чтобы поддержать сразу их все:



public None AssertOutTextEquals(Expression ioExpression, int index);
public TResult GetInText(Expression> ioExpression, int index);


Наконец, переставим для удобства параметры и дадим методам одинаковое краткое имя, чтобы подчеркнуть симметрию:



public None AssertIO(int index, Expression ioExpression);
public TResult AssertIO(int index, Expression> ioExpression);


Такая унификация оправдана тем, что если бы None было частью стандартной экосистемы, то вместо Action мы имели бы Func и первая функция исчезла бы как частный случай второй.



Для того, чтобы мы могли писать юнит-тесты, необходимо предусмотреть возможность работы не только с реальной консолью, но и с тестовой заменой:



Код
public sealed class AppInstance {
readonly static Lazy inst = new Lazy(() => new AppInstance((method, argTypes, args) => typeof(Console).GetMethod(method, BindingFlags.Static | BindingFlags.Public, null, argTypes, null).Invoke(null, args)));
public static AppInstance Get() { return inst.Value; }

readonly Func consoleDescriptor;

internal AppInstance(Func consoleDescriptor) {
this.consoleDescriptor = consoleDescriptor;
}
}
public static class AppInstanceTestExtensions {
public static AppInstance ForTests(this AppInstance inst, Func consoleDescriptor) {
return new AppInstance(consoleDescriptor);
}
}




Подготовим тестовое окружение:



Код
[TestFixture]
public class Tests {
TestConsole console;
AppInstance testInst;

protected void Setup(string input) {
console = new TestConsole(input);
testInst = AppInstance.Get().ForTests((method, argTypes, args) => {
var call = new object[] { console, console.In, console.Out }.Select(x => new { t = x, m = x.GetType().GetMethod(method, argTypes) }).Where(x => x.m != null).First();
return call.m.Invoke(call.t, args);
});
}
}
public class TestConsole {
readonly MemoryStream output;
StreamWriter writer;
readonly MemoryStream input;
StreamReader reader;

public TestConsole(string input) {
this.input = new MemoryStream(Encoding.UTF8.GetBytes(input));
this.reader = new StreamReader(this.input);
this.output = new MemoryStream();
this.writer = new StreamWriter(this.output);
}

public TextWriter Out { get { return writer; } }
public TextReader In { get { return reader; } }
public string Output {
get {
if(writer != null) {
writer.Close();
writer = null;
}
return Encoding.UTF8.GetString(output.ToArray());
}
}
}




Чистые функции ввода-вывода на C#. Тесты



Начнём с простого:



[Test]
public void WriteChars() {
Setup("");
testInst.AssertIO(0, () => Console.Write('A'));
testInst.AssertIO(1, () => Console.Write('B'));
Assert.AreEqual("AB", console.Output);
}


Заметим, что здесь тестируется не только работоспособность чистой функции AssertIO, но и сайд-эффекты: в коде написано ... Write('A') ... Write('B') ..., и ожидается что на экран будет выведено «AB».



Попробуем что-нибудь поинтереснее. Например, переставим местами вызовы AssertIO. Поскольку AssertIO — чистая функция, то пожет показаться, что результат (отсутствие эксепшенов) не должен измениться. Но это не так: это другой тест, в нём другой AppInstance, и поэтому результат может измениться (хотя может и не измениться). На практике оказывается, что в таком случае ничего не выводится:



[Test]
public void WriteCharsInBackOrder() {
Setup("");
Assert.Throws(() => testInst.AssertIO(1, () => Console.Write('B')));
Assert.Throws(() => testInst.AssertIO(0, () => Console.Write('A')));
Assert.AreEqual("", console.Output);
}


Чистая функция должна возвращать один и тот же результат на одном и том же наборе аргументов:



[Test]
public void WriteCharTwice() {
Setup("");
testInst.AssertIO(0, () => Console.Write('A'));
testInst.AssertIO(0, () => Console.Write('A'));
Assert.Throws(() => testInst.AssertIO(0, () => Console.Write('B')));
Assert.AreEqual("A", console.Output);
}


Если вывод не удался по какой-то причине, мы должны получить ошибку в виде эксепшена:



[Test]
public void GetWriteError() {
Setup("");
console.Out.Close();
Assert.Throws(() => testInst.AssertIO(0, () => Console.Write('A')));
Assert.Throws(() => testInst.AssertIO(0, () => Console.Write('B')));
}


Нам также нужен хотя бы один тест на чтение:



[Test]
public void ReadChar() {
Setup("123");
Assert.AreEqual((int)'1', testInst.AssertIO(0, () => Console.Read()));
Assert.AreEqual((int)'2', testInst.AssertIO(1, () => Console.Read()));
Assert.AreEqual((int)'3', testInst.AssertIO(2, () => Console.Read()));
Assert.AreEqual(-1, testInst.AssertIO(3, () => Console.Read()));
}


Чистые функции ввода-вывода на C#. Реализация



Напишем вспомогательный класс, который будет разбирать переданный linq expression и осуществлять реальный вызов указанного метода с указанными параметрами, используя переданный consoleDescriptor:



Код
class IOOperation {
readonly string method;
readonly Type[] argTypes;
readonly object[] args;

public IOOperation(LambdaExpression callExpression) {
var methodExpr = (MethodCallExpression)callExpression.Body;
this.args = methodExpr.Arguments.Select(x => Expression.Lambda>(Expression.Convert(x, typeof(object))).Compile()()).ToArray();
this.method = methodExpr.Method.Name;
this.argTypes = methodExpr.Method.GetParameters().Select(x => x.ParameterType).ToArray();
}

public TResult Do(Func consoleDescriptor) {
return (TResult)consoleDescriptor(method, argTypes, args);
}
}




Для отслеживания одинаковых вызовов (как в тесте WriteCharTwice) удобно перекрыть Equals:



Код
public static bool operator ==(IOOperation a, IOOperation b) {
bool aIsNull = ReferenceEquals(a, null);
bool bIsNull = ReferenceEquals(b, null);
return
aIsNull && bIsNull ||
!aIsNull && !bIsNull &&
string.Equals(a.method, b.method, StringComparison.Ordinal) &&
a.args.Length == b.args.Length &&
!a.args.Zip(b.args, Equals).Where(x => !x).Any();
}
public override int GetHashCode() { return method.GetHashCode() ^ args.Length; }
public static bool operator !=(IOOperation a, IOOperation b) { return !(a == b); }
public override bool Equals(object obj) { return this == obj as IOOperation; }




Теперь мы можем свести два метода AssertIO к одному:



public None AssertIO(int index, Expression ioExpression) {
return AssertIO(index, new IOOperation(ioExpression));
}
public TResult AssertIO(int index, Expression> ioExpression) {
return AssertIO(index, new IOOperation(ioExpression));
}
TResult AssertIO(int index, IOOperation operation);


Осталось реализовать последний метод — и дело сделано.



Ясно, что нам надо как-то кэшировать рузультаты наших обращений к нативной консоли. Добавим необходимые классы и филды:



Код
readonly List completedOperations = new List();

abstract class IOOperationResult { }
sealed class IOOperationResult : IOOperationResult {
readonly TResult returnValue;
readonly Exception exception;

public IOOperationResult(Func getResult) {
try {
returnValue = getResult();
exception = null;
} catch(Exception e) {
returnValue = default(TResult);
exception = e;
}
}

public TResult Result {
get {
if(exception != null)
throw new AggregateException(exception);
return returnValue;
}
}
}
abstract class IOOperationWithResult { }
sealed class IOOperationWithResult : IOOperationWithResult {
public IOOperationWithResult(IOOperation operation, IOOperationResult result) {
Operation = operation;
Result = result;
}
public readonly IOOperation Operation;
public readonly IOOperationResult Result;
}




Проделав предаврительную работу, можно наконец написать собственно AssertIO:



Код
bool rejectOperations = false;

TResult AssertIO(int index, IOOperation operation) {
if(index < 0)
throw new ArgumentOutOfRangeException("index");
if(index < completedOperations.Count) {
var completedOperation = completedOperations[index] as IOOperationWithResult;
if(completedOperation == null || completedOperation.Operation != operation)
throw new ArgumentException("", "operation");
return completedOperation.Result.Result;
}
if(rejectOperations)
throw new ArgumentOutOfRangeException("index");
if(index == completedOperations.Count) {
var completedOperation = new IOOperationWithResult(operation, new IOOperationResult(() => operation.Do(consoleDescriptor)));
completedOperations.Add(completedOperation);
return completedOperation.Result.Result;
}
rejectOperations = true;
throw new ArgumentOutOfRangeException("index");
}




Алгоритм работы простой. Если приходит запрос о результате уже выполненной операции, то мы лезем в кэш и сверяем IOOperation. Если мы имеем полное совпадение, значит действительно был произведён вызов указанного метода с указанными параметрами, и мы возвращаем результат; а если имеется отличие — рейзим экспешен. Далее, если операции в кэше ещё нет и сейчас как раз подходящее время чтобы её выполнить — выполняем операцию, добавляем вместе с полученным результатом в кэш, и возвращаем результат. Если же операции в кэше нет и для её выполнения требуется машина времени — ничего хорошего не выйдет, так что остаётся упасть, взведя предварительно специальный флажок rejectOperations, который обеспечит консистентность поведения метода при дальнейших вызовах.



Такая простая реализация ведёт себя так же, как возможная реализация на чистых функциях, и обеспечивает прохождение написанных (и ненаписанных) тестов.



Монада IO



Теперь, когда у нас есть чистые немонадные функции ввода-вывода, написать монаду IO не представляет никакого труда:



Код
public sealed class IO {
readonly Func> func;

internal IO(Func> func) {
this.func = func;
}
internal RealWorld Execute(RealWorld index, out T result) {
var resultTuple = func(index);
result = resultTuple.Item2;
return resultTuple.Item1;
}
}
class RealWorld {
readonly AppInstance inst;
readonly int index;

public RealWorld(AppInstance inst, int index) {
this.inst = inst;
this.index = index;
}
public Tuple Do(Expression callExpression) {
return Tuple.Create(Yield(), inst.AssertIO(index, callExpression));
}
public Tuple Do(Expression> callExpression) {
return Tuple.Create(Yield(), inst.AssertIO(index, callExpression));
}
public RealWorld Yield() {
return new RealWorld(inst, index + 1);
}
}




Мы даже можем поддержать имеющийся в C# специальный монадный синтаксис (from ... in ... select ...). Для этого кроме наших кастомных методов Return и Do понадобится реализовать методы Select и SelectMany (они должны называться именно так и иметь определённую сигнатуру — работает утиная типизация):



Код
public static class IO {
public static IO Return(T value) {
return new IO(x => Tuple.Create(x, value));
}
public static IO Select(this IO io, Func selector) {
return new IO(x => {
T t;
var index = io.Execute(x, out t);
return Tuple.Create(index, selector(t));
});
}
public static IO SelectMany(this IO io, Func> selector, Func projector) {
return new IO(x => {
T t;
var index = io.Execute(x, out t);
var ioc = selector(t);
C c;
var resultIndex = ioc.Execute(index, out c);
return Tuple.Create(resultIndex, projector(t, c));
});
}
public static IO Do(Expression callExpression) {
return new IO(x => x.Do(callExpression));
}
public static IO Do(Expression> callExpression) {
return new IO(x => x.Do(callExpression));
}
public static IO Handle(this IO io, Func> handler) {
return new IO(x => {
RealWorld rw;
T t;
try {
rw = io.Execute(x, out t);
} catch(Exception e) {
rw = handler(e).Execute(x.Yield(), out t);
}
return Tuple.Create(rw, t);
});
}
}




Кроме упомянутых, мы добавили весьма полезный метод Handle, позволяющий продолжить работу при возникновении эксепшена.



Также понадобится «монадная» точка входа в приложение:



public static class AppInstanceIOExtensions {
public static void DoMain(this AppInstance inst, Func> body) {
None result;
body().Execute(new RealWorld(inst, 0), out result);
}
}


Демонстрация результата



Напишем простейшее консольное приложение, используя монаду IO:



class Program {
static void Main(string[] args) {
AppInstance.Get().DoMain(IOMain);
}
static IO IOMain() {
return
from _ in IO.Do(() => Console.WriteLine("What is your name?"))
from name in IO.Do(() => Console.ReadLine())
let message = "Hi, " + name + "!"
from r in IO.Do(() => Console.WriteLine(message))
select r;
}
}


Результат работы:







Полный код на гитхабе: IOMonad.

Original source: habrahabr.ru.

https://habrahabr.ru/post/282940/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<машина времени - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda