[HN Gopher] Build a real-time Twitter clone with LiveView and Ph... ___________________________________________________________________ Build a real-time Twitter clone with LiveView and Phoenix 1.5 Author : chrismccord Score : 345 points Date : 2020-04-22 16:29 UTC (6 hours ago) (HTM) web link (www.phoenixframework.org) (TXT) w3m dump (www.phoenixframework.org) | chriszhu12 wrote: | I've been following this project for 5 years now, and I've really | hoped it would take off. However, part of what made (and to some | extent, still makes) the Rails framework so great is the | surrounding ecosystem of well supported gems that makes building | tedious things a breeze. | | I built a few things in Phoenix in the past, and while the core | framework is very simple and elegant to use, I recall having to | write my own file uploading library because the one that existed | had almost no documentation. This made me really hesitant to | adopt Elixir / Phoenix stack for any projects I'm serious about. | | I was hoping there was a world where most of the old rails | community moved onto phoenix, but in the time this project has | been around, it seems the backend web stack has really fragmented | into a multitude of Node, Go, python, etc, while Phoenix hasn't | really grown its mindshare. | | For former Rails / Django devs who has made the switch: what are | your thoughts on this? | innocentoldguy wrote: | In my experience, Elixir and Phoenix are both so straight | forward you don't really need to rely on third-party libraries | as much as you do in Rails. Some of the things you'd normally | reach for a gem to accomplish is just built into Elixir itself | (e.g., you don't need anything like Resque or Sidekiq because | Elixir processes can do all of that and more for you). | | The lack of magic is nice too. For example, it's easy to add a | new custom Plug (that you wrote yourself) to your pipeline | without resorting to third-party libraries to help you navigate | all the magic and boilerplate you'd otherwise have to deal in | other frameworks. | gremlinsinc wrote: | Coming from laravel, I've not used phoenix much, but often I | find i'll need what a package 'brings' but different, or | perhaps I need a package that's not updated for the latest | laravel. Often if it's MIT I'll take the code and just put it | in a service, modify it so it works w/ latest version, etc... | | you could do the same potentially for any rails or laravel | package you wanted for phoenix, obviously language constraints | would be different and programming paradigm. It'd take a little | bit but you could port things over, if you package it up - even | contribute to the community growth. | | I think choosing a framework solely because of available | packages isn't a good thing. I mean nodejs probably has the | most packages in it's ecosystem but a lot of them are pure | shit. | ithrow wrote: | This happened to me with Clojure. You really get to appreciate | languages with big ecosystems when working on niche languages. | It's sad because these languages are more enjoyable to work | with IMO. Thy are better cause they had the advantage of being | created much later than the mainstream ones but that also means | they had hard competition. | jtth wrote: | Seriously. I would love to work at this level of abstraction | in Clojure, but to do something that is trivial in Rails with | something like Devise is a chore in Clojure, and you have to | put it all together yourself. Of course this makes it | excellent for learning, but not so great for deadlines. More | and more I think I might've made a mistake trying to do basic | CRUD web stuff in Clojure for my current project. | rubyn00bie wrote: | Oh, holy crap, I'm stoked, as someone who has just started using | LiveView after like too many years of single page apps... all I | can say, is "oh fuck, did we get it wrong." The experience is | truly great. Also, LiveComponents are pretty awesome since they | don't have their own process, just mount in existing LiveView, it | makes 'em really easy to reuse and performant (at least from my | experience so far). | | Writing UI code, in a language/environment that has first class | support of pattern matching is like a dream for me. Seriously, | pattern matching is sooo good when you don't have fucking classes | and objects and bullshit "abstractions" in the way of your data. | | Also, if you took a stab at it before 1.5, try it again. This | release has all the goodies to make it nice and documentation to | go with it... I'm stoked to delete the phx.live generators I | copy-pasta'd into my repo from phx master a couple weeks ago | trying to get my bearings. | | Truly awesome work on this! Thank you so much. | | Also, pro-tip, use the generators at least once, even if you have | already used live view without 'em, because now there's more or | less an official pattern for how to organize yer live code and it | really helps. | czbond wrote: | I feel your pain on SPA's.... I feel like code velocity drops | on small teams. | | What about LiveView/Phoenix made the process more enjoyable - | let's call it 'less painful'? | dangerboysteve wrote: | The video is hilarious in a good way. You can hear the disbelief | and amazement in Chris McCord's voice when Liveview works as he | (and team) developed it. | dyren wrote: | Disbelief might be a stretch, but he is clearly extremely | excited about the recent advancements and proud of what the | team has been able to accomplish. | | I agree that it's fun to witness that level of passion and it | makes me happy that Chris is so amped about this. | darepublic wrote: | Looks interesting but I am not interested in making timelines .. | is this Phoenix framework general enough to make arbitrary real | time web apps? | rupurt wrote: | Happy LiveView user here :) Can confirm it can be used in many | scenarios. One of my favorites is streaming realtime data to a | chart. I serialize my data as JSON in the LiveView template and | read it back in from a Hook. Saves me time by not having to | create a separate API endpoint. e.g. <div | phx-hook="MyChart" data-days="<%= | Jason.encode!(days(@balances)) %>" data-amounts="<%= | Jason.encode!(amounts(@balances)) %>" > <div phx- | update="ignore"> </div> </div> | cultofmetatron wrote: | I'm currently using it to make a restaurant POS. It's very | flexible. Everything composes like lego pieces in a very clean | and straightforward way with surprisingly little magic. | zem wrote: | i'm currently using it to make a board game where updates are | pushed live to everyone. it's the most pleasant experience i've | ever had writing a webapp. | rglover wrote: | Yes, think of it like Rails for the Elixir language. | | Phoenix -> Elixir | | Rails -> Ruby | chrismccord wrote: | Absolutely. We have extremely varied usage in the wild. From | sports/content websites with tens of millions of users | (bleacher report, cars.com), to folks running social/chat | services with millions of persistent connections, to folks | running Phoenix on embedded systems. And as you would expect, | there's a ton of "boring business app / JSON API" usage in | there as well, so we have a good story about generalized use. | The goal of the screencast, as I outlay in the beginning is to | show how easy LiveView makes it to build interactive and real- | time applications, and a social timeline was a decent way to do | that -\\_(tsu)_/- | [deleted] | nathan_long wrote: | You can see some other examples of LiveView apps at | https://phoenixphrenzy.com/results | cvaidya1986 wrote: | This seems ideal for a social network idea I am cooking up. Is | there a tutorial or course I can purchase like beginner to all | the way to deployment on Digital Ocean or something like that ? | fenwick67 wrote: | FYI, Pleroma is a Mastodon clone written in Elixir with | Phoenix, might be good to check it out. | | https://git.pleroma.social/pleroma/pleroma | rozap wrote: | I wrote a little app recently to test out live-view. It was my | first time really working with it, though I've been writing | elixir for years. https://gif.energy | | Didn't really have any issues. There were maybe one or two | gotchas but they were explained clearly by the docs (appending vs | updating elements) and I was able to get it sorted easily. That | whole app took a couple days, but more than half the time was | wasted on iOS not supporting mp4 and webm formats for the | animated shots in the chat messages. Unfortunately, liveview does | not fix Apple's stupid walled garden. | | Overall it was a great way to minimize the javascript involved. | It doesn't make sense for certain types of apps, but it really | adds a ton of value and saves a lot of time when the use case is | right. And it performs really well. Loading and rendering a | chatroom full of messages (with animated gifs in each message) | makes a couple requests and happens in less than a second. | | And combined with phoenix presence it was like...5 lines of code | to add realtime online/offline statuses for users. Super cool. | ishwarn wrote: | > It doesn't make sense for certain types of apps, but it | really adds a ton of value and saves a lot of time when the use | case is right | | What are some of these use cases? | brightball wrote: | This is by far the most amazing demonstration of framework | capability that I've ever seen. I'm stunned. | | _HOW_ do you manage to broadcast only the minuscule change of a | number to all of the connected clients with server side | rendering? I 'm trying to wrap my brain around it. | nathan_long wrote: | Chris McCord has given some talks and explained this - eg see | https://www.youtube.com/watch?v=8xJzHq8ru0M at about 22 minutes | in | chrismccord wrote: | We are really excited about this release! I also wanted the | screencast to be a single take, so I'm happy to answer questions | if there are gaps to fill in for things I could have explained | more clearly. | thebigshane wrote: | First of all, great demo, and great showcase for this | technology. I think it did a great job of showing its strengths | to traditionally backend developers like me. | | I also appreciated the twitter analogy. I just couldn't help | but think "Wait, Twitter doesn't have 'Update/Edit Post'!" :) | | I've heard that Blazor in the .net world has similar goals to | LiveView, but I haven't gotten into it much. Can anyone | confirm? | zeroc8 wrote: | Yes, there are two Blazor variants. Serverside Blazor is | basically the same thing as Phoenix Live View, client side | Blazor is providing some optimized Mono runtime as | webassembly. | andruby wrote: | Hi Chris. I built a demo with LiveView a year ago and am very | happy to see all the improvements. | | One thing I didn't understand from the video is how the | :post_updated callback worked. It prepends the updated post to | the list of posts. How come that doesn't lead to the same post | being double in the list? | josevalim wrote: | Components IDs are unique. So once you try to render the same | component, we know you simply want to update the current one. | | The same happens on the client. If the server emits the same | DOM ID, the client just updates it in place too. | mangoman wrote: | Congrats on the launch! I've been following this for a while | and played around with it a bit, I'm excited to get my hands | dirty with this. | | How does reconnection work? lets say someone is using the site, | and you do a deployment, rolling the nodes over. Does live view | simply reconnect to another node without any interruption from | the user's perspective? | chrismccord wrote: | Confirm. LiveView will apply css loading state classes, which | are user defined, but we generated css by default in new apps | to lock the UI with loading cursor, and show a top loading | bar. On reconnect, which happens automatically, we rematch | the DOM with any changes, and we also send up any user form | input beforehand so client state is lost that was in | progress. We already have reports of users pushing bugs to | their systems and the UI seemingly being unaffected because | the LiveViews automatically recover, wether from a disconnect | or a crash on the server. | [deleted] | sertyrion wrote: | Great work! I think the phoenix framework project really | recognizes the power of "Defaults". Defaults for dev, user | interaction, server, and client. Make useful things quick but | also learn how and why they are chosen. | AlchemistCamp wrote: | How stable do you expect LiveView's API will be going forward? | | I'm especially invested in this since I've just rolled out an | alpha of a project that may incorporate it in the future: | https://phoenixigniter.com | chrismccord wrote: | As always, I can make no guarantees before 1.0, but we have | really focused on refining the programming model and APIs for | the last several months, so we're in a good state currently. | [deleted] | Scarbutt wrote: | You are running this on localhost but won't interactions be | slow if a user is 200ms from the server? | | Re-rendering templates every time on things that require high | interaction also seems very expensive and an easy way to slow | down your server when you have multiple users correct? or what | I'm missing? | chrismccord wrote: | you are correct, you can't escape the laws of physics latency | wise, but most web things we are all building, including 100% | JavaScript apps require a round trip to the server. For | example I can't post tweet without a round trip to twitter. | On the LiveView side UX wise, we apply css loading states to | the interacted elements, so the user is provided feedback | while awaiting an acknowledgement. On the server, our | templates are precompiled and do change tracking. So we only | execute the elixir code that is necessary to render the parts | of the template that changed. The rest of the calls are | noops. From the top down, it appears like an extremely heavy | approach, but with our change tracking and diffing, it's | actually incredibly optimized. Also note the Erlang VM was | made to handle multiple users, as we showed in our "The road | to 2M connections with Phoenix" blog post a couple years ago. | More connected users will mean more memory, but Memory is | cheap, and the platform scales well enough that opening a | websocket on all pages as a matter of course is completely | viable. Hope that helps! | chrismccord wrote: | I also forgot to mention we provide optimistic UI features | with our loading states and annotations. For example, in | the screencast, the "Save" button has phx-disable- | with="Saving...", which applies on client instantly and | stays until an ack is received. You can leverage the | loading states as well to toggle entire containers for a | happy medium between traditional optimistic and instant | user feedback. | josevalim wrote: | LiveView does not re-render the template on every | interaction. LiveView actually sends patches over the wire, | which is typically smaller than a hand-written JSON response. | The screencast linked above has a good example of this, where | clicking the "retweet" button sends a minimal payload, since | we know the exact position on the page. | | LiveView also uses a long-running WebSocket connection and | that reduces the amount of data sent over the wire compared | to regular requests/responses, as you don't need to encode | headers, cookies, etc. | | Finally, if you are worried about latency, you can call | "liveView.enableLatencySim(200)" in your browser console, and | you will be able to emulate how your application behaves over | high latencies. | dmix wrote: | This is the future of frontend development. Happy to see it | making progress. | | DHH was right about this stuff long ago in 2012 (mixing | server side rendering with interactive JS frontends) even | as the software wasn't quite there yet: | https://signalvnoise.com/posts/3112-how-basecamp-next-got- | to... | | The other approach is SSR with React/Vue that hydrates on | pageload with something like Next.js/Nuxt.js. | https://nextjs.org/ | | But the Phoenix approach seems better for a more Railsy | single framework approach (assuming you don't like writing | the entire server side code in JS, which I do not). It's | more cohesive and quicker to roll something out. | | I currently do mostly Vue with Rails backends | professionally but if it was from scratch or rearchitected | with Elixir I'd seriously reconsider using full blown | Liveview. | | My only concern would be missing out on some UI libraries | and pure size of community support. But I wouldn't miss | getting rid of the super complicated JS tooling set ups I | currently use (in addition to rails or trying to jam it | through the asset pipeline via webpacker), in exchange for | a more centralized approach. I've gotten a bit too used to | maintaining the frontend almost separately from the server | app and sometimes miss the simple days of being pure Rails. | | One additional concern may be portability for mobile with | React native. But that only applies to a subset of apps | where reuse/cross platform makes sense. Still it was a big | reason why these SPA style frameworks flourished like they | did. | Scarbutt wrote: | The patches don't need to be processed by the template | engine to render them? | tlack wrote: | The patches are sent as pure data and a small client-side | Javascript library (morph-dom) patches the DOM | accordingly, so the template itself doesn't get sent. | heisenzombie wrote: | It turns out that Elixir/Phoenix templating is, in | general, astoundingly fast, and that diffing operations | are highly optimized at the language level due to the | data structure (IO list) used. | | Here's some information: | https://www.evanmiller.org/elixir-ram-and-the-template- | of-do... | miki123211 wrote: | Any hope of getting this as a text article, or even a git repo? | Blind user here who can't see code embedded in a video, but can | read ascii-text with a screen-reader perfectly fine. | | People who don't enjoy videos for various reasons would | probably like a text version too. | thex10 wrote: | > People who don't enjoy videos for various reasons would | probably like a text version too. | | Yes this. I went to the video eagerly looking for a repo link | and was disappointed not to find one. | nickjj wrote: | Hey Chris, do you have any plans in the very near future to | update the example LV repo to use all of the goodies in LV | 0.12+ and Phoenix 1.5+ and perhaps even add more examples? | rglover wrote: | Thanks for putting this together, Chris :) | sdwolfz wrote: | I'm really amazed this sort of functionality can be implemented | in such a nice and clean manner. Your presentation is mind | blowing! | | Just out of curiosity (please don't consider this as a feature | request, more like a thought exercise), in case you wanted the | application to have offline editing functionality, could the | framework provide it in a similar manner? having a generic JS | handler that manages pushing state when an internet connection | is available again and exposing hooks that you can implement to | handle data consistency? Of course in this case the data | payload passed through sockets would be bigger as you would | need some identifiers to make sure for example increments don't | get duplicated, and there could potentially be different | strategies for solving data conflicts (CRDTs for example). But | would it need extra back-end support for this or would it be | enough to have engineers manage state through the current | handlers? | chrismccord wrote: | "offline support" is strictly on the side of needing to run a | lot of code on the client, so this is where we draw a line | and say you need a js framework, of which there are number of | great options. We have javascript interop via phx-hook, so it | could be made to work, but you'd necessarily have to have a | lot of js running on the client, and any feature you wanted | to work online & offline would not share a code path or need | js, thus client side rendering and templating gets involved | and suddenly we're writing a SPA. If we're strictly talking | allowing the edit of a textual input and syncing on | reconnect, it would be straight forward to do with LV and | some small js hooks, but the moment you start adding offline | UI features you'll need a js library. | | The funny thing is last I tried to use Google Docs offline, | it locked the page in read-only mode, so offline mode even in | the SPA space isn't just a given - you're definitely opting | in to some necessary complexity. | banashark wrote: | For those wondering how this works under the hood, there is a | great presentation: https://www.youtube.com/watch?v=9eOo8hSbMAc | | I wrote out a quick analysis of the details there along with some | looking into the backing code in text format here: | https://github.com/SaturnFramework/Saturn/issues/228#issueco... | holtalanm wrote: | This is pretty darn awesome. I remember looking at LiveView a few | months ago and being impressed. Elixir and Phoenix are still my | favorite backend to use for personal projects, but now with | LiveView I'm thinking I'm going to start using it fullstack. | neya wrote: | I was a Rails user for almost a decade. I switched to | Phoenix/Elixir 3 years ago. Elixir is a super simple language. It | has no OO concepts and everything is functions first. In fact, | it's so good that I started teaching it for universities. All my | production web applications are now fully on Elixir. | | Elixir is one of those languages where everything has been done | perfectly as of the time of this comment. When I say perfect, I | mean, there has been never once in my career where I hit a | roadblock due to the language's limitation or complexity or | flawed assumption. I faced this with other languages, but not | Elixir. | | I'm currently a full time consultant writing, teaching and | deploying production apps for clients in Elixir. Most of my apps | have served my clients well beyond my contractual agreement. It's | almost deploy and forget because of Phoenix. It also scales | amazingly well. It's extremely performant. | | I just re-wrote the entire crappy mess Wordpress core in Elixir | and enjoyed the process. In any other language, I wouldn't be | saying the same. It's really an enjoyable language that forces | you to rethink the way you write code. Don't be put off by this | statement, I mean that in a really good way. And once you start | thinking interms of functions, you just won't touch any of those | terrible OO programming paradigms (Eg. writing for loops, nesting | conditionals, etc.). You will start writing maintainable, | beautiful code. | | You will appreciate Elixir a lot especially if you have a | background in horrible programming languages like Javascript. You | will literally start finding ways to use Elixir in your entire | stack like I am. It's that good. | | Enough said. Give it a shot. | neya wrote: | Thank you HN. If you're interested, I'm actually documenting my | journey as a consultant building ideas on Elixir at | https://medium.com/build-ideas/ | | Thank you. | [deleted] | mcintyre1994 wrote: | > When I say perfect, I mean, there has been never once in my | career where I hit a roadblock due to the language's limitation | or complexity or flawed assumption. I faced this with other | languages, but not Elixir. | | This is pretty amazing praise! Are many of the other languages | you've used strongly typed? That's the thing that gives me | pause - I feel like I rely on the compiler a lot in languages | where it can do a lot for me, and that I'd be really frustrated | managing a large project without it. | | I'm not sure what other languages you have a lot of experience | with, but does anything in Elixir stand out to you as the | reason dynamic typing works well there? I think dynamic typing | is the main reason I nod along with "horrible programming | languages like Javascript", for instance. | QuinnWilton wrote: | > Are many of the other languages you've used strongly typed? | That's the thing that gives me pause - I feel like I rely on | the compiler a lot in languages where it can do a lot for me, | and that I'd be really frustrated managing a large project | without it. | | As someone who has been writing Elixir professionally for | 4-ish years, and who is otherwise about as big of a proponent | of static types as is possible, this is simultaneously my | biggest critique of Elixir, and also a critique that has | ended up being more theoretical than not. I wish that it were | statically typed, but in practice that hasn't been slowing me | down. | | Part of the reason for that is that while Elixir is | dynamically typed, I've found that it's possible to pretend | it is a statically typed language if you squint just right. | By that, I mean making judicious use of typespecs [0], | dialyzer [1], Norm [2], and just generally constraining the | way you write code to mirror the sorts of code that you'd | write in a language that offers algebraic data-types, like | Haskell. I've been meaning to put together a blog post on | what I mean by this, because I often hear people talking | about how unusable Dialyzer is, so I feel like my team is a | rare example of a large production app that requires that | Dialyzer typechecks on every build. This makes large-scale | refactoring of our app almost (but not quite) as easy as it | would be in Haskell. | | The macro system in Elixir also means that it's possible to | write libraries like typeclass [3], that offer compile-time | guarantees (using compile-time property tests!) that your | implementations correctly implement a given interface. I | think that as the language evolves, we'll be seeing a lot | more examples of macros that move runtime logic into the | compilation step, to offer compile-time guarantees and | safety. For example, a few weeks ago, I prototyped an | experimental library in a few hours that added support for | compile-time OCaml style parameterized-modules. [4] | | Obviously, all of these techniques don't get you quite as far | as proper static typing would (though the Whatsapp team is | working on static types for Erlang! [5]), but the rest of the | language and the ecosystem is just so well thought out, that | I've been okay with that! | | [0] https://hexdocs.pm/elixir/typespecs.html | | [1] https://github.com/jeremyjh/dialyxir | | [2] https://github.com/keathley/norm/ | | [3] https://github.com/witchcrafters/type_class | | [4] https://github.com/QuinnWilton/daat/ | | [5] https://www.facebook.com/careers/jobs/229713254864749/ | scythmic_waves wrote: | To add to this, it's worth noting that Elixir would have | difficulty _ever_ becoming statically typed because of how | message-passing works. For example, if you look at Gleam | [0] -- the recent project that's trying to create a | statically typed language for the BEAM -- it can't handle | message-passing [1]. | | And I totally agree with you here: | | > I've found that it's possible to pretend it is a | statically typed language if you squint just right. | | My Elixir code tends to use a lot of pattern matching on | structs. It's not real static typing, but it gets me a lot | of mileage! | | [0] https://gleam.run/index.html | | [1] https://gleam.run/faqs.html#how-is-message-passing- | typed | mercer wrote: | I've heard a number of times that static typing would be | near impossible for Erlang because of how message-passing | works, but just the other day I read a quote where one of | the creators (I think it was Joe Armstrong, but not | completely certain) indicated that the primary reason | they decided against static typing was the hot code | reloading, not the feasibility. or not primarily anyways. | | Not disagreeing with you, I guess. It was just | surprising/interesting to read. | | Sadly I can't find the quote... | lpil wrote: | I'm the author of Gleam, a statically typed language on | the BEAM. | | What you've said matches my findings. Messages can be | typed, however hot code upgrades cannot be. For Gleam | we've sacrified hot code loading in exchange for types, | which I think is a good trade for most use cases. | QuinnWilton wrote: | I don't know if I'd say "ever", but it definitely won't | be easy! Take a look at session types [0] if you haven't | seen them! | | Type checking unrestricted message passing would be | difficult, but I can imagine a subset that's typeable. | | [0] http://groups.inf.ed.ac.uk/abcd/index.html#about | scythmic_waves wrote: | I haven't seen them, thanks! | | > Type checking unrestricted message passing would be | difficult, but I can imagine a subset that's typeable. | | I'd love to see a discussion on this somewhere. I can | totally imagine a world where it's true that "yes, you | can't send messages of type X because the receiver can't | pattern match on them in the way you expect, but what | you're really trying to do is Y, and you _can_ send | messages of type Z that accomplish Y." | lpil wrote: | Hi, I'm the author of Gleam. | | We have fully type safe message passing, we just don't | have any special syntax for it. I'll update the | documentation to make this clearer. | scythmic_waves wrote: | Hi! Super cool project you've embarked on. I know | everyone I work with (those who write Elixir, anyway) is | very interested in its progress. | | > I'll update the documentation to make this clearer. | | Thanks! Much appreciated. | neya wrote: | Before Elixir, I considered Scala because static typing was a | strict requirement for me. After I tried Elixir, I found out | that good, reliable and maintainable code doesn't have to be | necessarily static typed. | mcintyre1994 wrote: | That's an encouraging answer because I currently use Scala | :) Thanks! | AlchemistCamp wrote: | Static typing is far from a panacea. Don't forget that the | rise of Perl and PHP, and then later Ruby and Python was _in | response_ to statically typed languages like Java. | | There are trade-offs. | mcintyre1994 wrote: | I agree, I wasn't trying to imply otherwise. It's just that | static typing is the first thing that looks like a | theoretical trade-off of Elixir based on my experience, and | I was curious why it doesn't feel that way in practice. | AlchemistCamp wrote: | Structs and pattern matching help quite a bit. | paultannenbaum wrote: | Dialyzer via Dialyxir also helps as well | andy_ppp wrote: | One of the things I like about Guards is that you can | enforce/match against types as needed. | conradfr wrote: | To be fair PHP, while still dynamic, has eventually evolved | to be (optionally) a strict and strongly typed language. | pdpi wrote: | > And once you start thinking in terms of functions, you just | won't touch any of those terrible OO programming paradigms | | This sort of absolutism does nobody any favours. Functional | zealotry is every bit as bad as OO zealotry. | mwcampbell wrote: | > I just re-wrote the entire crappy mess Wordpress core in | Elixir and enjoyed the process. | | That sounds useful. Do you plan to make a product out of that? | neya wrote: | Yup, launching soon. Probably will open source it too. | jeremy_k wrote: | Any way to get updates when this happens? | | Edit: Saw the link to your blog below | https://medium.com/build-ideas/ | codegeek wrote: | If you want to compete with WordPress, you should open | source it. | mattste wrote: | I 100% agree about Elixir and its surrounding ecosystem being | almost perfect. It's insane how simple the building blocks are | and how easy the code is to read and write. | | Here are a few pain points I've ran into: 1. Typespecs leave | something to be desired compared to other type systems 2. Maps | vs structs and easily using one in place of the other | | Things that my team would like to see: 1. Components in | addition to templates as a first-class citizen in Phoenix. My | team loves React because of the component model even though we | hardly use an insane amount of interactivity. | joerichsen wrote: | If you are interested in a React like component library built | on top of LiveView you should check out Surface | http://surface-demo.msaraiva.io/ | benzible wrote: | Seconding the recommendation to check out Surface! From the | docs: | | Surface is a server-side rendering component library that | allows developers to build rich interactive user- | interfaces, writing minimal custom Javascript. | | Features: * An HTML-centric templating language with built- | in directives (:for, :if, ...) and syntactic sugar for | attributes (inspired by Vue.js). * Components as modules - | they can be stateless, stateful, renderless or compile- | time. * Declarative properties - explicitly declare the | inputs (properties and events) of each component. * Slots - | placeholders declared by a component that you can fill up | with custom content. * Contexts - allows a parent component | to share data with its children without passing them as | properties.. * Compile-time checking of components and | their properties. * Integration with editor/tools for | warnings/errors, syntax highlighting, jump-to-definition, | auto-completion (soon!) and more. | perseusprime11 wrote: | Are there any good tutorials to get started? What major sites | are currently using Elixir? | AlchemistCamp wrote: | Discord and Pinterest use Elixir at a massive scale. Discord | in particular has been writing a great technical blog about | the process. | | On a smaller scale Feedback Panda very successfully | bootstrapped to a 55k MRR SaaS and was aquired in under 2 | years: https://youtu.be/vaXp81OxxK0 | | Edit: Not to be too self-promotional, but AFIK I've created | more free Elixir-learning screencasts than anyone on YouTube. | I cover a pretty broad range of topics from total beginner to | some fairly niche things: https://youtu.be/z1nKbzZiRtY | nickjj wrote: | > What major sites are currently using Elixir? | | Besides what was listed already, if you're looking for info | on people running Elixir / Phoenix in production there's this | podcast: https://runninginproduction.com/tags/phoenix | | One guy was pumping over 7 billion log events a month through | it. | | Another one (remote.com episode) serves 100k+ daily requests. | | Each podcast episode goes through how the app was built and | how it's deployed at a high level, but still has enough | details to apply those things back to your own projects. | | With that said, if anyone is running Elixir or Phoenix in | production and sees this and wants to be on the show, just | head over to the above link and click "become a guest" on the | top right. I'd love to have you on, both big and small sites | are welcome. | bmelton wrote: | > any good tutorials? | | https://elixir-lang.org/learning.html - Start here. It starts | off kind of abstract, and then works its way into the | concrete (from my experience) -- but is very easy to pick up. | | > What major sites? | | Last I heard, Discord was serving ~5 million concurrent | users, and that was well before compelled isolation. | | Bleacher Report is also using Elixir, and serving something | like 100k requests per minute just to mobile clients. | | Bet365 handles as much as 100k monetary transactions during a | big sporting event like a Champions League final. | innocentoldguy wrote: | The Elixir video courses at https://pragmaticstudio.com/ are | the best available, in my opinion. There are some free | tutorials available and some other good paid courses, to be | sure, but I think Mike and Nicole over at Pragmatic Studio | offer the most complete and easy-to-follow courses available. | The cost-to-value ratio of their courses is really good, I | think. | | I'm not affiliated with them in any way. I'm just a happy | customer. | conradfr wrote: | I don't know why you're downvoted, I did the Elixir course | and it was useful and well done. | barefootford wrote: | Haven't done their Elixir course but can vouch for their | Ruby/Rails stuff. They just really value simplicity and | sweat the details more than most instructors. Great stuff | olah_1 wrote: | I actually agree with every pain point that Dave Thomas | described in his talk[1] and is outlined in the Component | github repo[2]. Sadly, it's unclear to me if anyone else is as | passionate and willing to continue the effort. Nonetheless, it | put to words exactly what I hated about genservers and such. | | [1]: https://www.youtube.com/watch?v=6U7cLUygMeI | | [2]: https://github.com/pragdave/component | throwaway286 wrote: | >Elixir is one of those languages where everything has been | done perfectly as of the time of this comment. | | Including GenServers? I find writing GenServers to be | needlessly painful. Their syntax is one of my least favorite | things about Elixir. | bmelton wrote: | > And once you start thinking interms of functions, you just | won't touch any of those terrible OO programming paradigms | | Also worth noting (because Elixir was strange and new for | everyone at some point) that a lot of what _used_ to be mind- | boggling to me in earlier versions of Elixir /Phoenix have | become relatively commonplace when dealing with things like | React or modern Javascript. | | If you've ever handled a JS promise for something as simple as | a `fetch()` query and processed results, you _already | understand_ how to build code in functional components. | | There's much less difference between | fetch(url) .then(results => doSomething()) | .then(results => doSomethingElse()) | | and doSomething() |> | doSomethingElse() | | than it looks like at first blush. | yachtman wrote: | Can you explain in your own words for elixir where the balance | between "joy" (productivity, flow, whatever you want to call | it) and what you call "rethinking the way you write code". As | another example of a functional oriented language that prides | itself in the same thing look at Haskell. I wouldn't say most | people think that Haskell is an easy or productive language | (that is until after you have years of experience under your | belt), but it definitely forces you to rethink how you code. | Scala is another example, but draws the line at a different | place. How is where elixir draws the line preferable in your | opinion? | lostcolony wrote: | So as someone who worked in Erlang (and is familiar with | Elixir), I can answer that a little bit. | | They hit a sweet spot. Enough rigor, enough limitation, at | the language level, to force you to do things "the right way" | for a broad swath of problems (there are certain problems | they're poorly suited for), but enough simplicity and freedom | to keep a minimal learning curve, and minimal runtime | complexity to figure out issues. | | The focus on fault tolerance also means once you learn to use | the app structure, with supervisors, you mostly have just one | concern for reliability ("how do I start this into a known, | good, consistent state?"), rather than hundreds ("what | happens if -this- fails? Or that? Or that other thing?"). | | Haskell and Scala both have a huge surface area to learn | before you're productive, let alone anything approaching | fault tolerant, and even after years of use, there's still a | lot of hidden gotchas with them. I've seen teams take Erlang, | and their very first project just...worked. None of their | lessons learned caused production issues, and no weirdness; | the three issues that made it to prod I can even think of | were, variously, one that wasn't user facing (just a log | entry indicating something wasn't handled right; supervisor | took care of it), one where performance started to slow (and | it was due to having written an O(N^2) algorithm | accidentally, and not testing at higher loads than | production), and one where a low level C driver made an | unnecessary reverse DNS lookup that, when the caches flushed, | caused things to hang, which became an issue when load | increased, and which should have been circuit broken (but | which instead caused a failover to another node). | | I've never had that experience with another language. The | ramp up time to productivity was longer, the production | issues caused by us missing something about the language were | more frequent, the production issues caused by us failing to | expect or handle a failure condition were more frequent, etc. | | That said, Elixir and Phoenix do raise the bar a little. | Elixir has a larger surface than Erlang (only a few concepts, | but a lot of ramifications when it comes to macros if you | haven't used them in other languages), and Phoenix is reliant | on a fair bit of magic so you need to read the docs and it | may take a little bit of work to feel happy with things. But | even when I knew nothing about it, picking it was easy | because of the experience I had with Erlang. | neya wrote: | Ok, I've heard of Haskell and I've been to many Haskell | meetups too. This was before I found Elixir. For me, the | biggest problem with Haskell is that it will turn cryptic if | you don't touch the codebase for say, 6 months. Because, you | need to essentially re-learn the special syntax of the | language if you're touching Haskell code after 6 months. | | In contrast, Elixir is super simple. I can describe the | language in a single HN comment. Everything is just a | function and they're inside modules. That's pretty much it. | When I was picking up Elixir, I started off writing an | E-Commerce platform from scratch with Elixir. When I was | about to finish it, I was thrown into a 8 month long PHP | project. When I was finally done with it and returned back to | work on my E-Commerce project, I was able to just open up the | code in a text editor and immediately understand what was | happening - without having to re-learn the language. That is | a the "Joy" part for me. | | One more "Joy" of writing in Elixir is you don't need to | worry about memory usage or performance that much. It's | pretty efficient unlike Ruby or PHP even. So, I can just | throw this entire application in a $5 Digital Ocean droplet | and watch it handle insane amounts of traffic. And this is | without optimizing anything. You will probably able to | squeeze more with caching, optimizations, etc. | | As for re-thinking the way you write code, it's mostly to do | with pipe operations. Piping was a new concept for me jumping | from Ruby. It's absolutely powerful, concise and productive. | In Elixir, if you don't write good code, the compiler will | warn you AND show you examples of how (and sometimes why) it | should be re-written. Eg. If you write a nested case | statement which many people subconsciously may do coming from | OOP backgrounds, the compiler will ask you to re-think your | code. And stuff like this really challenges you, but actually | doesn't do anything destructive to your code - your code will | still compile and function, but you will feel that tingling | inside - "Hmm, maybe I should consider re-writing that?" | | THAT is the balance. Before Elixir, I also used Scala. Took | me 3 months to fully learn the language from the 700+ pages | book on Scala. I absolutely love Scala and the JVM. It's | powerful, but there's just too many ways to do the same thing | in Scala to keep track of. So, it goes back to my first point | on looking at the language after 6 months without the need to | re-learn it. I would still try Scala at some point because | JVM is very powerful. But, will it replace Elixir? Absolutely | not. | nickjj wrote: | > When I say perfect, I mean, there has been never once in my | career where I hit a roadblock due to the language's limitation | or complexity or flawed assumption. I faced this with other | languages, but not Elixir. | | That is interesting. | | I've gone from Geocities pages to ASP Classic, PHP, WP, Rails, | Flask and have been dabbling with Elixir and Phoenix for a bit. | | So far LiveView (and some bits of Elixir in general) has been | the only time in my career where I felt like I'm seriously | struggling hard to understand how things come together and how | to solve real but fairly common web dev problems without asking | for help (mainly with LV). I've had to ask for help on IRC / | the forums an embarrassing amount of times. Where as with Rails | and Flask almost everything except the most crazy problems were | solvable with a little bit of Googling and self exploration. | | Now, I'm not saying Elixir and Phoenix is bad at all. I'm still | happily using it to build my next big project (for all of the | reasons that everyone else uses it for), and the Programming | Phoenix book is a great learning resource for building regular | web apps with Phoenix, but there's nothing like that for | LiveView. | | But on the bright side, Chris (the author of Phoenix) and Jose | (the author of Elixir) are remarkably responsive to help the | community. Lots of other folks are super helpful too. That and | once things start to click, you can write some really pleasant | looking code and actually understand what it does a few months | later. | gprasanth wrote: | I've been writing code for close to 10 years now and I see this | and feel like what a beginner would feel trying to understand | assembly code. Or someone hearing a similar but new language. | | Looks to me hours of work and learning got abstracted as under | the hood "magic". | | It's like saying: Oh, so I've been weight training for the last | 17 years and I should tell you Olympic silver is not too hard, | see? | | Am I right in my thinking? or is learning to be this proficient | with the framework not as time taking for a novice developer as I | think it to be? | lawik wrote: | I think the biggest hurdle would be the Functional Programming | aspects. Elixir was quite easy for me to approach coming from | mostly PHP, Node.js and Python (preferred Python before | Elixir). | | Phoenix has a sprinkling of magic or syntactic sugar through | macros and stuff but the step between Phoenix and LiveView is | not that mystical to me. | | I have taught a novice developer, not yet out of 2-year school | for web dev, to use Elixir and the Phoenix Framework. I really | should interview him about that. Write something up. | | I would say that Django does more stuff under the hood than | Phoenix. But they are fairly different vehicles. | lostcolony wrote: | Are you familiar with Ruby on Rails? Phoenix was built as a | simpler equivalent, to leverage websockets, and to leverage the | BEAM to be performant with high concurrency (i.e., what you | generally want in a web server). | | Is it encompassing a lot of magic? Yes. But the quick growth in | mindshare that Ruby on Rails attained back in 2005-2010 (part | of which it still retains) was due to how easy it was for a | novice developer to pick it up and build something useful. Pick | it up and understand all the pieces, no. Phoenix is less | Express/Martini in nature, more Spring/Rails (but more pleasant | than either of those to work with, I would contend, but again, | that's opinion). | brightball wrote: | There's no magic. It's functionality that's made possible by | the BEAM. It's just one of those things that came out of | realizing how powerful the runtime was for handling certain | types of problems. | | When you combine a couple of those bits together, you end up | with this. | | The only modern web problem that Elixir isn't ideally suited | for is heavy number crunching. Otherwise it gives this amazing | balance of efficiency, scalability, reliability, | maintainability and capability that can't be replicated in any | language that has a shared memory model. | IanCal wrote: | I'm of a similar view to them, this felt like lots of magic | or at least lots of gotchas I can see myself hitting. I saw | two function definition types (def and defp), pattern | matching input then requiring a caret for the variable name | (?), passing in "assigns" but then referring directly to | other variable names, pipes, etc. There's a lot there | compared to when I've worked in Erlang. | lawik wrote: | Gotchas, probably some of those. Both in Elixir and | Phoenix. It is an entire language plus an entire web | framework. | | A lot of people find Elixir more approachable than Erlang. | But I wouldn't be surprised if Erlang is technically | simpler and as such potentially easier to learn. The syntax | is quite foreign to most people coming from more | conventional languages while Elixir reads and writes fairly | conventionally. | | Now Phoenix LiveView does a lot for you, so I wouldn't | dismiss the claim of having some magic in there. But once | someone starts to get familiar with the stateful approach | the model is fairly simple and the "magic" is less | mystical. A lot of the things you don't need to pay | attention to would be optimizations in markup and templates | and how the JS does its job. That's where a bit of magic | happens. | | Most of the things you mention sound like things about the | language. Which I found to be very approachable and the | onboarding documentation to be great. | faizshah wrote: | Question to the former Rails devs out there: | | We mostly see threads fawn over Elixir and Phoenix, have you | experienced any downsides to switching? Anything you miss from | Rails? | | I'm convinced to give it a try after that demo! | pg_bot wrote: | I have ~6 years experience working in rails and ~3 years | working with Phoenix. The only downsides I can think of are | lack of official clients for APIs, and some missing packages. | For example, if I wanted to do some PDF work I would really | miss the prawn library. I don't miss anything from rails, | Phoenix IMO is a superior web framework. | buf wrote: | Also curious. I've been a django user for 5 years and a rails | user for 9 years. I mostly use rails as an API these days and a | React app for the front end. | mercer wrote: | I'd say I sometimes miss Rails' ecosystem, but the many | advantages of Phoenix and especially LiveView are worth that | small downside. | | To someone who uses Rails/Django with React, I'd describe | LiveView as something that offers pretty much everything | you're used to, but with the React part consolidated with the | server-side of things. | | I've been playing around with LiveView since it came out, and | have been using it for 'serious' work for the past year or | so, and I still regularly discover ways in which this setup | simplifies things, makes development faster and more fun, and | saves me from various potential security flaws or 'busiwork'. | | Imagine that your store (Redux, whatever) and view (React) | ran in the same place as your backend. | | Direct access to the database and the rest of the backend, no | need to carefully consider whether it's _really_ worth it to | increase your js payload with library x, no constant context | switching, no building and maintaining of, essentially, two | completely separate applications, no need to make sure your | API endpoints are properly versioned, or that they don 't | expose data that shouldn't be exposed, or that they send only | the data you need. Little to no need for rube-goldberg js | pipelines, and significant less time spent dealing with | synchronizing state between server and client. | | I'm probably forgetting some other advantages. | | There are some things where LiveView doesn't seem ideal, but | I've found that even in those cases I prefer using LiveView | wherever I can anyways, and then I do the specific | javascripty bits wherever I need them. | itake wrote: | It seems like a lot of companies are moving to Go for fast | services. As a rails dev for 10+ years, I am looking at Go | instead of Elixir and Phoenix, b/c Go is a much more popular | language than Elixir. | innocentoldguy wrote: | Go may be popular, but is also a lot less complete. | | Some other things to consider are that Go's concurrency model | is quite bloated when compared to Elixir's, Go uses public | memory for its goroutines vs. Elixir's private memory that | protects processes, Go uses cooperative multitasking vs. | Elixir's preemptive model, and Go's dependency handling is | absolute rubbish. Elixir's process supervision is amazing and | Elixir's pattern-matching allows you to write easier-to-read | and more maintainable code, in my opinion. | | While both are good languages, I've personally found Elixir > | Go. | donatzsky wrote: | Which would you say is easiest to learn, for someone with a | little programming experience? | | I'm currently planning a SaaS project and had settled on | go, but this discussion is making me wonder if perhaps | Elixir is the better choice. | innocentoldguy wrote: | I think the basics of Elixir are easier to learn. The | syntax is pretty straight forward and you don't have to | worry about much more than functions and modules. | | OPT, which is how you build supervision trees and other | more complex Elixery things, is tricky at first but | reading through Designing Elixir Systems with OTP, by | James Edward Gray & Bruce A. Tate, and The Little Elixir | & OTP Guidebook, by Benjamin Tan Wei Hao should clear it | up fairly well. | | This is highly subjective, of course, but I've written | code in C, C++, Pascal, Basic, Python, Perl, Ruby, | JavaScript, Go, PHP, Java, C#, Clojure, Scheme, Cobol, | and Pick Basic (of all things). Elixir has been my most | favorite language so far. | aczerepinski wrote: | I've invested a lot of time into both Go and Elixir. I like | both languages a lot. | | My anecdotal view of the marketplace is that Elixir | recruitment emails tend to come from startups looking for | their 2nd or 3rd dev, usually full stack, and the salaries | are nothing special. Go recruitment emails tend to come from | bigger companies looking for backend devs, often hoping for | kubernetes experience too, and the pay is higher. | | I built an internal service with LiveView at work and I loved | it. I gave a presentation about how it all works and was | hoping it would catch on. It didn't. The front end team would | probably mutiny if we told them to give up React and level up | their LiveView skills. | | If I were running a web business as a solo founder I would | 100% want to use LiveView. As a dev on a larger team that | already has specialization it's a tougher sell. | scraplab wrote: | There's so much to like about Elixir/Phoenix. The only real | downside I've experienced is that there isn't the range of | libraries available compared to Ruby/Rails. That said, many of | the libraries that do exist are of a decent quality with good | documentation, and easy to contribute to where you need to. | msie wrote: | I recommend this free Phoenix course: | | https://shankardevy.com/phoenix-inside-out-mpf/#mastering-ph... | | I found it more approachable than the PragProg book. Helps if | you've dabbled in Rails a little bit. | jakearmitage wrote: | How would this scale for, I don't know, 10k concurrent users? | Would a live chat app be reasonable? | AlchemistCamp wrote: | A live chat app is its sweet spot. | | It runs on the same Erlang VM that Whatsapp used to scale to | hundreds of millions of users on under 30 machines and very few | employees. | dorian-graph wrote: | Here's an example of using it with 2 millions users: | https://www.youtube.com/watch?v=c6JcVwbOGXc | mercer wrote: | I'd say live chat and many concurrent users (+ many small | messages being sent around) is exactly the kind of thing that | Phoenix (and LiveView, _and_ Elixir) is basically made for. | benja123 wrote: | Congratulations Chris, Jose and the Phoenix core team on Phoenix | 1.5 and the livedashboard. | | I used liveview in https://readastorytome.com. I posted it here | last week and it stayed on the front page for a good few hours. I | was concerned about load, so before I posted it I upgraded my $5 | DO box to a 4 core box which turned out to be completely | unnecessary. The CPU load stayed in the 2-3% range the entire | time it was on the front page, which is pretty amazing | considering how much traffic was coming in. Funnily enough I came | across the livedashboard repo just before I posted it, so I set | it up and I was using it to monitor my app live while it was on | the frontpage of HN. | | The dev experience is pretty amazing - I don't think I would have | been able to write this app as quickly and to add new features at | the pace I have been in any other language. | | So thank you Chris, Jose and the Phoenix/Elixir team. | | If anyone has questions about my experience with liveview free | free to ask - either here or by email - ben@readastorytome.com | AlchemistCamp wrote: | It's so great to see this released. The community has been | excited about LiveView for a long time! | voppe wrote: | This just keeps getting better and better. Thank you for your | effort. | | I'm a big fan, Elixir + Phoenix is excellent enough, and LiveView | is what really sealed the deal for me as the perfect web | development stack. It's simple, it's fast, it works. Sure, it has | a few rough spots that have to be worked around when you start to | push its limits, but as this blog post showed it's perfect to | quickly have a working web application by completely skipping | having to write client-side code. I developed a couple of | prototypes and single-use applications in hours that with any | other stack would have taken me days. Hell, I'd argue that in | some cases it took me less time to complete my app with LiveView | than it would have taken me to setup Webpack with Frontend | Framework. | | I don't believe it to be a silver bullet for sure, and it has to | prove its worth at scale, but it gives excellent results with | little effort, and honestly the reduced tedium is worth it on its | own. | | Also, protip: try LiveView with Tailwind CSS. | leeoniya wrote: | > On the heels of the official LiveDashboard release, Phoenix 1.5 | projects now ship with LiveDashboard by default, for real-time | performance monitoring and debugging tools. | | and it uses uPlot [1][2] in the metrics panel :D | | [1] https://news.ycombinator.com/item?id=21207132 | | [2] https://news.ycombinator.com/item?id=22567922 | codegeek wrote: | I have been watching Phoenix/Elixir for a while and wondering if | it has gained a lot more traction compared to say 2 years ago ? | My choice of full web app framwork is Symfony/Laravel (PHP) or | Python (Flask/Django) but I really like what Phoenix has to | offer. | | However, does it have a mature ecosystem now ? What about | deployments and tools around it ? Does it work with generic web | servers like Nginx or does it run its own http server ? | lawik wrote: | I would say it is quite mature. The ecosystem is a bit smaller | and younger, so sometimes you'll find an insufficient library | somewhere or no library at all. But I've never had a show- | stopper in that regard. | | I've done plenty of Django and Flask and I'd rather work with | Phoenix. I still like Django plenty but only time I'd pick it | is if I think the admin might save me copious time or if there | is something else tying the project to Python, such as machine | learning. | t0astbread wrote: | Wouldn't you typically run Nginx (or something similar) as a | reverse proxy anyways in production? | codegeek wrote: | correct. I was just wondering if it also has inbuilt http | server as well to process things like static files etc or is | that completely on 3rd party tools like nginx (which I love | btw). Kinda like in Golang where you could technically use | the static file server but I always use nginx for those | things. | innocentoldguy wrote: | Cowboy is Elixir's built-in HTTP server. You can configure | things like Nginx or Haproxy as you would with most other | frameworks. | conradfr wrote: | It's more an Erlang http server used by Phoenix ;) | josevalim wrote: | The ecosystem has been quite mature for a while. The language | reached v1.0 almost 6 years ago and Phoenix v1.0 was released | almost 5 years ago. The community has built a lot on top of | them since then. | | The deployment topic is wide, so the answer will depend on your | favorite ways to deploy. If you are using Heroku or another | PaaS, it has been very straight-forward since ever. If you want | to ship a self-contained executable or a Docker image, the | language has standardized on the tooling for assembling those | in the last year or so (they are called releases). It was | always possible before, just not standardized at the language | level. | | It runs its own http server but you can easily put it behind | nginx, websockets included, by using the reverse-proxy | settings. Here is an example article: | https://dennisreimann.de/articles/phoenix-nginx-config.html | thruflo wrote: | I'm a long time Python dev converted to Elixir (for ~3 years | now, mainly web dev and data pipelines). | | The tooling and developer experience in Elixir is first class | and the ecosystem is highly mature. | julioo wrote: | What if you get disconnected due to bad network (LTE in | transport). The reconnect will recover the state from the server | or you loose everything ? | josevalim wrote: | It automatically reconnects. I wrote about the client/server | state here: https://news.ycombinator.com/item?id=22897436 | | In a nutshell, temporary state in the client (URLs, forms) are | resubmitted on reconnect. The server state is typically backed | by a database. ___________________________________________________________________ (page generated 2020-04-22 23:00 UTC)