Skip to content

Explain why Ref functions return Effect (Ref s) rather than Ref s #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/Effect/Ref.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,40 @@
-- |
-- | _Note_: `Control.Monad.ST` provides a _safe_ alternative to `Ref` when
-- | mutation is restricted to a local scope.
-- |
-- | You'll notice that all of the functions that operate on a `Ref`
-- | (e.g. `new`, `modify`, `modify_`, `read`) return an `Effect (Ref s)`
-- | and not a `Ref s`. Here's what would happen if the `Ref s` was not
-- | wrapped in an `Effect`:
-- | 1. In some situations, one could violate referential transparency.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think putting it this way downplays the importance of Effect to retain referential transparency here; I'd argue that a Ref which could be read or written outside of Effect would end up violating referential transparency in almost all situations, since every time you write a new value to a ref, you change the result of reading from it. I also think we could do more to explain what referential transparency is, as if the reader isn't already familiar with the term, these docs probably won't make a lot of sense (and if they are familiar with it, they likely understand why reading or writing a Ref is effecful already anyway).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think putting it this way downplays the importance of Effect to retain referential transparency here

So... change the wording to "In all situations, one will violate referential transparency" ?

I also think we could do more to explain what referential transparency is

Odd. Usually you want docs to explain less and have readers look up terms elsewhere. Why the change here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

change the wording to “in all situations...”?

Well, that’s not quite accurate either - it’s definitely possible that a Ref is initialised and then never written to, in which case all of the reads would return the same value and you wouldn’t observe a violation.

Why the change here?

I want docs to only address things that are directly relevant to the topic at hand; in this case, referential transparency is directly relevant to why refs are effectful. I also expect that it only needs a sentence or two; if it needed more than that, I’d agree that it should go elsewhere. If you can point to an example where you think I’m being inconsistent I’m happy to reconsider.

-- | 2. One can violate the type system by giving a `Ref` a polymorphic type
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reflecting on this a bit more, I think this particular point is specific to Ref.new, and is probably best documented there (sorry).

-- | and then specializing it after the fact.
-- |
-- | In regards to the first, consider the below example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the topic of why using Refs is effectful in general, I think it will probably be more persuasive to use a simpler example where a Ref is created, read from, written to, and then read from again: that's likely to be easier to understand than this example, which is a bit more subtle.

-- | ```
-- | let
-- | x = Ref.new “hi”
-- | first = Tuple x x
-- | second = Tuple (Ref.new "hi") (Ref.new "hi")
-- | ```
-- | In the above code, `first` holds the same reference twice whereas `second`
-- | holds two different references. Thus, it invalidates referential
-- | transparency. If `first` was referentially transparent to `second`,
-- | then `first` would hold two separate references just like `second`.
-- | By making `Ref.new` return an `Effect`, we return an _action_. Each time
-- | this action is called, it will create a new separate reference that is
-- | initialized to the given value.
-- |
-- | In regards to the second, consider the below example:
-- | ```
-- | let
-- | ref :: forall a. Ref (Maybe a)
-- | ref = Ref.new Nothing
-- | in
-- | Tuple (ref :: Ref (Maybe Int)) (ref :: Ref (Maybe String))
-- | ```
-- | The Tuple stores the same reference twice, but its type is `Maybe Int` in
-- | one situation and `Maybe String` in another.
module Effect.Ref
( Ref
, new
Expand Down