Skip to content

🔋 Add Miso.Lens module#854

Merged
dmjio merged 13 commits intomasterfrom
lenses
Apr 6, 2025
Merged

🔋 Add Miso.Lens module#854
dmjio merged 13 commits intomasterfrom
lenses

Conversation

@dmjio
Copy link
Copy Markdown
Owner

@dmjio dmjio commented Apr 6, 2025

🍜 Miso.Lens

Miso applications consist of very simple state management (using the Effect State monad). Given the necessity (and convenience) of lenses defined in terms of MonadState (.=, %=, etc.), the importance of state management in web / mobile applications, the shortcomings of vanilla Haskell records in regards to updating deeply nested state, and the dependency footprint of existing lens libraries, we believe it is a good idea to include our own Miso.Lens abstraction that is lightweight, powerful and accessible for new users.

The documentation will describe the motivation, formulation and usage of Lens in terms of building miso applications (specifically when using the update function).

This module is meant to make miso more "batteries-included" and reduce dependency footprint, along with exposing new users to lensing, but with a seatbelt. This lens formulation is very simple (simpler than microlens and the original lens library). It exposes an identical interface (and keeps fixity parity) of existing combinators in lens.

This module is not meant to "compete" with other lens libraries like lens and microlens that are based on Van Laarhoven formulations and are much more flexible, and have different project goals. With that said, for user's writing miso applications, this module should meet at least 98% of your needs regarding state management of model in your applications.

Some combinators have been elided for brevity, others because of conflicts (e.g. view is omitted because of conflicts with App { view }, and is also irrelevant since it's defined in terms of MonadReader -- we only use MonadState for Effect). We also believe exposing an identical interface to existing lens libraries will allow LLMs to produce identical code, and it will make it easier for new users to transition to more mature lens libraries if and when they decide.

Why not just re-export another dependency?

Since most miso applications are cross-compiled to Web Asembly and JavaScript it is important to have a small dependency footprint. If a user wants something more flexible then it will come at the cost of payload size in their application. Exposing a Miso.Lens provides a good power-to-weight ratio and optimizes for minimal payload.

What are the pros of using this module over a lens library like microlens

Convenience and simplicity, this is a simpler formulation, and its semantically equivalent. It has a smaller dependency footprint, it's also more approachable for beginners (which is a goal of the project, to learn Haskell through web development). It is also extensible (you can define your own Lens combinators simply since we re-export everything).

What are the cons of using this module as opposed to lens?

This formulation might be less efficient when it comes to updating very large and deeply nested records when compared to an existing lens library, but this needs to be benchmarked on JS / WASM platforms. Miso's new Component feature also amortizes this cost, since now Model do not need to be deeply nested, but are locally-stateful and isolated, and updated independently in separate threads.

Miso.Lens is also not as flexible and is not meant to be a general purpose library, but only a more powerful way to manage state in miso applications (while maintaining API parity with the ecosystem for the subset of lens combinators it exposes). The MonadState combinators for lenses are very convenient and powerful and miso would like to make them first class and exposed by default from import Miso.Lens.

  • Updates README.md and examples/simple/Main.hs to use Miso.Lens
  • Documents combinators, smart constructors, haddockage
  • Add a Generic combinator field (might be best for follow-up PR)

dmjio added 7 commits April 5, 2025 22:30
Miso applications consist of very simple state management in a State
monad. Given the shortcomings of Haskell records it is incumbent upon
us to provide users with a simple way to get / set their records.

This module is meant to make miso more "batteries-included" and reduce
dependency footprint, along  with exposing new users to lensing, but
with a seatbelt. This lens formulation is very simple (much simpler than
microlens and the original 'lens' library). It exposes an
identical interface wih fixity parity of existing combinators in microlens.

Some combinators have been omitted for brevity, others because of
conflicts (e.g. @view@ is omitted because of conflicts, and @effect@
only operates in 'MonadState'). We are attempting
to make an identical interface (so as not to confuse LLMs) and to
make it easy for users to upgrade to more mature, sophisticated lens
libraries at their leisure.

- Update README.md and examples/simple/Main.hs to use Miso.Lens
- Document combinators, smart constructors, haddockage
@dmjio
Copy link
Copy Markdown
Owner Author

dmjio commented Apr 6, 2025

Will add Generic field in follow-up PR

----------------------------------------------------------------------------
-- | Field class
class Field (name :: Symbol) record field | record name -> field where
  field :: Proxy name -> Lens record field

  default field
    :: ( Generic record
       , GField name record field (Rep record)
       ) => Proxy name -> Lens record field
  field name = gField name (Proxy @(Rep record))
----------------------------------------------------------------------------
-- | Generic Field
class GField name record field rep | record name -> field where
  gField :: Proxy name -> Proxy rep -> Lens record field

@dmjio dmjio merged commit 4099cbe into master Apr 6, 2025
3 checks passed
@dmjio dmjio deleted the lenses branch April 6, 2025 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant