Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Aug 14, 2019. It is now read-only.

Conversation

@philipcmonk
Copy link
Contributor

Refer to #1145 for a discussion of motivation and some alternatives.

This introduces a new synthetic rune =%. This:

=%  a=@  b  c
d

Expands to this:

%+  (b a=@)
  c
|=  a=@
d

The intended use case is monadic operations. Here's an idiomatic real-world example where I use it to boot two ships and have them |hi each other. Note that each of these actions requires sending moves to another gall app and receiving multiple responses from that app.

=/  m  (ph ,~)
^-  data:m
=%  [%booted from=@p]     bind:m  (boot-ship ~bud ~)
=%  [%boot-done from=@p]  bind:m  (check-ship-booted from)
=%  [%booted to=@p]       bind:m  (boot-ship ~dev ~)
=%  [%boot-done to=@p]    bind:m  (check-ship-booted to)
(send-hi ~bud ~dev)

This assumes you have defined an explicitly parameterized monad that looks something like this:

++  ph
  |*  a=mold
  |%
  ++  data  !!
  ++  return
    |=  =a
    ^-  data
    !!
  ::
  ++  bind
    |*  b=mold
    |=  [m-b=(data:(ph b) fun=$-(b data)]
    ^-  data:
    !!
  --

Since bind is associative and monads compose, you can refactor this test:

++  full-boot
  |=  her=ship
  =/  m  (ph ,~)
  =%  [%booted from=@p]     bind:m  (boot-ship her ~)
  =%  [%boot-done from=@p]  bind:m  (check-ship-booted from)
  (return:m ~)
::
++  hi
  =%  ~  bind:m  (full-boot ~bud)
  =%  ~  bind:m  (full-boot ~dev)
  (send-hi ~bud ~dev)

This sort of composability of operations that include sending and receiving moves to other apps is something we've never properly had. This rune provides a convenient syntax to do that.

You can also use this with wet gates if you prepend _. The gates also don't have to "bind" operations. For example:

=/  a=(list (unit @))  ~[`1 ~ `3 `4 ~]
=%  b=(unit @)  _murn  a
=%  c=@         _biff  b
?:  =(0 (mod c 2))
  ~
(some (scow %ub c))

The usual cautions around wet gates apply. As is usual with wet gates, some extra casts may be necessary. In this case, the (list (unit @)) and some lines are important to assign the correct types and faces.

If the above code is confusing, consider adding some casts for documentation. This is too many, but it gets the point across:

=/  a=(list (unit @))  ~[`1 ~ `3 `4 ~]
^-  (list tape)
=%  b=(unit @)  _murn  a
^-  (unit tape)
=%  c=@         _biff  b
^-  (unit tape)
?:  =(0 (mod c 2))
  ~
(some (scow %ub c))

Copy link

@belisarius222 belisarius222 left a comment

Choose a reason for hiding this comment

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

LGTM this is very cool

@ohAitch
Copy link
Contributor

ohAitch commented Apr 17, 2019 via email

@pilfer-pandex
Copy link
Contributor

Although I like this proposal more than the other one, for reasons I will discuss in more detail there, I think this is a bad idea. Please hold off on merging this until further discussion.

@pilfer-pandex
Copy link
Contributor

Okay, it looks like you closed the other one, so I'll move my comments here.

@cgyarvin
Copy link
Contributor

cgyarvin commented Apr 17, 2019 via email

@ohAitch
Copy link
Contributor

ohAitch commented Apr 17, 2019

Or possibly to %;? That's annoying to pronounce tho.

To elaborate on the rune category concern: : runes expand to cons, | runes expand to cores, ? runes expand to an outer nock 6, = runes expand to an outer nock 7(or nock 8 which is by definition a very thin macro around nock 7), % runes expand to an outer nock 9(or nock #), ~ runes expand to nock 10 11(grr), ^ runes expand away at runtime. Since ultimately the rune in the pr expands to %:, it's arguably a % rune; but it also declares a core and has a very nonstandard argument ordering, and the category for "does multiple things with weird control flow" is clearly ;
in particular ;: already does "weird %-", and ;~ does both "create a new core" and "using wet gate application for a particular pattern of monadic composition"

Copy link
Contributor

@pilfer-pandex pilfer-pandex left a comment

Choose a reason for hiding this comment

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

I have to say, I'm surprised and pleased. I expose you to Wadler for a few days, and here you are trying to add special syntax for monadic operations to Hoon. However, I want to urge restraint.

My biggest takeaway from #1145 is that everyone agrees that wet gates are incredibly cumbersome and difficult to work with. We have a few patterns for working around this difficulty, but these patterns make for repetitive code when trying to write in a monadic style.

I firmly believe more syntax is not the answer here, at least not yet. The answer is to fix wet gates. What will happen when we find we like applicative functors? More syntax? Alternatives? More syntax? Foldable and traversable?

What we need is a way to make generic functional programming work well. The main obstacle to this is the type system. In a world with HM type inference, you could just use ;: with your bind function, as I think you alluded to in the other pr. In such a world, I think we'd all more or less be opposed to the change suggested here.

I do understand that it will take time to create such a world, and in the meanwhile, you're stuck writing your aquarium code in a cumbersome way. I wish I had a quick answer for you, but I feel strongly this is not it.

@pilfer-pandex
Copy link
Contributor

It's also worth noting that when I was in college, I spoke with one of the original designers of Haskell, and he, to that day, was opposed to the addition of the do notation to haskell, saying it was a frivolous addition. For when

do
  x <- y
  foo
  z <- w
  bar

merely translates to

y >>= \x
foo >>
w >>= \z
bar

it's unclear what is gained by adding the first form directly to the grammar.

The fact that there is special syntax here can also lead beginners astray.

@pilfer-pandex
Copy link
Contributor

@benjamin-tlon

@ohAitch
Copy link
Contributor

ohAitch commented Apr 17, 2019

I disagree that wet gates are incredibly cumbersome and difficult to work with ^-^

They have some pitfalls, but can be very easily reasoned about mechanically, which I'll take any day over trying to puzzle out what types Hinley-Minler is failing to bidirectionally concoct properly.

it's unclear what is gained by adding the first form directly to the grammar.

Huh, for me it is perfectly clear: the number of glyphs in the code has been reduced by a factor of 3. This might sound somewhat hypocritical from a hoon perspective ofc, and I certainly concede that reserved words are significantly worse - but it doesn't make the gain any less obvious.

@philipcmonk
Copy link
Contributor Author

Please hold off on merging this until further discussion.

Will do. I think Joe has thoughts too.

Or possibly to %;? That's annoying to pronounce tho.

%; is pronounced "cenmic" nowadays, which isn't too hard to pronounce.

The argument for putting it in = is that it feels like "subject modification" when you're using it. It feels a lot like =+. However, that may be misleading; it's certainly significantly more complicated than any of the others, and there may be flow-of-control implications. That's probably enough to warrant ; status.

Unfortunately, most of the good ; runes are taken for sail. ;@ ;$ ;^ ;& ;| ;\ ;, and ;. are available. None of them seem particularly evocative, so perhaps ;| is the best.

I firmly believe more syntax is not the answer here, at least not yet. The answer is to fix wet gates.

Wet gates have been recognized to be a pain for at least 5 years, and they're not fixed yet, despite multiple attempts to do so. I don't claim that it's impossible to fix them -- eg I think HM would likely fix the problems we've had with them, which is not to say it's necessarily a good idea -- but I don't think we should delay anything on the hope that they'll be fixed any time soon.

What will happen when we find we like applicative functors? More syntax? Alternatives? More syntax? Foldable and traversable?

The same could be said of =^ and the state monad, and everyone loves it. One of hoon's design principles is that often can get away with not implementing a fully general solution if you just implement the one or two most useful forms of it. I don't think we're generally in danger of going wild and adding a new rune every time we find a new pattern we like.

As for why this specific pattern is likely to be useful, my answer is that (1) we've often wished for monads, as evidenced by the fact that every 6 months somebody goes and half-implements them in a specific context and (2) it's the first pattern we've used that gets rid of decomposed state machines, and decomposed state machines are a fairly urgent problem (not in a threshold sense, it's just that programming in arvo is drastically slower and more bug-prone than it could be, and there's a significant cost to that).

What we need is a way to make generic functional programming work well. The main obstacle to this is the type system. In a world with HM type inference, you could just use ;: with your bind function, as I think you alluded to in the other pr. In such a world, I think we'd all more or less be opposed to the change suggested here.

It's not clear to me that better type inference will give a better experience for this than what this PR gives. Honestly, I like that you have to specify your bind operation and the intermediate types. You needed to give them names anyway, why not types? I feel that this style is significantly more readable than do notation because it includes the types. When I look at Haskell code in do notation, I don't know what anything is doing or what type it is because we don't explicitly name bind and the types.

However, my point is not that we shouldn't switch to HM or otherwise improve type inference -- that's a whole other discussion which we're not ready to have yet. My point is that we shouldn't hold off on language improvements because there might some day be a better way to do it. Almost everything could reasonably be claimed to be close to changing, but you can't let that stop you from pushing the current system to its limits. For one, it bogs down progress by making you program more painfully, but more importantly you can't make a good decision as to whether a significant change like HM is worth it without pushing the current system to its limits.

My general rule is that it's best to do things the right way given the current conditions until such time as a decision to change those conditions has actually been made.

Additionally, even if we fix wet gates such that ;: is all we need, it's very easy to just search for ;| and convert it to a ;:. Certainly much easier than searching for %+ and finding which ones are binds. If we agree that the occasional monad is good, then making them easier now will only make it easier to understand why improved type inference simplifies the language (we'll be able to get rid of runes).

it's unclear what is gained by adding the first form directly to the grammar.

I don't know idiomatic Haskell, so I can't judge whether do notation is a good idea there. But in hoon as it stands now, the expanded version is significantly more cumbersome and less readable.

@philipcmonk
Copy link
Contributor Author

@joemfb suggested ;< where the < is evocative of =< and do notation. It suggests something like "do what's on the right and assign the result to the first argument", and makes the most sense in tall form like this:

;<  a=@  bind  data
next-stuff

It's not great, but I think it's better than ;|, which would be my second choice. ;> appears to be sail for div, so we don't have to worry about preserving < and > for a symmetric operation -- that symmetry has already been broken for ; runes.

@philipcmonk
Copy link
Contributor Author

@pilfer-pandex You said IRL that you're less opposed to this than before; do you have any further objection to me merging this once I get feedback from people about naming it ;<?

@joemfb
Copy link
Contributor

joemfb commented Apr 18, 2019

I'm in favor of this addition. To briefly recap some offline conversations:

I don't think it makes sense to defer this until we've "fixed wet gates". I agree that the experience of parametric polymorphism is desirable, and that wet gates don't exactly provide it. I still think that wet gates are useful, and that the experience of using them can be improved, but I'll say no more until I have so demonstrated (and in the meantime, +bake is a good solution for many use-cases). Any more disruptive change in this area will take awhile to materialize, and there are improvements to be realized via this new rune in the meantime.

I sympathize with the idea of =% given the feel of this rune, but it's a bit too much -- the last sub-expression of all the other = runes is an arbitrary hoon evaluated against the new subject a la =>, the last sub-expression here is a function body. ; is the appropriate rune family for the reasons given above, and ;< seems the best remaining rune therein.

I do find this macro-expansion to be a little confusing, but I don't see any obvious way to improve it. It's certainly not any more confusing than ;~ already is, and it may be that I'll find it intuitive once I've used it in anger.

@Fang-
Copy link
Contributor

Fang- commented Apr 18, 2019

I don't have anything more substantive to say than "this seems really neat!"

But I do have a question! Is there a strong argument for the current wing ordering? ie, instead of:

=%  [%boot-done to=@p] bind:m  (check-ship-booted to)
(send-hi ~bud ~dev)

why don't we have:

=%  bind:m  [%boot-done to=@p]  (check-ship-booted to)
(send-hi ~bud ~dev)

That seems closer to the expansion, and ;~ also puts the "glue" operation first.

@philipcmonk
Copy link
Contributor Author

I could see it going either way, but this introduces a new variable to your subject, defined by the given type. As far as I can think, every time we do that it's the first argument to the rune except =-, which is widely considered a mistake (and =; was introduced exactly to remedy this) and =<, which replaces the entire subject and practically screams at you "we're reversing the normal flow".

Additionally, putting the type second splits the bind from its argument. The type is an annotation, like ^-. It happens to be used to parameterize the bind, but that's because we don't have true polymorphism, not because it's an argument that changes anything about bind.

;~ has to put the glue operation first because it's n-ary, unlike here.

@philipcmonk philipcmonk mentioned this pull request Apr 23, 2019
@jtobin
Copy link
Contributor

jtobin commented Apr 23, 2019

I think this is fine to merge in, based on how the above discussion unfolded. @philipcmonk was there anything else to add here?

@pilfer-pandex
Copy link
Contributor

Based on further discussion in person, I think this is ok in the short term.

@philipcmonk philipcmonk merged commit e7ac772 into next Apr 23, 2019
@jtobin jtobin deleted the philip/tis-cen branch April 25, 2019 06:51
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants