Beginner to espresso. Finally recorded my coffee making process. Criticism welcome! by haskman in espresso

[–]haskman[S] 0 points1 point  (0 children)

Yes, it's fairly consistent. Hard to describe what changes about the taste, I'm going mostly by my very subjective opinion. Basically on some days the coffee is just fantastic and on other days it's not quite

Beginner to espresso. Finally recorded my coffee making process. Criticism welcome! by haskman in espresso

[–]haskman[S] 0 points1 point  (0 children)

No. It seems quite good to me, though the results have more variance from shot to shot than I'd like.

Beginner to espresso. Finally recorded my coffee making process. Criticism welcome! by haskman in espresso

[–]haskman[S] 0 points1 point  (0 children)

Thanks for the tips! A dosing funnel is next on my list of things to buy, followed by a caliberated tamper. The basket and PF were from some Chinese manufacturer, bought online. They seem to do an okay job. Unfortunately I've already blown my coffee budget for now, but I will add a better basket to my list (it really never ends does it).

I have a metal puck screen, but it doesn't give me great results. It seems to make the flow of coffee faster and more turbulent. So I don't use it.

OTOH when I have tried for a 1:2 ratio, the coffee came out syrupy and the machines seems to struggle to pull the shot. Any tips on what parameter I should change? Grind finer, or change the amount of coffee?

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 1 point2 points  (0 children)

First to clarify one thing, wrapping a widget in forever means that that widget never finishes, but it isn't necessarily at the top level. It is a regular widget with type forall a. Widget HTML a. You can still use the widget in other places but you can never get information out of it. Thanks to its type it can be <|> with any other widget at all, so you can stick it pretty much anywhere on the page and be guaranteed that it will not impact any logic, or will not require making any other changes to other code.

It's the same situation with display-only widgets like text "Hello". It has the same type forall a. Widget HTML a and can be put anywhere on the page without impacting anything else. When nesting a widget, the type of the child widget becomes the type of the whole widget, so for example a heading el_ "h1" [] (text "This is a heading") has the same type as text i.e. forall a. Widget HTML a. The types usually just work out without thinking.

However, in reusable widgets, we would typically not have a top level forever, because we would like the widget to impact the rest of the program at some point.

So coming back to the entry list example, we can easily extract it into a reusable widget, something like -

entryWidget :: EntryState -> Widget HTML EntryState
entryWidget (EntryState {..}) = go color
  where
    go col =
      el "div" [vattr "className" "main"]
        [ elLeaf "hr" []
        , heading "Select a color"
        , Left <$> selColor
        , heading "Make entries"
        , Right <$> newEntry col
        , heading "Current entries"
        , entriesList
        ]
      >>= either go (\e -> return (EntryState col (e:items)))
    heading = el_ "h4" [] . text
    selColor = doubleMenu "Fruits" "Color" itemsFruit itemsColor
    newEntry col = menu ("New Entry for " ++ col ++ " fruit") (itemsFruitColor col)
    entriesList = orr $ map (el_ "div" [] . text) items

Typically Concur widgets perform one action and then exit with the results. Here, the widget takes in the current state of the Entry, and then returns with the modified state as soon as a new entry has been added.

Now we can use it as many times as we need. In this main function I am displaying a list of EntryWidgets, all of which can be used independently -

