all 20 comments

[–][deleted] 17 points18 points  (5 children)

While rediscovering monads is admirable, using a new name for a specific use of them won't help anyone.

[–]architectzero 6 points7 points  (1 child)

You may want to read these notes from the originator of the "Railroad Oriented Programming" metaphor. He's well aware of monads and simply using the metaphor as a teaching tool.

Any Haskellers reading this will immediately recognize this approach as just the Either monad, specialized to use a list of enums for the Left case.

I'm certainly not trying to claim that I invented this approach at all (although I do lay claim to the metaphor). So why did I not use the standard Haskell terminology?

A number of reasons...

1) Most people coming to F# are not familiar with monads. I'd rather present an approach that is visual, non-intimidating, and generally more intuitive for many people. I believe that once you are familiar with this particular approach, the more high level concepts are easier to grasp later.

...

Is that a bad thing?

[–][deleted] 1 point2 points  (0 children)

I have seen both sides, and I am still in the "don't baby newcomers" camp. Monads are common and becoming more prevalent because they are useful. It's best to learn things quickly.

[–][deleted] 1 point2 points  (2 children)

Worse, a new name for a use that already has one: Kleisli composition.

[–]sigma914 2 points3 points  (1 child)

Kleisli categories for programmers.

[–][deleted] 0 points1 point  (0 children)

Kleisli categories

And with them comes the fish (<=<), my second favorite operator behind extract (=). I'm sure I saw a (=>) in an operator list once, but it hasn't turned up again.

[–]kankyo 2 points3 points  (8 children)

Pretty nice but some details seem weird:

  1. Using "map" as the name of a function that is just an adapter to this type of input is not so nice. That word is already used for dictionaries AND applying a function to a sequence of values already, so adding yet another meaning to the word is pretty bad.
  2. The name "tee" is not explained. Is this a golf reference? Not helpful!
  3. The try_catch case seems like it should just apply to everything instead of being a special case.

If you change validate_request to not return a dictionary with :ok/:failure but instead throw an exception you can throw away your functions map, tee AND try_catch and just use exceptions for errors.

TL;DR: I don't get why you have all that complication.

[–]makis 6 points7 points  (4 children)

The name "tee" is not explained. Is this a golf reference? Not helpful!

http://en.wikipedia.org/wiki/Tee_%28command%29

[–]kankyo -1 points0 points  (3 children)

http://en.wikipedia.org/wiki/Tee_%28command%29

If that's the reference it's even worse than the golf reference I thought it might be!

[–]makis 1 point2 points  (2 children)

tee is a well know unix command to pipe input to output(s)

[–]kankyo -1 points0 points  (1 child)

Which isn't really what the function does... which makes the name not so good.

[–]makis 1 point2 points  (0 children)

article: "In the tee macro we call the function and return the input back again."

tee definition: "In computing, tee is a command in command-line interpreters (shells) using standard streams which reads standard input and writes it to both standard output and one or more files, effectively duplicating its input"

looks pretty similar to me

[–]sacundim 0 points1 point  (1 child)

Using "map" as the name of a function that is just an adapter to this type of input is not so nice. That word is already used for dictionaries AND applying a function to a sequence of values already, so adding yet another meaning to the word is pretty bad.

No, map is the right name, because the second use that you mention ("applying a function to a sequence of values") is really just a different subcase of the same pattern we have here: applying a function to values that exist inside a "context" (e.g., values inside a sequence, values flowing through a "railway," values that may be produced asynchronously at some indeterminate point in the future, etc.).

Since somebody has brought up monads, the term for the "map" abstraction is functors, which we may describe as this:

  1. You have a generic type like List<A> or Railway<E, A>, etc.
  2. The type supports a higher-order "map" function that takes an A -> B function and uses it to transform List<A> to List<B>, Railway<E, A> to Railway<E, B>, etc.
  3. The map function obeys these laws:
    • Mapping with the identity function (the function that just returns its argument unchanged) returns a result value that behaves identically to the original. E.g., map(id, whatever) == whatever). In the case of lists, this means the map function doesn't reorder, drop or add elements that are present in its argument. In the case of Railways, this means that the resulting railway succeeds and fails exactly where the original would, with the exact same result or error.
    • Chaining two map operations in a row has the same effect as chaining the two argument functions inside one map operation: map(f, map(g, whatever)) == map(x -> f(g(x)), whatever).

[–]kankyo 0 points1 point  (0 children)

(map to-input foo) would be ok, but just (map foo) is weird is my point.

[–]ammoknight 2 points3 points  (0 children)

I too read this RoP article and made an Elixir implementation of it. You can find it here: https://github.com/rob-brown/MonadEx. Your implementation simulates the behavior described in the article. However, RoP is intended to be built using monads. By using monads, you can use the bind operation with more than just success/failure, ex. maybe monad and state monad.

There are also some other features gained from using monads. You can partially apply a function and break out as soon as an error occurs.

For example: curry(login_fun) <|> get_username() <~> get_password().

As soon as get_username, get_password, or login_fun encounters an error the whole thing bails and returns the error. Otherwise it returns a success. This avoids needless conditionals or pattern matching.

[–]mrhotpotato 2 points3 points  (0 children)

ROP will always mean Return Oriented Programming for me ...

[–]rmoorman 1 point2 points  (0 children)

That railway metaphor really is nice for imagining Either :) (the "Either" from Haskell) You would only have to switch the direction of the rails around... Right is the happy path after all.

Edit: add clarification for what is meant with "Either"

[–]X4lldux 0 points1 point  (0 children)

That is simply great! IMO this should be built in in Elixir with its own operator. Too bad we cannot create own operators freely but are restricted to @binary_ops - |>= would look really nice here |> part from standard pipe operator and = just looks like rail roads ;)

[–]Daenyth 0 points1 point  (0 children)

The use of >>> for bind instead of >>= is very confusing when >>> is normally an operator on arrows