2016-01-06 3 views
2

Я новичок в scala и функциональной программировании. У меня есть вариант реализации протокола связи. Может ли кто-нибудь указать правильный способ обработки контекстов (сохранение, создание, внесение изменений состояния) и данных в функциональном режиме.Управление контекстами и государственным управлением

Например, рассмотрите пример протокола телефонии. Коммутатор получит 1000 вызовов. Следовательно, необходимо сохранить контекст каждого из вызовов, чтобы обрабатывать их правильно. Как я обрабатываю обработку контекста неизменным образом?

Caller ------------------------------ вызываемая сторона [телефонный коммутатор]

placecall ----- -------------> -------- Полученный код

Предупреждение ---------------------- ---------- Предупреждение

Ответ -------------------------------- Ответ

Вводный текст

Отключить -------------------------------- Отключить

Если я выполнял программирование на C, я буду выделять хранилище контекста (может быть хешем) для хранения всех контекстов, и когда я получу вызов, я сделаю контекст выборки из магазина. Если это новый call, я мог бы вернуть null, и мне нужно создать новый контекст, и если это существующий вызов, я получу правильный контекст.

Будет здорово, если бы вы могли дать представление о правильном шаблоне функционального дизайна. Будем ли мы использовать монашескую монаду и монаду? Как мы моделируем хранилище контекста. Как мы обеспечиваем параллелизм в функциональном мире, когда делаем контекстный выбор.

Заранее спасибо ..

С уважением Махеш

+0

Я начал писать ответ, но это поворот в BlogPost :). Я постараюсь закончить, как только смогу ... –

+0

Большое спасибо. С нетерпением жду вашего сообщения. –

+0

Добавлен пример государственной монады. Не уверен, найду ли я время, чтобы написать пример Акки - может быть. Надеюсь, это ясно. –

ответ

2

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

В конце концов будет место, где будут возникать побочные эффекты, если вы используете государственную монаду или какую-либо другую технику.

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

простой аргумент прохождения пример:

object StateExample extends App { 

    //--- start of pure part of your program ---// 
    type PhoneNumber = String 
    type CallState = Map[PhoneNumber, User] 

    case class User(username: String) 

    def startCall(calls: CallState, caller: User, phone: PhoneNumber): CallState = 
    calls + (phone -> caller) 

    def finishCall(calls: CallState, phone: PhoneNumber): CallState = 
    calls - phone 

    def startCallingPeople(calls: CallState) = { 
    val intermediateState1 = startCall(calls, User("one"), "123") 
    val intermediateState2 = startCall(intermediateState1, User("two"), "456") 
    intermediateState2 
    } 

    def hangupCalls(calls: CallState) = { 
    val intermediateState1 = finishCall(calls, "123") 
    val intermediateState2 = finishCall(intermediateState1, "456") 
    intermediateState2 
    } 
    // --- end of pure part of the program ---// 


    // --- start of impure part of your program ---// 
    var callState: CallState = Map() 

    def runSimulation(): Unit = { 
    println(s"BEFORE ANY CALLS: $callState") 
    callState = startCallingPeople(callState) 
    println(s"AFTER CALLING 2 PEOPLE: $callState") 
    callState = hangupCalls(callState) 
    println(s"AFTER HANGING UP: $callState") 
    } 

    runSimulation() 
    // --- end of impure part of your program ---// 
} 

печатает:

BEFORE ANY CALLS: Map() 
AFTER CALLING 2 PEOPLE: Map(123 -> User(one), 456 -> User(two)) 
AFTER HANGING UP: Map() 

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

В качестве альтернативы вы можете использовать государственную монаду в «чистой» части программы и запустить ее в «нечистой» части и сохранить созданное состояние так же, как состояние сохранено в var выше. Ниже приведен пример:

import scalaz.State 

object StateMonadExample extends App { 

    def startCall(caller: User, phone: PhoneNumber): State[CallState, Unit] = 
    State { s => s + (phone -> caller) ->() } 

    def finishCall(phone: PhoneNumber): State[CallState, Unit] = 
    State { s => (s - phone) ->() } 

    def startCallingPeople: State[CallState, Unit] = 
    startCall(User("one"), "123").flatMap(_ => startCall(User("two"), "456")) 

    def hangupCalls: State[CallState, Unit] = 
    finishCall("123").flatMap(_ => finishCall("456")) 

    // mutable part of your program 
    var callState: CallState = Map() 

    // side effecting part of your program 
    def runSimulation(): Unit = { 
    test1() // intermediate state being saved 
    test2() // intermediate state being passed through 
    test3() // same as test 2 but also outputs intermediate state without updating it as test 1 does 
    } 

    def test1(): Unit = { 
    // reset initial state just in case 
    callState = Map() 
    println("TEST 1:") 
    println(s"BEFORE ANY CALLS: $callState") 
    callState = startCallingPeople.run(callState)._1 
    println(s"AFTER CALLING 2 PEOPLE: $callState") 
    callState = hangupCalls.run(callState)._1 
    println(s"AFTER HANGING UP: $callState") 
    println("END OF TEST 1.\n") 
    } 

    def test2(): Unit = { 
    // reset initial state just in case 
    callState = Map() 
    println("TEST 2:") 
    println(s"BEFORE ANY CALLS: $callState") 
    val computation = for { 
     _ <- startCallingPeople 
     _ <- hangupCalls 
    } yield() 
    callState = computation.run(callState)._1 
    println(s"AFTER CALL AND HANGUP: $callState") 
    println("END OF TEST 2.\n") 
    } 

    def test3(): Unit = { 
    // reset initial state just in case 
    callState = Map() 
    println("TEST 3:") 
    println(s"BEFORE ANY CALLS: $callState") 
    val computation = for { 
     s1 <- startCallingPeople.flatMap(_ => State { s: CallState => println(s"AFTER CALLING 2 PEOPLE: $s"); s ->() }) 
     _ <- hangupCalls 
    } yield() 
    callState = computation.run(callState)._1 
    println(s"AFTER HANGING UP: $callState") 
    println("END OF TEST 3.\n") 
    } 

    runSimulation() 
} 

печатает:

TEST 1: 
BEFORE ANY CALLS: Map() 
AFTER CALLING 2 PEOPLE: Map(123 -> User(one), 456 -> User(two)) 
AFTER HANGING UP: Map() 
END OF TEST 1. 

TEST 2: 
BEFORE ANY CALLS: Map() 
AFTER CALL AND HANGUP: Map() 
END OF TEST 2. 

TEST 3: 
BEFORE ANY CALLS: Map() 
AFTER CALLING 2 PEOPLE: Map(123 -> User(one), 456 -> User(two)) 
AFTER HANGING UP: Map() 
END OF TEST 3. 

Обратите внимание, что State монады заботится о прохождении через состояние. По существу, мы просто собираем кучу вычислений, а затем выполняем их, вызывая run и передавая начальное состояние.

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

Практический подход состоит в том, чтобы либо сохранить состояние извне в некоторой базе данных (управляет синхронизацией для вас), либо как-то избежать синхронизации. Если бы мне пришлось сохранить состояние в памяти, я бы, вероятно, использовал Akka и представлял каждый активный звонок в качестве актера. Актеры могут безопасно инкапсулировать изменяемое состояние, потому что они обрабатывают каждое сообщение последовательно. Когда звонок закончен, я убью актера, чтобы освободить ресурсы. Вы можете разделить ваше приложение по-другому - возможно, вместо того, чтобы иметь одного актера за звонок, у вас может быть один актер на один коммутатор. Это действительно зависит от требований. Обратите внимание, что актеры принимают изменчивость, поэтому это не чистое решение FP.

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

Посмотреть полный проект здесь: https://github.com/izmailoff/scala-state-example

+0

Я приведу примеры для государственной монады и акки здесь позже: https://github.com/izmailoff/scala-state-example. Еще не успел. –

+0

Спасибо большое .... –

+0

Я попытаюсь обновить этот ответ с большим количеством примеров в предстоящие дни;) –