Уявлення log in та log out. Паттерн Model-Update-View та залежні типи Набір logout html view

Django працює з безліччю будівельних ресурсів для найбільш загального використання випадків з Web application. Registration app є дуже хорошим прикладом і добре, що про це є, що можуть бути використані з-за box.

З Django registration app ви можете отримати додатки з наступних нюансів:

  • Login
  • Logout
  • Sign up
  • Password reset

У цьому розділі будуть фокусуватися в Login and Logout features. Для sign up and password reset, check the tutorials below:

Getting started

Після того, як ви почнете, ви можете виявити django.contrib.auth у вашому INSTALLED_APPS і authentication middleware належним чином configured в MIDDLEWARE_CLASSES settings.

Both come already configured when you start new Django project using the command startproject . Якщо ви не збираєтеся перейти до початкових налаштувань, ви повинні бути всі налаштування.

У випадку ви стаєте на новий проект тільки для цього літератури, створіть користувача, використовуючи лінію спілкування тільки для того, щоб виконати тестування і логотипи сторінок.

$ python manage.py createsuperuser

У кінці цього article I will provide the source code of the example with the minimal configuration.

Configure the URL routes

Перший import django.contrib.auth.views module and add URL route for login and logout views:

від django.conf.urls import url від django.contrib import admin від django.contrib.auth import views as auth_views urlpatterns = [ url (r"^login/$" , auth_views . login , name = "login" ), url r"^logout/$" , auth_views .

Create a login template

Будь-який, django.contrib.auth.views.login view буде спробувати render the registration/login.html template. З basic configuration would be creating a folder named registration and place a login.html template inside.

Following minimal login template:

(% extends "base.html" %) (% block title %)Login(% endblock %) (% block content %)

Login

(% csrf_token %) (( form.as_p ))
(% endblock %)

Цей simple example already validates username and password and authenticate correctly the user.

Customizing the login view

Там є кілька параметрів, які можуть пройти до Login View для того, щоб зробити його власний проект. Для прикладу, якщо ви хочете, щоб ваш login template деякий час, коли ви зареєструєтеся/login.html Ви можете pass template name як параметр:

url (r"^login/$" , auth_views . login , ( "template_name" : "core/login.html" ), name = "login" ),

Ви можете pass a custom authentication form using the parameter authentication_form , incase you have implemented custom user model.

Новий, дуже важлива configuration is done in the settings.py file, which is the URL Django буде redirect the user after a successful authentication.

Всередині settings.py file add:

LOGIN_REDIRECT_URL = "home"

Value може бути hardcoded URL або URL name. Зменшення значення для LOGIN_REDIRECT_URL є /accounts/profile/ .

Це також важливо, щоб помітити, що Django буде намагатися відредагувати користувача до іншого GET param.

Setting up logout view

Після отримання django.contrib.auth.views.logout view, Django буде render the registration/logged_out.html template. У такій мірі, як ми бачимо, в Login View, Ви можете пройти в різних template як:

url (r"^logout/$" , auth_views . logout , ( "template_name" : "logged_out.html" ), name = "logout" ),

Зазвичай я можу скористатися параметром next_page і об'єднувати їх до homepage з вашим проектом або до Login page, коли він використовується.

Це приклади демонструють, як автоматично logout with default Spring security configuration.

Для logout, нам потрібно використовувати URL "/logout" with POST request.

У POST /logout form, будуть також потрібні для включення CSRF керування, який є захист проти CSRF attack .

Let's see the example how to do that.

Java Config class

@Configuration @EnableWebSecurity @EnableWebMvc @ComponentScan public class AppConfig extends WebSecurityConfigurerAdapter (protected void configure(HttpSecurity http) throws Exception ( http.authorizeRequests() .anyRequest().authenticated() .and() void configure(AuthenticationManagerBuilder builder) throws Exception ( builder.inMemoryAuthentication() .withUser("joe") .password("123") .roles("ADMIN"); ) @Bean public ViewResolver viewResolver() (); viewResolver.setPrefix("/WEB-INF/views/");

Зверніть увагу на те, що в наведеному налаштуванні, є або заздалегідь configure (HttpSecurity http), щоб зменшити Basic Authentication (відповідь про орієнтовний метод в WebSecurityConfigurerAdapter source code) і за допомогою форми, заснованої на Authentication. Ви повинні зробити, щоб browsers cache the Basic Authentication information aggressively (після першого успішного Login) і не є способом, щоб logout the user in the current session. У більшості випадків, ми не повинні використовувати Basic Authentication mechanism.

A controller

@Controller public class ExampleController ( @RequestMapping("/") public String handleRequest2(ModelMap map) ( map.addAttribute("time", LocalDateTime.now().toString()); return "my-page"; ) )

The JSP page

src/main/webapp/WEB-INF/views/my-page.jsp

Spring Security Example

Time: $(time)

Для того, щоб зробити приклади, виконані текатом (configured in pom.xml of example project below):

Mvn tomcat7: run-war

Output

Initial access to URI "/" will redirect to "/login":

Після того, як миттєвий user name і password є налаштуванням у нашому AppConfig class:

Clicking on "Logout" button:


Example Project

Dependencies and Technologies Used:

  • spring-security-web 4.2.3.RELEASE: spring-security-web.
  • spring-security-config 4.2.3.RELEASE: spring-security-config.
  • spring-webmvc 4.3.9.RELEASE: Spring Web MVC.
  • javax.servlet-api 3.1.0 Java Servlet API
  • JDK 1.8
  • Maven 3.3.9

Відредагуйте файл urls.pyпрограми account:

from django.conf.urls import url from . import views urlpatterns = [ # previous login view # url(r"^login/$", views.user_login, name="login"),# login / logout urls url(r"^login/$" , "django.contrib.auth.views.login", name="login" ), url(r"^logout/$" , "django.contrib.auth.views.logout", name="logout" ), url(r"^logout-then-login/$" , "django.contrib.auth.views.logout_then_login", name="logout_then_login" ), ]

Ми закоментували шаблон URL-адреси для представлення user_login, створеного раніше для використання уявлення loginДжанґо.

Створіть новий каталог у каталозі шаблонів програми accountі назвіть його registration.Створіть новий файл у новому каталозі, назвіть його login.html

(% extends "base.html" %) (% block title %)Log-in(% endblock %) (% block content %)

Log-in

(% if form.errors %)

Your username і password didn"t match. Please try again.

(% else %)

Please, використовуючи наступні форми для log-in:

(% endif%) (% endblock %)

Цей шаблон для входу дуже схожий на раніше створений. Джанго використовує AuthenticationForm, розташовану в django.contrib.auth.forms. Ця форма намагається перевірити справжність користувача і породжує помилку перевірки, якщо ім'я користувача було неправильним. У цьому випадку ми можемо шукати помилки за допомогою команди (% if form.errors%). Зверніть увагу, що ми додали прихований елемент для надсилання значення змінної з ім'ям next.

Параметр nextмає бути URL-адресою. Якщо цей параметр вказано, після входу користувача в систему він перенаправляється на задану URL-адресу.

Тепер створіть шаблон logged_out.htmlусередині каталогу шаблону registrationі вставте в нього наступний код:

(% extends "base.html" %) (% block title %)Logged out(% endblock %) (% block content %)

Logged out

You have been успішно logged out. Ви можете log-in again.

(% endblock %)

Це шаблон, який відображатиметься після входу користувача до системи.

Після додавання шаблонів URL-адрес та шаблонів для вхідних та вихідних уявлень сайт готовий до входу в систему з використанням уявлень про аутентифікацію Джанго.

Зверніть увагу, що уявлення logout_then_login, включене до нашого urlconf, не потребує шаблону, оскільки він виконує перенаправлення на log in view.

Тепер створимо новий view для відображення панелі моніторингу для користувача, щоб знати, коли користувач увійде до свого облікового запису. Відкрийте файл views.pyпрограми accountі додайте до нього наступний код:

from django.contrib.auth.decorators import login_required @login_required def dashboard (request) : return render(request, "account/dashboard.html" , ("section" : "dashboard" ))

Ми додаємо в нашу виставу декоратор login_required authentication framework. Декоратор login_requiredперевіряє, чи поточний користувач пройшов автентифікацію. Якщо користувач пройшов аутентифікацію, рпедставлення виконається; Якщо користувач не пройшов аутентифікацію, він буде перенаправлений на сторінку входу.

Ми також визначили змінну section. Ми збираємося використовувати цю змінну для відстеження розділу сайту, за яким спостерігає користувач.

Тепер потрібно створити шаблон для представлення панелі моніторингу. Створіть новий файл усередині шаблонів/обліковий templates/account/і назвіть його dashboard.html :

(% extends "base.html" %) (% block title %)Dashboard(% endblock %) (% block content %)

Dashboard

Welcome to your dashboard.

(% endblock %)

Потім додайте наступний шаблон URL-адреси для цього, щоб змінити файл urls.pyпрограми account:

Urlpatterns = [ # ... url(r"^$" , views.dashboard, name="dashboard" ), ]

Тепер відредагуйте файл settings.py:

від django.core.urlresolvers import reverse_lazy LOGIN_REDIRECT_URL = reverse_lazy("dashboard" ) LOGIN_URL = reverse_lazy("login" ) LOGOUT_URL = reverse_lazy("logout" )
  • LOGIN_REDIRECT_URL: Повідомляє, на яку URL-адресу перенаправляти користувача після входу в систему.
  • LOGIN_URL: URL-адреса для перенаправлення користувача на вхід (наприклад, за допомогою декоратора login_required)
  • LOGOUT_URL: URL-адреса для перенаправлення користувача на вихід

Тепер ми збираємось додати до нашого базового шаблону посилання на вхід та вихід із сайту.

Для цього необхідно визначити, чи увійшов поточний користувач у систему чи ні, щоб відобразити відповідне поточний стан користувача посилання. Поточний користувач задається в HttpRequestоб'єкт проміжного класу authentication. Доступ до нього можна отримати за допомогою request.user. У запиті буде знайдено об'єкт user навіть якщо user не пройшов аутентифікацію. Неавтентифікований користувач задається в запиті як примірник AnonymousUser. Найкращий спосіб перевірки стану автентифікації поточного користувача - виклик request.user.is_authenticated()

Відредагуйте у шаблоні base.html

з ID header:

Як можна бачити, меню сайту відображається лише для користувачів, які пройшли аутентифікацію. Ми також перевіряємо поточний розділ, щоб додати обраний атрибут класу до відповідного елемента

  • , щоб виділити поточний розділ у меню за допомогою CSS. Також відображається ім'я користувача та посилання для виходу в систему, якщо користувач пройшов автентифікацію, або посилання для входу до системи.

    Відкрийте у браузері http://127.0.0.1:8000/account/login/. Ви повинні побачити стріницю входу. Введіть валідний логін та пароль. Ви побачите таке:

    Можна побачити, що розділ My dashboard виділено за допомогою CSS, тому що він має class selected. Оскільки користувач пройшов аутентифікацію, ім'я користувача відображається у правій частині header-а. Клацніть на посилання Logout. Ви побачите наступну сторінку:

    На цій сторінці можна побачити, що користувач вийшов із системи, і тому більше не відображається меню веб-сайту. Посилання праворуч хедера показує тепер Log-in.

    Якщо ви бачите сторінку log out із сайту адміністрування Джанго, а не власну сторінку виходу з системи, перевірте налаштування INSTALLED_APPS та переконайтеся, що django.contrib.adminзнаходиться після account. Обидва шаблони знаходяться в тому ж відносному шляху, і завантажувач шаблону Джанго використовуватиме перший знайдений.

    В основному для розробки інтерфейсів користувача. Щоб ним скористатися треба створити тип Model, що представляє повний стан програми, тип Message, що описує події зовнішнього середовища, на які програма повинна реагувати, змінюючи свій стан, функцію updater, яка зі старого стану та повідомлення створює новий стан прорами та функції view, яка обчислює за станом програми необхідні впливу на зовнішнє середовище, які породжують події типу Message. Паттерн дуже зручний, але в нього є маленький недолік - він не дозволяє описати, які події мають сенс для конкретних станів програми.

    Така проблема виникає (і вирішується) і з використанням ОО-патерна State.

    Мова Elm проста, але дуже строга - він перевіряє, що функція updater хоч якось обробляє всі можливі поєднання моделі-стан та повідомлення-події. Тому доводиться писати зайвий, нехай і тривіальний - як правило, що залишає модель без змін, код. Я хочу продемонструвати, як цього можна уникнути у складніших мовах - Idris, Scala, C++ та Haskell.

    Весь наведений код доступний на GitHub для експериментів. Розглянемо найцікавіші місця.


    Функція msg незвичайна – вона повертає не значення, а тип. Під час виконання про типи значень нічого не відомо - компілятор виконує стирання всієї зайвої інформації. Тобто така функція може бути викликана лише на етапі компіляції.

    MUV – це конструктор. Він приймає параметри: model – початковий стан програми, updater – функція оновлення стану при зовнішній події, та view – функція створення зовнішнього уявлення. Зауважте, що тип функцій updater і view залежить від значення моделі (за допомогою функції msg з параметрів типу).

    Тепер подивимося, як цю програму запустити

    MuvRun: (Application modelType msgType IO) -> IO a muvRun (MUV model updater view) = do msg<- view model muvRun (MUV (updater model msg) updater view)
    Як зовнішнє уявлення (view) ми вибрали операцію вводу/вывода (в Idris, як й у Haskell, операції вводу/вывода - first class values, що вони виконалися треба зробити додаткові дії, зазвичай повернути таку операцію з функції main).

    Коротко про IO

    При виконанні операції типу (IO a) відбувається деякий вплив на зовнішній світ, можливо порожнє, і програма повертається значення типу a, але функції стандартної бібліотеки влаштовані так, що обробити його можна тільки породжуючи нове значення типу IO b. Таким чином, чисті функції відокремлені від функцій з побічними ефектами. Це незвично багатьом програмістам, але допомагає писати надійніший код.


    Так як функція muvRun породжує введення/виведення, вона повинна повернути IO, але так як вона ніколи не завершиться, тип операції може бути будь-який - IO a.

    Тепер опишемо типи сутностей, з якими ми збираємося працювати

    Data Model = Logouted | Logined String data MsgOuted = Logined String data MsgIned = Logout | Great total msgType: Model -> Type msgType Logouted = MsgOuted msgType (Logined _) = MsgIned
    Тут описаний тип моделі, що відображає наявність двох станів інтерфейсу - користувач не залогінений, і користувач має назву типу String.

    Далі ми описуємо два різнихтипів повідомлень, релевантрих для різних варіантів моделі - якщо ми розлогінені, то ми можемо лише залогінитись під деяким ім'ям, а якщо вже залогінені, то можемо або розлогитися, або привітатись. Idris – строго типізована мова, яка не допустить можливості переплутати різні типи.

    І, нарешті, функція, що задає відповідність значення моделі типу повідомлення.

    Функція оголошена тотальною - тобто вона не повинна впасти або зависнути, компілятор постарається за цим простежити. msgType викликається на етапі компіляції, а значить її тотальність означає, що компіляція не зависне через нашу помилку, хоча і не може гарантувати, що виконання цієї функції призведе до вичерпання ресурсів системи.
    Також гарантовано, що вона не виконає "rm -rf /", тому що в її сигнатурі немає IO.

    Опишемо updater:

    Total updater: (m:Model) -> (msgType m) -> Model updater Logouted (Login name) = Logined name updater (Logined name) Logout = Logouted updater (Logined name) Greet = Logined name
    Думаю, логіка цієї функції зрозуміла. Хочу ще раз наголосити на тотальності - вона означає що компілятор Idris перевірить, що ми розглянули всі дозволені системою типи альтернативи. Elm теж здійснює таку перевірку, але він не може знати, що ми не можемо розлогитися, якщо ще не залогінені, і вимагатиме явну обробку умови

    Updater Logouted Logout = ???
    Idris ж зайвої перевірки знайде невідповідність типів.

    Тепер приступимо до view - як завжди в UI це буде найскладнішою частиною коду.

    Total loginPage: IO MsgOuted loginPage = do putStr "Login: " map Login getLine genMsg: String -> MsgIned genMsg "" = Logout genMsg _ = Great total workPage: String -> IO MsgIned workPage name = do ++ name ++ "\n") putStr "Input empty string for logout or nonempty for greeting\n" map genMsg getLine view: (m: Model) -> IO (msgType m) view ) = workPage name
    view повинна створювати операцію вводу/виводу, яка повертає повідомлення, тип якого залежить від значення моделі. У нас є два варіанти: loginPage, який виводить повідомлення «Login:», читає рядок з клавіатури та загортає її у повідомлення Login та workPage з параметром ім'ям користувача, який виводить привітання та повертає різні повідомлення (але однакового типу – MsgIned) залежно від того, введе користувач порожній чи не порожній рядок. view повертає одну з цих операцій залежно від значення моделі, і компілятор перевіряє їхній тип, незважаючи на те, що він різний.

    Тепер ми можемо створити та запустити наш додаток

    App: Application Model Main.msgType IO app = MUV Logo updater view main:
    Тут треба відзначити тонкий момент – функція muvRun повертає IO aде a не було специфіковано, а значення main має тип IO(), де () - це ім'я типу, зазвичай званого Unit, у якого є єдине значення, що теж записується як порожній тупл () . Але компілятор із цим легко справляється. підставивши замість a().

    Scala та залежні від шляху типи

    У Scala немає повноцінної підтримки залежних типів, але є типи, залежні від екземпляра об'єкта, через який на нього посилаються (path dependent types). Теоретично залежних типів їх можна описати як варіант сигма-типу. Типи, що залежать від шляху, дозволяють заборонити складати вектори з різних векторних просторів, або описати кому з ким можна цілуватися. Але ми їх застосуємо для більш простих завдань.

    Sealed abstract class MsgLogoed case class Login(name: String) extends MsgLogoed sealed abstract class MsgLogined case class Logout() extends def view() : View ) case class Logouted() extends Model ( type Message = Msg ) : View .... )
    Алгебраїчні типи Scala моделюються через успадкування. Типу відповідає деякий sealed abstract class, а кожному конструктору успадкований від нього case class. Ми намагатимемося їх використовувати саме як алгебраїчні типи, описуючи всі змінні як такі, що належать до батьківського sealed abstract class.

    Класи MsgLogined та MsgLogouted у рамках нашої програми не мають спільного предка. Функцію view довелося розмазати за різними класами моделі, щоб мати доступ до конкретного типу повідомлень. У цьому є свої плюси, які оцінять прихильники ГО – код виходить згрупований у відповідності до бізнес-логіки, все що пов'язано з одним use case виявляється поруч. Але мені більше сподобалося б виділити view в окрему функцію, розробку якої можна було б передати іншій людині.

    Тепер реалізуємо updater

    Object Updater ( def update(model: Model)(msg: model.Message) : Model = ( model match ( case Logouted() => msg match ( case Login(name) => Logined(name) ) case Logined(name) => msg match ( case Logout() => Logouted() case Greet() => model ) ) ) )
    Тут ми, використовуючи залежні від шляху типи, описуємо тип другого аргументу значення першого. Щоб Scala сприймала подібні залежності, функції доводиться описувати в карбованному вигляді, тобто у вигляді функції від першого аргументу, яка повертає функцію від другого аргументу. На жаль, Scala тут не здійснює багатьох перевірок типів, для яких у компілятора достатньо інформації.

    Тепер дамо повну реалізацію моделі та view

    Case class Logouted() extends Model ( type Message = Msg (name) ) ) ) case class Logined(name: String) extends Model ( type Message = Msg ("Empty string for logout, nonempy for greeting.") scala.io.StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) ) abstract class View ( def run( ) : Msg ) object Viewer ( def view(model: Model): View = ( model.view() ) )
    Тип повертається функцією view залежить від екземпляра її аргументу. Але за реалізацією вона звертається до моделі.

    Запускається створене так додаток так

    Object Main ( import scala.annotation.tailrec @tailrec def process(m: Model) ( val msg = Viewer.view(m).run() process(Updater.update(m)(msg)) ) Array) = ( process(Logouted()) ) )
    Код runtime-системи, таким чином, ні чого не знає про внутрішній пристрій моделей і типи повідомлень, але компілятор може перевірити, що повідомлення підходить до поточної моделі.

    Тут нам знадобилися не всі можливості, що надаються залежними від шляху типами. Цікаві властивості виявляться, якщо ми паралельно працюватимемо з кількома екземплярами систем Model-Updater-View, наприклад при симуляції багатоагентного світу (view тоді б являв собою вплив агента на світ і отримання зворотного зв'язку). У цьому випадку компілятор перевіряв, що повідомлення обробляється саме тим агентом, якому призначено, незважаючи на те, що всі агенти мають однаковий тип.

    С++

    С++ досі чутливий до порядку визначень, навіть якщо вони зроблені в одному файлі. Це створює деякі незручності. Я наводитиму код у зручній для демонстрації ідей послідовності. Впорядковану для компілюваності версію можна подивитися на GitHub.

    Алгебраїчні типи можуть бути реалізовані так само, як у Scala - абстрактний клас відповідає типу, а конкретні спадкоємці - конструкторам (назвемо їх «класами-конструкторами», щоб не плутати зі звичайними конструкторами C++) алгебраїчного типу.

    У C++ є підтримка залежних від шляху типів, але компілятор не може використовувати цей тип абстрактно, не знаючи реального типу, з яким він пов'язаний. Тому реалізувати Model-Updater-View з їх допомогою не виходить.

    Але C++ має потужну систему шаблонів. Залежність типу від значення моделі можна заховати в шаблонний параметр спеціалізованої версії виконавчої системи.

    Struct Processor ( virtual const Processor * next () const = 0; ); template struct ProcessorImpl: public Processor (const CurModel * model; ProcessorImpl (const CurModel * m): model (m) (); const Processor *next() const ( const View
    Ми описуємо абстрактну виконавчу систему, з єдиним методом – виконати все, що потрібно, та повернути нову виконавчу систему, що підходить для наступної ітерації. Конкрена версія має шаблонний параметр і буде спеціалізована для кожного класу-конструктора моделі. Тут важливо, що всі властивості типу CurModel будуть перевірені під час спеціалізації шаблону конкретним параметром-типом, а на момент компіляції шаблона їх описувати не потрібно (хоча і можливо за допомогою концептів або інших способів реалізації класів типів). Scala теж має досить сильну систему параметризованих типів, але перевірки властивостей типів-параметрів вона здійснює під час компіляції параметризованого типу. Там реалізація такого патерну утруднена, але можлива, завдяки підтримці класів типів.

    Опишемо модель.

    Struct Model ( virtual ~Model() (); virtual const Processor *processor() const = 0; ); struct Logined: public Model ( struct Message ( const virtual Model * process (const Logined * m) const = 0; virtual ~Message() (); ); struct Logout: public Message ( const Model * process (const Logined * m) const;); struct Greet: public Message public View (...); const View * view() const ( return new LoginedView(name); ); (...); const Processor *processor() const ( return new ProcessorImpl (this);
    ); ); struct Logouted: public Model ( struct Message ( const virtual Model * process(const Logouted * m) const = 0; virtual ~Message() (); ); struct Login: public Message ( const std::string name; Login(std ::string lname) : name(lname) ( ); const Model * process(const Logouted * m) const;

    * view() const ( return new LogoutedView(); );

    Const Model * Logouted::Login::process(const Logouted * m) const ( delete m; return new Logined(name); ); const Model * Logined::Logout::process(const Logined * m) const (delete m; return new Logouted(); ); const Model * Logined::Greet::process(const Logined * m) const ( return m; );
    Тепер зберемо разом все, що стосується view, включаючи внутрішні сутності моделей

    Template struct View ( virtual const Message * run() const = 0; virtual ~View () (); ); struct Logined: public Model ( struct LoginedView: public View ( const std::string name; LoginedView(std::string lname) : name(lname) (); virtual const Message * run() const ( char buf; printf("Hello %s", name.c_str()) ; fgets(buf, 15, stdin); (new Logout()) : static_cast (New Greet); ); ); const View
    * view() const ( return new LoginedView(name); ); ); struct Logouted: public Model ( struct LogoutedView: public View

    ( virtual const Message * run() const ( char buf; printf("Login: "); fgets(buf, 15, stdin); return new Login(buf); ); ); const View

    * view() const ( return new LogoutedView(); ); );

    І, нарешті, напишемо main

    Int main(int argc, char ** argv) ( const Processor * p = new ProcessorImpl

    (New Logouted());


    while(true) (const Processor * pnew = p->next (); delete p; p = pnew; ) return 0; )

    Class ProcessorImpl(model: M)(implicit updater: (M, Message) => Model, view: M => View) extends Processor ( def next(): Processor = ( val v = view(model) val msg = v.) run() val newModel = updater(model,msg) newModel.processor() ) )
    Тут бачимо нові таємничі параметри (implicit updater: (M, Message) => Model, view: M => View). Ключове слово implicit означає, що компілятор при викликі цієї функції (точніше конструктора класу) буде шукати в контексті позначені як implicit об'єкти відповідних типів і передавати їх як відповідні параметри. Це досить складна концепція, одне їх застосування якої - реалізація класів типів. Тут вони обіцяють компілятору, що для конкретних реалізацій моделі та повідомлення всі необхідні функції нами будуть надані. Тепер виконаємо цю обіцянку.

    Object updaters ( implicit def logoutedUpdater(model: Logouted, msg: LogoutedMessage): Model = ( (model, msg) match ( case (Logouted(), Login(name)) => Logined(name) ) ) implicit def viewLogouted(model : Logouted) = новий перегляд ( override def run() : LogoutedMessage = ( println("Enter name ") val name = scala.io.StdIn.readLine() Login(name) ) ) implicit def loginedUpdater(model: Logined, msg : LoginedMessage): Model = ( (model, msg) match ( case (Logined(name), Logout()) => Logouted() case (Logined(name), Greet()) => model ) ) implicit def viewLogined( model: Logined) = новий перегляд ( val name = model.name override def run() : LoginedMessage = ( println(s"Hello, $name") println("Empty string for logout, nonempy for greeting.") scala.io .StdIn.readLine() match ( case "" => Logout() case _ => Greet() ) ) ) ) import updaters._

    Haskell

    У мейнстрімовому Haskell немає залежних типів. У ньому також немає успадкування, яке ми істотно застосовували при реалізації патерну в Scala і C++. Але однорівневе успадкування (з елементами залежних типів) може бути змодельовано за допомогою більш-менш стандартних розширень мови -TypeFamilies та ExistentialQuantification. Для загального інтерфейсу дочірніх ООП-класів заводиться клас типів, у якому є залежний «сімейний» тип, самі дочірні класи видаються окремим типом, а потім завертаються в «екзистенційний» тип з єдиним конструктором.

    Data Model = forall m. (Updatable m, Viewable m) => Model m class Updatable m where data Message m:: * update:: m -> (Message m) -> Model class (Updatable m) => Viewable m where view:: m -> (View (Message m)) data Logouted = Logouted data Logined = Logined String
    Я спробував рознести updater і view якнайдалі, тому створив два різних класи типів, але поки це погано вийшло.

    Реалізація updater проста

    Instance Updatable Loguted where data Message Logouted = Login String update Logouted (Login name) = Model (Logined name) instance Updatable Logined where data Message Logined = Logout | Greeting update m Logout = Model Logoeted update m Greeting = Model m
    Як View довелося зафіксувати IO. Спроби зробити його абстрактнішим все ускладнювали і підвищували зв'язаність коду - тип Model повинен знати, який саме View ми збираємося використовувати.

    Import System.IO type View a = IO a instance Viewable Logouted where view Logouted = do putStr "Login: " !\n" hFlush stdout l<- getLine pure $ if l == "" then Logout else Greeting
    Ну і виконуване середовище мало відрізняється від аналогічного в Idris

    RunMUV :: Model -> IO a runMUV (Model m) = do msg<- view m runMUV $ update m msg main:: IO () main = runMUV (Model Logouted)

  • mob_info