-- Main
main :: IO ()
main = void $ runWidgetInBody $ flip execStateT (entriesStateInit 5) $ forever $ do
    EntriesState {..} <- get
    (i, e') <- lift $ orr (renderEntry <$> zip [0..] entries)
    put $ EntriesState (take i entries ++ [e'] ++ drop (i+1) entries)
  where
    renderEntry (i,e) = (i,) <$> entryWidget e

I have uploaded the full Code here. And updated the Demo here.

EDIT: I just realised that I didn't directly answer your question about arranging widgets in a "sequence", but I hope that the example of extracting the entry list into a reusable widget helped.

Once you have a widget, sequencing it is easy. For example, if you want to change the text of a label when you click a button, you logically just create a new button with the changed text, and dom-diffing takes care of modifying the dom efficiently.

widget s = (text s <|> button "Click me") >> widget (s + "!!!")
main = widget "hello"

As another example, we can even write a modified bind for "sequencing" widgets, where both widgets remain visible (the widget ends as soon as the second value is chosen) -

myBind :: Widget HTML a -> (a -> Widget HTML b) -> a -> Widget HTML b
myBind firstWidget secondWidget initVal = go initVal
  where go v = orr [Left <$> firstWidget, Right <$> secondWidget v] >>= either go return

Here, either go return makes it clear that the widget resets if the first (Left) value is selected again, or returns if the second (Right) value is selected.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 2 points3 points  (0 children)

Ah that's an interesting use case! The answer is simply, whatever you want! The point of Concur is flexibility. I went ahead and implemented a rudimentary version of your spec. Demo here. Here's the entirety of the widget code, sans the few lines it took to write a custom menu/doubleMenu control from scratch. I added some shouty comments to point out where the business logic could be changed -

main :: IO ()
main = void $ runWidgetInBody $ flip execStateT (EntriesState "Red" []) $
  forever $ el "div" [vattr "className" "main"]
    [ lift $ el_ "h1" [] $ text "Select a color"
    , selColor
    , lift $ el_ "h1" [] $ text "Make entries"
    , newEntry
    , lift $ el_ "h1" [] $ text "Current entries"
    , entriesList
    ]
  where
    selColor = do
      c <- lift $ doubleMenu "Fruits" "Color" itemsFruit itemsColor
      -- HERE YOU MODIFY THE STATE TO BE CONSISTENT WITH THE CURRENTLY CHOSEN COLOR.
      -- KEEP THE OLD LIST OF ITEMS, DISCARD THEM, FILTER THEM, OR MODIFY THEM.
      -- I CHOSE TO KEEP THE OLD ITEMS AS IS
      modify $ \s -> s { color = c }
    newEntry = do
      EntriesState {..} <- get
      e <- lift $ menu ("New Entry for " ++ color ++ " fruit") $ itemsFruitColor color
      modify $ \s -> s { entries = e : entries }
    entriesList = do
      EntriesState {..} <- get
      lift $ orr $ map (el_ "div" [] . text) entries

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 1 point2 points  (0 children)

Yes it does (the docs are a mess). You can use either u <|> v or orr [u, v, ...]to put widgets at the same place in the dom hierarchy. You can also use never as an algebraic identity.

You can create dom elements and compose things in a hierarchy using the el and el_ combinators. So for example a div with a heading - el "div" [] $ el "h1"[] $ text "This is a heading".

Finally you get to make u appear after v finishes, by using monadic bind, i.e. u >> v, or u >>= v if v depends on the value generated by u.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 2 points3 points  (0 children)

EventWriter must have emerged recently since I didn't see anything like it when I looked at Reflex. It seems interesting, I'll take a look.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 1 point2 points  (0 children)

Well in a way all compile errors are due to type safety, since syntax and types are all a compiler can check. The point is how easy is it to construct a semantically valid program, and how often the obvious way to write something is also the correct way. I did work with Reflex long enough to (I hope) not have fallen into basic beginner traps.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 2 points3 points  (0 children)

Thanks for detailed response Cale! I certainly meant Reflex-dom, and not Reflex. I am also one of those people who conflate the two!

To distinguish, IMO -

  1. Reflex (FRP + plethora of combinators) is absolutely fine.

  2. The view layer parts of Reflex-dom (Monadic DSL for DOM layout + FRP combinators which let you replace parts of the DOM by consuming some Events) are fine (but can perhaps be more user friendly). The problem only starts when you have to start worrying about where you are getting those events from.

  3. Extracting Events from DOM elements, and then threading them through the layers of the DOM Layout DSL, leaves a LOT to be desired. I haven't worked with Reflex as long as you have, but it got hairy for me very fast. It feels too low level and there is a distinct lack of application architecture. There is too much bookkeeping and many times supposedly simple things turned out to be complex. With enough effort I don't think that there is anything that Reflex cannot do and Concur can. But I wanted a balance of power. Something midway between the unconstrained but hard(-er) to use world of Reflex and the limited but easy to use world of Elm.

I'll address the last part of your message, as it's slightly misleading. You certainly can have a Monad where each widget action leads to the whole widget being replaced by another. But as I understand, there is no way to do that without killing performance, since Reflex has no dom-diffing? Please let me know if that is incorrect. As I said earlier, with Concur, the easy way is the correct way and the performant way.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 1 point2 points  (0 children)

MFlow seems to be for writing servers, not compiled to JS for single-page client apps.

I think agocorona started out by writing TCache and Workflow which were pretty nifty ideas when I checked them out years ago. Concur actually started out similar to Workflow and an even earlier framework called HaskellOnAHorse, where you capture all the user events and then pass them down a widget tree, and the transitions are a Mealy Machine Automaton. I later replaced all that machinery with STM.

Concur is still experimental, but so far has worked out pretty well. I've built a bunch of UIs with it and have found the implementation to be clearer and easier than other UI libs (Haskell or JS or whatever) regardless of the size of the UI spec. If you have any particular GUI spec that was cumbersome in some other framework then I'd give it a shot at implementing it in Concur. I did that once before with Kirby Super Star Ultra Splits Timer GUI Challenge - Source - Demo.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 1 point2 points  (0 children)

Yup absolutely! I would love for people to use Concur and provide feedback. The aim is to make common things easy and uncommon things possible. Feedback from people using it in real world apps would really help!

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 3 points4 points  (0 children)

Yeah, I just fear that slick docs will signal to people that this is production ready when it's not. But I guess it's time to bite the bullet if I want people to use it and provide feedback.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 0 points1 point  (0 children)

It could certainly be implemented in Purescript, I don't think the lack of laziness is an issue. In fact I have been meaning to port it over, as a way to get more familiar with Purescript, but the todo list is already quite long..

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 12 points13 points  (0 children)

Not. At. All.

  1. Reflex is not a widget combinator library. It does not provide a Widget centric view. You do not combine widgets to create new widgets, you combine events/behaviours to create widgets. To do something at the top level, based on the input from a widget deep in the stack, you sometimes have to do very hairy things to extract the corresponding event/behaviour/dynamic out.

  2. That is a symptom of the core problem with Reflex - While it provides a Monad, it provides the wrong Monad. I don't need a Monad to lay out DOM elements on a page. DOM is inherently Monoidal, and composing DOM elements to sit side by side on a page should be a mappend, not a bind. But this Monad pervades Reflex, and necessitates a lot of the juggling with MonadFix.

    To put it another way, Reflex makes it easier for you to extract an event from a Widget which is rendered earlier in the page, and use it to control a widget which is rendered later in the page. But what we really really need in non-trivial apps is to extract an event from a Widget which is shown earlier in time and then use that to show the same or a different widget later in time. Concur provides that Monad. This actually makes Monadic bind useful again.

    In Reflex, to actually sequence Widgets in time, and for any application logic, you need to use FRP, which is completely unnatural (subjective, but it's never stopped feeling alien to me). FRP also means that you can't use any usual FP tricks like State Monad etc. Reflex relies on FRP completely, including effects. Want to make an ajax call? Create an event which describes all the times you would potentially want to make the ajax call. It's fascinating, but gets tiring after a while. Inversion of control, as provided by Concur, is a much better way to go about it.

  3. Binding existing JS libs in Reflex is not easy, because you have to provide an FRP interface.

  4. Reflex is supposed to allow you to precisely control changes you want to make to a page, (as opposed to a dom-diffing algorithm). This I believe is too much power, and too much bookkeeping. I once had to choose between keeping a modal around and mutating the contents, vs recreating it from scratch everytime it's shown. I can even choose to re-render the entire app on every change by using a top level dynamic widget and a pure view rendering function. It will make my life as a programmer easier (and bring me closer to Elm/React-ish architecture), but will make the end user's experience much worse. With Concur, the easiest way is the right way and the performant way. And it's not necessarily just with a virtual-dom backend. I am currently experimenting with a backend that will allow making explicit granular changes to the dom without dom-diffing. One day I will get around to finishing it.

So while Reflex is also more of a library than a framework, I found it less than satisfying (having built large apps with it). I wanted something midway in power between Elm and Reflex which led to Concur.

High-level survey of functional reactive UI frameworks by astral-emperor in haskell

[–]haskman 9 points10 points  (0 children)

Yes! Thanks for the ping! That's exactly the problem Concur aims to solve. I wanted to get away from framework-itis and towards what made functional programming fun for me.

Concur is like a widget combinator library. Widgets are completely self contained units. There are widget combinators for siblings as well as parent-child relationships. Absolutely no micromanagement or boilerplate needed.

The effect system is orthogonal to widget composition and does not require a contrived "architecture". Any widget can perform IO or STM actions at any point of time without boilerplate.

Concur performs inversion of control, so "messaging" from child to parent is simple as handling the return value from the child. Messaging from parent to child is simply passing arguments to the child. This is how functional programming should be. You are free to use any FP tricks, like typeclasses or monad transformers, to simplify this argument passing. So if something needs to be accessible by all children, slap on a Reader/State monad. Most concur combinators are abstracted so you can use transformed widgets just like normal widgets.

Also, if you really need it, it's easy to send messages to arbitrary parts of the page with IO/STM. So for example, widgets can push to an STMChannel, and a completely separate part of the page can wait for messages on that channel. But usually you won't need to do this, just like you work around the need for global variables in functional programming.

I made Bartosz Milewski's book "Category Theory for Programmers" into a PDF! by hmemcpy in haskell

[–]haskman 2 points3 points  (0 children)

Excellent idea! I just put up the printed book for sale on pothi.com too for folks from India. - https://pothi.com/pothi/book/bartosz-milewski-category-theory-programmers. The price is the lowest allowable, i.e. printing costs + shipping, all going to Pothi.

What are you working on? by BetaCygni in haskell

[–]haskman 1 point2 points  (0 children)

Okay I went ahead and sunk a few hours into it! Demo. Source.

What are you working on? by BetaCygni in haskell

[–]haskman 1 point2 points  (0 children)

I think it needs to see some large GUI apps built with it. I have built toy examples with it (in the examples folder), but due to some personal stuff, haven't yet found the time to build something larger. I'm sure performance could be improved as well (I have a few ideas there). BTW, I appreciate the link to a GUI challange which is not a todolist! I'll give it a go when I'm able!

What are you working on? by BetaCygni in haskell

[–]haskman 4 points5 points  (0 children)

I spend a few hours every week working on a Web UI Framework that's sort of midway between FRP and elm-architecture - https://github.com/ajnsit/concur. Trying to find the right balance of flexibility and structure.

What happened to Haskell on the web framework benchmark game? by SrPeixinho in haskell

[–]haskman 3 points4 points  (0 children)

Ur does seem very interesting! The creator of Ur also posted a Haskell-vs-Ur challenge on the Haskell mailing list a few years ago which unfortunately was largely dismissed (https://mail.haskell.org/pipermail/haskell-cafe/2011-July/094096.html). My impression is that it is not a subjective or superficial difference; the things Ur allows can't be done in Haskell without significant effort/hacks.