With do(:)?

True! The String handling in Elixir is top-notch! Fixed all the UTF8 horror I dealt with in Erlang!

Structs are interesting, but I’m too used to records, they seem like a decorated map to me just like records decorated tuples, except that it is easy enough to pattern match on the :__struct__ key as it is. ^.^

Protocols are single-dispatch, they have great uses, like the inspection protocol and such, but I would love a multi-dispatch version of them (*hint*hint*). ^.^

Honestly I’d think protocols could be generalized out to a multi-dispatch pretty easily.

Though to be entirely honest, I’d love it if the language was typed, protocols would be useless then as you could just have auto-witness dispatching with no run-time cost. ^.^
That would probably be a new language though. :slight_smile:

But yeah, I forget about Strings, they are awesome. I’m still in my Erlang and C++ habit of *DO*NOT*TOUCH* unicode, I just read it from files (translations and such based on keys read in to ETS and so forth) and just do simple moving around of data without actually transforming it. I still do not even, say, do something as ‘simple’ as upper-case strings in my applications due to how strongly this has been ingrained in to me. ^.^;

/me rates macros > protocols for sure though

1 Like

Of course, Elixir’s most magnificent feature is the &foo(&1,bar) shorthand function syntax :smile:!

1 Like

pipe is handy too…

1 Like

I really, really miss that in Erlang…

That’s not quite true. Protocols are about open ad-hoc polymorphism and types do not necessarily give you that. The point of protocols is exactly that the call-site may not know where the implementation is at compile-time so runtime work needs to be performed (unless of course you recompile the whole project once new implementations are added, similar to protocol consolidation, although that impacts incremental compilation).

1 Like

I just wish I could write &foo(bar).

2 Likes

Seriously?

While it may be a welcome addition to the toolbox, I always felt that the pipe was “a poor man’s path” to the effect of function composition - now granted it does have a nice way of dealing with those pesky extra parameters.

And even though Erlang doesn’t support function composition out of the box, how hard is:

-spec compose(F,F) -> F when F :: fun((T) -> (T)), T :: any().
compose(F,G) ->
    fun(X) -> G(F(X)) end.

Using partial application (via closures) you can DIY a function pipeline of any complexity to your heart’s content.

Heck, you can even practice all this in JavaScript!

I’m constantly amazed how often it takes syntactic sugar to convince some people to adopt beneficial practices (apparently if it’s a little more work/more boilerplate it’s simply not worth practicing).

3 Likes

(What do you mean exactly by, the ‘effect of function composition’?)

But it’s still ugly when you have a whole bunch of |>'s piped together, right?

Haskell is super convenient to do,

let (|>) x f = f x

which might be instructive, but I love love love, the way people have framed Elixir as the ‘|>’ language.

Which shows that sometimes it’s not the facts of the story but the way you retell it that counts…

To elaborate on what José said. Even if you recompile the project when you add implementations and have strong typing you still need dynamic dispatch for ad-hoc polymorphism. C++ and Haskell are typed languages and they still have dynamic dispatch for polymorphism.

1 Like

The pipeline itself does not produce a reusable artifact like function composition does (i.e. the composed function) even though in effect you are just weaving/threading some functions together to accomplish some larger unit of work. To go that step further the pipeline itself needs to be wrapped in a function (which contains nothing but the pipeline).

For all I know it could be a good practice to always factor out pipelines into their own separate functions - as it forces you to actually name what they are doing (declarative) rather than showing you how they are doing it (imperative).

(Shorter functions, more opportunities to use def fun(args), do: expression :icon_biggrin: )

Yes, but Haskell was originally designed as a research and teaching language - so there was considerable motivation to minimize the friction (cognitive dissonance?) between the language syntax and the “big idea” being conveyed - i.e. the primary importance is to understand the concept, not the Haskell syntax.

Francesco Cesarini:

One question I got once : We’re learning Haskell and you’re giving us a lecture about how Erlang’s being used in the real world. Why are they actually teaching us Haskell, and not Erlang?

And a natural answer there was, you’re here to learn how to learn.

Another example:

And yet I never got the feeling that a large part of the Java community really embraced the “objects as closures” concept - but instead was content to just complain and yearn for whatever the next language release spoonfed them.

Ultimately higher-order functions, higher-kinded types are all nice and dandy but part of the experience is to become a “higher-order” programmer who isn’t trapped or constrained by the syntax of any particular language, as long as the language provides the necessary lower-level building blocks.

3 Likes

Implicit Witnesses do the same thing. Basically at all function calls in the call path there is an implicit hidden argument (not really hidden) that holds the witness, then calls to the passed in argument go through the witness. All calls handling it do not know what they are dealing with, only the calls that it can handle that the witness must fulfill. :slight_smile:

In elixir’y syntax without any magical implicit bindings (everything explicit, this is how OCaml does it for example, although OCaml is adding in implicit support for it ‘soon’):

First let’s define our interface, let’s make a generic to_string that anything can support, this should probably be a behaviour or so, but I’m going for brevity here:

defmodule ToString do
  def to_string(value, tswitness), do: tswitness.to_string(value)
end

Well that is the basic interface. You can of course have more functions and so forth.

Next, let’s fulfill it for some basic types, how about strings and integers:

defmodule StringWitness do
  def to_string(str), do: str
end

defmodule IntegerWitness do
  def to_string(i), do: Integer.to_string(i)
end

They know what they are so they ignore their witness. With Elixir we could even make a single module that can fulfill ‘many’ types, so let’s do one for floats and atoms:

defmodule MultiWitness do
  def to_string(f) when is_float(f), do: Float.to_string(f)
  def to_string(a) when is_atom(a), do: Atom.to_string(a)
end

Notice that the Elixir built-in modules of Float, Atom, Integer, and others already fulfill this behaviour, we could use them straight if we want (OCaml, for example, does this same pattern, the ‘default modules’ fulfill a lot of the basic witness behaviours).

To use it we have to define the witness at the same place that we define the value and just carry it around with it. With something like Implicit Modules in OCaml, this carried around value becomes implicit, you do not need to manually define it and it can be grabbed from whatever is in scope that fulfills it for the value, however with using it explicitly (which you could still do with implicit support) you can instead override it, so you can define your own special form of it to, like let’s make another integer one:

defmodule MySpecialIntegers do
  def to_string(i), do: "Special Integer: #{Integer.to_string(i)}"
end

And we could use them each like (try doing this with protocols I dare you without wrapping the value inside something else like a struct or tagged tuple or something):

iex> ToString.to_string(42, IntegerWitness)
"42"
iex> ToString.to_string(42, MySpecialIntegers)
"Special Integer: 42"

Same with everything else we’ve defined earlier too:

iex> ToString.to_string("A string", StringWitness)
"A string"
iex> ToString.to_string(6.28, MultiWitness)
"6.28"
iex> ToString.to_string(:an_atom, MultiWitness)
"an_atom"
iex> ToString.to_string(:an_atom, Atom) # Normal Elixir module!
"an_atom"

Now so far this is stupid, we could easily just call the witness straight, like MultiWitness.to_string(6.28) or whatever, the point of it is carrying the ‘how’ to operate on the value as you pass the value around, so to show this let’s make a composite witness (OCaml of course has all kinds of nice helpers and such that could be done here too, and Implicit Modules make this pretty well free, but being explicit here to show how it works ‘internally’), how about a struct, and let’s fulfill the ‘default’ to_string internally to it too, in fact let’s do 2 struct’s, one that knows its internal value type, and one that does not, so you can easily compare:

defmodule Tester0 do
  defstruct blah: 42
  def to_string(%Tester0{blah: blah}), do: Integer.to_string(blah) # Could also do ToString.to_string(blah, Integer), but eh
end

defmodule Tester1 do
  defstruct bloop: nil
  def to_string(%Tester1{bloop: bloop}, {Tester1, bloopWitness}), do: "Tester1: #{ToString.to_string(bloop, bloopWitness)}"
end

And you can use it by just giving ‘arguments’ to the witness as a tuple:

iex> ToString.to_string(%Tester0{}, Tester0)
"42"
iex> ToString.to_string(%Tester1{bloop: 42}, {Tester1, Integer})
"Tester1: 42"
iex> ToString.to_string(%Tester1{bloop: 6.28}, {Tester1, Float})
"Tester1: 6.28"
iex> ToString.to_string(%Tester1{bloop: 42}, {Tester1, MySpecialIntegers})
"Tester1: Special Integer: 42"

Or let’s make one that stringifies ok/error tuples:

defmodule OkErrorTuple do
  def to_string({:ok, ok}, {OkErrorTuple, okWitness, _errorWitness}), do: "Ok: #{ToString.to_string(ok, okWitness)}"
  def to_string({:error, error}, {OkErrorTuple, _okWitness, errorWitness}), do: "Error: #{ToString.to_string(error, errorWitness)}"
end

And let’s use it:

iex> ToString.to_string({:ok, 6.28}, {OkErrorTuple, Float, StringWitness})
"Ok: 6.28"
iex> ToString.to_string({:error, "An error message"}, {OkErrorTuple, Float, StringWitness})
"Error: An error message"
iex> ToString.to_string({:ok, 42}, {OkErrorTuple, Integer, StringWitness})
"Ok: 42"
iex> ToString.to_string({:ok, 42}, {OkErrorTuple, MySpecialIntegers, StringWitness})
"Ok: Special Integer: 42"
iex> ToString.to_string({:ok, %Tester1{bloop: 42}}, {OkErrorTuple, {Tester1, MySpecialIntegers}, StringWitness})
"Ok: Tester1: Special Integer: 42"
iex> ToString.to_string({:ok, %Tester1{bloop: 42}}, {OkErrorTuple, {Tester1, Integer}, StringWitness})
"Ok: Tester1: 42"

And it can be composed and combined however you want. You can include multiple witnesses for different purposes in, say, via a map or multiple arguments, all sorts of styles are enabled by this.

This is also precisely how typeclasses are implemented in Haskell for example (very implicit), and how they are ‘emulated’ in OCaml (explicit). You can use default actions, override them just by passing a different module, etc… etc…

/me also thinks this is a great use of tuple-calls on the BEAM, really really dislikes people trying to get rid of tuple-calls…

So yes, the point of witnesses is that the call-site may not know where the implementation is at compile-time, however runtime work does not need to be performed and no pre-compilation is necessary. The only thing needed is to pass a witness around, and I truly think there is some way to bake implicit witnesses in to Elixir to become as succinct as they would be in Haskell, they’d probably just need some form of language change that I’ve not really looked in to yet (I’m fine with passing explicit witnesses around and in fact I do in many of my projects). This has no hit on incremental compilation; it does not require pre-compiling monolithic protocol modules; it supports multiple dispatch just fine (admittedly by making the caller choose which path, but the callee does not care at all, just like with protocols); and you can change what it should do on a call-by-call basis as well without needing to change callee at all (see the above Integer/MySpecialIntegers example, changing the implementation of the action without changing the callee or the value).

Yes, I like witness, I do like implicit witnesses a little bit more though. ^.^

Pipe in elixir is a slightly reduced form of traditional piping in functional languages, but it still does a lot of the capability. I do wish the Elixir pipe was a little bit more powerful though (whoo monads could be easily supported!). The main difference is that elixir piping takes a function call and magically moves the left to be the first argument in the right, where a traditional piping takes the left and ‘calls’ the right with it (putting it in the last position), this means that all the calls are function passing rather than function call, the elixir macro just moves arguments around but the traditional piping ‘does’ the call itself (which I did in MLElixir if you saw that thread, that is traditional piping, ‘inside’ elixir). I wish Elixir used traditional piping even if that meant ‘wrapping’ the normal call syntax. It would have made anon functions and currying much easier and would have ‘unified’ how to call things in pipes then.

There are Erlang libraries that add all that for you too, and a parse transform I remember using a decade ago that added all sorts of cool auto-currying and such. :slight_smile:

Now more easily done in Elixir via libraries and macros, which @expede on github has already done for us. :slight_smile:

Very true, I’m constantly amazed at how people think typeclasses and monads are hard, but as shown above with witnesses/typeclasses and I could easily demonstrate here with monads in elixir (if anyone is curious), they are stupid-easy. People just do not learn anymore, school’s do not teach programming well anymore (I like to think I teach better in my classes than the great majority), and tutorials online are mostly ignored that talk about this.

They do, but a witness is a single indirect function pointer call on-the-metal, which is well optimized by CPU’s, in addition OCaml goes through a whole host of optimizations to inline and make some optimized call paths for specific types based on usage that gets rid of that indirect call in 95% of cases, making it either a direct call or inlining it entirely if simple enough. This is O(1) regardless, even protocols are O(N) as they are implemented now in Elixir as it has to compare the types through the compiled protocol to find the one that matches by using the guards and head matching (which is serialized when guards are added, though it turns more in to an O(log(n)) tree when there are no guards, still not O(1)).

I prefer to do that, I have this one file that has about 40 little functions that just pipeline and some larger functions that take their results into named bindings to munge together. ^.^

If I can use , do: then I do use , do: , forces me to keep better expression purity, which forces functions to be more simple, and thus more readable and easier for my mushy brain meats to parse.

This is why I like to use OCaml has examples instead, it can do anything Haskell can, with significantly fewer constructs, though you often have to be explicit, which also means no magic. Way too many people think things like typeclasses are magical when they are nothing of the sort.

Indeed, I just re-implemented the back-end of typeclasses above, no macro’s or any magic of the sort. ^.^

4 Likes

I believe this might be rooted in the observation that Java is a class-oriented language rather than an object-oriented language, as in Java we cannot morph the structure of an object in real time: everything has to go through the class specification.

Can you point to some literature on witnesses? Your examples of witnesses doesn’t really seem like ad-hoc polymorphism since you actually have to pass around the implementation and you have to anticipate what protocols are going to be used on the value which kind of loses the whole point of polymorphism.

1 Like

True - but while you cannot morph instances, you can use them to construct other ones based on different classes.

And Java was even back then already heavily relying on IDE "life-"support but I’m not aware that anybody was creative enough to add automatic interface refactoring to generate the interfaces needed by consumers while tacking them on to classes which could support them - in order to actually comply with the interface segregation principle.

In this context I find Scott Wlaschin’s observation incredibly apt:

If we take the single responsibility principle, which says: “only one reason to change”, interface segregation principle: “don’t contaminate interfaces with too many things” - if you take that too the extreme what do you get?

You get a rule that every interface should only have one method!

If it only has one method, it’s only got one reason to change and I guarantee you’re not contaminating your interfaces … now what’s interesting about that is that an interface with one method is basically a function type.

I also suspect that for many individuals Java’s capabilities and limitations simply defined and constrained what OO is - when in fact they should have been investing in understanding SOLID.

1 Like

I think I feel some bitterness in you :slight_smile: .

Let me argue that while Monads are not at all hard, they are -by definition- inherently highly abstract, which means that it takes quite a lot of effort to grasp them, especially when someone has to un-learn something. The same, albeit slightly less severe, can be said of Typeclasses.
The fact that Monads are hard to grok but when you understand them you are amazed how simple they are is probably the the source of the ‘Monad Tutorial Fallacy’.

As for what they teach in schools and universities: I would also like them to focus more on Functional Programming, but unfortunately, this is the Bandwagon Effect in practice: People learn Object-Oriented languages+concepts because that is what the industry uses, and the industry uses these languages/concepts because that is what people have learned.

It is very true that in this modern age, ‘we drown in information but thirst for knowledge’, which I believe is the source of so the (recursive) propagation of bad or outdated tutorials and other information sources.

But in the end, I think that ‘people just do not learn anymore’ whose truthiness depends on your personal view of the human psyche.


In the end, people use patterns (like Monads) without realizing they do (An example: recently I hinted someone in the Hanami chat group that the ‘Interactor’ they used there was indeed very powerful, because it was an exact manifestation of the Either monad.). When you realize that you used a pattern somewhere, then this results in wisdom.

I believe that ‘wisdom’ can be defined as ‘knowing at what points the possibility to make a choice exists’, whereas knowledge is just ‘knowing some choices that might be made at a point’. Clearly, (according to this definition at least) wisdom is the more powerful one, as it is always possible to find out possible choices after you have decided that you might make a choice here, but the inverse (making a mindful choice at a point you do not know about) is impossible.

Much education is geared towards increasing your knowledge, however, instead of improving your wisdom. Why? I do not know. Maybe because ‘a choice’ is less abstract than ‘the possibility to make a choice’?

What is Elixir’s role in this? I believe that Elixir’s explicit over implicit is greatly beneficial to pointing out at what locations a choice might or has been made, to clearly show to your future self and others what is going on. I also like how Elixir tries to bridge the gap between the practical world (with all its imperfections, undecidabilities) and the functional, referential transparent world by being something that is easy to get into, greatly readable, extendable, flexible and plain old sturdy because of the way fault tolerancy is handled.

1 Like

With OCaml it is explicit, so you have to pass them explicitly, which is indeed a bit wordy (Implicit Modules will fix that though, whooo!).

Compare that to Haskell, which implements Implicit Witnesses not via Implicit Modules like OCaml is going to do, but via typeclasses. Look at a standard Haskell typeclass usage, any function that you do not know what the type is you have to define the witness:

treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
    | x == a = True
    | x < a  = treeElem x left
    | x > a  = treeElem x right

See the type of the treeElem function, it takes 2 arguments a -> Tree a and returns a Bool, but oh wait, what is that before the =>, it is a third argument, in reality it is Ord a -> a -> Tree a, just you replace the -> with => to make it an ‘implicit argument’ instead of an ‘explicit argument’. The difference is that an Implicit Argument takes whatever it can from its call site, so when you use the function:

treeElem 42 theTree

The implicit argument you cannot explicitly give, what it does instead is look in the scope at where treeElem is called and takes whatever bindings are available at this point and uses it (in this case it knows it is looking for the Ord typeclass and looking through the Ord module’s registered types to see what matches with the type of the first resolved a type, which since I pass in a 42 it knows is an Int, so it looks for an Ord Int class and implicitly passes it in at this point).

If you have a function that calls the treeElem function but does not know the type, then you have to specify it here too:

findElemAsStringResult :: (Ord a) => a -> Tree a -> String
findElemAsStringResult x node = show (treeElem x node)

Notice that I don’t need to add the Show Bool witness here because we already know what the type is, so when I call show on the Bool type return value it passes it in here. I do not need to pass the witness to show since the implicit argument means it grabs it from the scope right here, and there is only one witness that fulfills it. Explicitly this entire function looks more like this:

findElemAsStringResult :: Ord a -> a -> Tree a -> String
findElemAsStringResult ord x node = show (Show Bool) (treeElem ord x node)

And that is indeed more of how it is compiled as, just it can ‘hide’ most of that for you since it knows about the types. In OCaml it looks more like this one, all explicit (but once Implicit Modules comes out then it will be just as succinct).

So yes, even typeclasses pass things around. You cannot call Show on a type that does not have an appropriate class defined. You cannot call Show on some value that you do not know what it is without accepting the witness in to your function (this allows dynamic linking as well). With Protocols you have to know all types ahead of time, not so with witnesses/typeclasses as only the point where the value is created must know.

Even in Haskell/OCaml if you have some type, say a record, that can hold an arbitrary type in one of its keys, then if ‘some call’ needs to Show it in then the Show witness is passed through every single call down in to it from the upper-most point of where the record is defined and stored all the way down. This is why implicit versions are so nice. OCaml’s explicit witnesses would be a lot more verbose if it were not possible to define modules in-line, like take the Map module in OCaml (like elixir maps), you define the witness in the module definition:

module StringMap = Map.Make(String)

Map.Make is a Functor, a module that takes an argument and returns a new module, this one takes a module that has a t type of the key of the map and a compare : t -> t -> int function to use for comparisons, of which String already has this. So the StringMap module is a module that only accepts Strings as keys. I can implement one for Integers like:

module IntMap = Map.Make(struct type t = int let compare l r = if l < r then 1 else if l > r then -1 else 0 end)

There is no default Int module that fulfills these requirements (though most libraries define one for you so normally you’d never write that as-is), but I could even invert the comparison to get a swapped ordering of the IntMap, or zip-compare the integers or whatever I want. The witness becomes part of the type, so any place IntMap is used it already knows how to use Int’s. To use this in a function that does not know what the key type is then you still need to specify the type:

(* Let us create the witness module type first, OCaml already has tons of these though, but showing how it works: *)
module type HasKeyIn = sig
  type con
  type key
  val mem: key -> con -> bool
end

(* And define a function that uses it like *)
let contains_key (type key) (type con) (module Witness : HasKeyIn with type key = key and type con = con) key map =
  Witness.mem key map

(* And given these: *)
let is = IntMap.singleton 42 "Hello"

let ss = StringMap.singleton "bloop" "blorp"

(* You can do this, defining the witness inline here, usually it would be elsewhere or it would be a functor for easy-construction: *)
let true = contains_key (module struct type con = string IntMap.t type key = IntMap.key let mem k m = IntMap.mem k m end) 42 is

let true = contains_key (module struct type con = string StringMap.t type key = StringMap.key let mem k m = StringMap.mem k m end) "bloop" ss

And I could even change the mem function callback, or I could change it to be a set instead of a map, or a tree/trie, or a list, or an array, or whatever I want.

For note, the module Map.Make is defined as:

module Make: (Ord: OrderedType) => S with type key = Ord.t;

You see the Ord witness there for OCaml of type OrderedType, and OrderedType is:

module type OrderedType = sig
  type t
  let compare: t -> t -> int
end

It is not quite as pervasive through OCaml as it is in Haskell (where it really is over-used), but they are ubiquitous and is how polymorphism works entirely type-safe and efficiently. :slight_smile:

If curious, the Implicit Modules syntax in OCaml looks to be slated to become:

module type Show = sig
  type t
  val show : t -> string
end

let show {S : Show} x = S.show x

implicit module Show_int = struct
  type t = int
  let show x = string_of_int x
end

implicit module Show_float = struct
  type t = float
  let show x = string_of_float x
end

implicit module Show_list {S : Show } = struct
  type t = S. t list
  let show x = string_of_list S . show x
end

let () =
  print_endline ("Show an int: " ^ show 5);
  print_endline ("Show a float: " ^ show 1.5);
  print_endline ("Show a list of ints: " ^ show [1; 2; 3]);

Since things like Show_int are in scope then it can automatically use it, if it were not in scope then you’d get an error when trying to show 5 for example, with it saying that no implicit module that takes an argument of int with the specified signature was in scope. You can still explicitly pass modules too, which is great for overriding default functionality that you cannot easily do with typeclasses (really at all).

Here is the standard haskell’y monad in OCaml implicit modules:

module type Monad = sig
  type + ’a t
  val return : ’a -> ’a t
  val bind : ’a t -> ( ’a -> ’b t) -> ’b t
end

let return {M : Monad } x = M.return x

let (>>=) {M : Monad } m k = M.bind m k

let map {M : Monad} (m : ’a M.t) f =
  m >>= fun x -> return (f x)

let join {M : Monad} (m : ’a M.t M.t) =
  m >>= fun x -> x

let unless {M : Monad} p (m : unit M.t) =
 if p then return () else m

implicit module Monad_option = struct
  type ’a t = ’a option
  let return x = Some x
  let bind m k =
    match m with
    | None -> None
    | Some x -> k x
end

implicit module Monad_list = struct
  type ’a t = ’a list
  let return x = [x]
  let bind m k = List.concat (List.map k m)
end

In OCaml you do + to add integer, +. to add floats, and some other things for other number types, with implicit modules you could instead just do:

val (+) : {N : Addable} -> N.t -> N.t -> N.t

Then you could just use + for every single number type, or strings, or whatever else you want to be addable, just have to define an implicit module that uses the type you want to support and then done. :slight_smile:

The original Implicit Module document that grew into the full PR to OCaml to add Implicit modules is https://arxiv.org/pdf/1512.01895.pdf (which also speaks of what witnesses are) if you wish to see how it is implemented, how the search space is defined, how it resolves it all at compile-time and all. :slight_smile:

And nicely it is all not only implicit, saving a lot of typing, but it is still explicit as the modules have to be marked as implicit or you have to mark it inline (like implict module TheImplicitModule = SomeExplicitModule), thus nothing is fully ‘hidden’, yet it is still implicit and overridable. :slight_smile:

In my MLElixir project I already have code started to support implicit arguments like that too. :slight_smile:

Scala supports that. :slight_smile:

Mmmm maybe a little? ^.^;

Well let’s see, let’s treat Erlang/Elixir ok/error tagged tuples as a monad and let’s make a module to handle them, and maybe a few others too:

defmodule MonadHelpers do
  defmacro __using__(_) do
    quote do
      def value >>> cb, do: bind(value, cb)
      def map(value, cb), do: bind(value, fn x -> return(cb.(x)) end)
      def join(dvalue), do: bind(dvalue, fn x -> x end)
    end
  end
end

defmodule ResultMonad do
  @type t :: {:ok, any()} | {:error, any()}
  def return(value), do: {:ok, value}
  def bind({:error, _value}=tagtup, _cb), do: tagtup
  def bind({:ok, value}, cb), do: cb.(value)
  use MonadHelpers
end

defmodule OptionMonad do
  @type t :: {:ok, any()} | :error
  def return(value), do: {:ok, value}
  def bind(:error, _cb), do: :error
  def bind({:ok, value}, cb), do: cb.(value)
  use MonadHelpers
end

defmodule ListMonad do
  @type t :: [any()]
  def return(value), do: [value]
  def bind(lst, cb), do: :lists.concat(:lists.map(cb, lst))
  use MonadHelpers
end

And we can use them like:

iex> import ResultMonad
ResultMonad
iex> return(42)
{:ok, 42}
iex> return(21) >>> fn x -> x*2 end
42
iex> return(21) |> map(fn x -> x * 2 end)
{:ok, 42}
iex> return(21) \
...> |> map(fn x -> x * 2 end) \
...> |> map(&to_string/1)
{:ok, "42"}
iex> return(21) \
...> |> map(fn x -> x * 2 end) \
...> >>> fn x when x>100 -> {:ok, x}; x -> {:error, "Error with: #{x}"} end \
...> |> map(&to_string/1)
{:error, "Error with: 42"}
iex> return(21) \
...> |> map(fn x -> return(x * 2) end)
{:ok, {:ok, 42}}
iex> return(21) \
...> |> map(fn x -> return(x * 2) end) \
...> |> join()
{:ok, 42}
iex> return(21) \
...> |> map(fn x -> {:error, "blah"} end) \
...> |> join()
{:error, "blah"}
iex> return(21) \
...> |> map(fn x -> {:error, "blah"} end) \
...> |> join() \
...> >>> fn x -> x * 2 end
{:error, "blah"}

iex> import ListMonad
ListMonad
iex> return(2)
[2]
iex> return(2) \
...> |> map(fn x -> x * 2 end)
[4]
iex> lst = [0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
iex> lst \
...> |> map(fn x -> x * 2 end)
[0, 2, 4, 6, 8]
iex> lst \
...> |> map(fn x -> return(x * 2) end)
[[0], [2], [4], [6], '\b']
iex> lst \
...> |> map(fn x -> return(x * 2) end) \
...> |> join()
[0, 2, 4, 6, 8]
iex> lst \
...> |> map(fn x -> x * 2 end) \
...> |> map(fn x when x>5 -> []; x -> [x] end) # A filter predicate!
[[0], [2], [4], [], []]
iex> lst \
...> |> map(fn x -> x * 2 end) \
...> |> map(fn x when x>5 -> []; x -> [x] end) \
...> |> join() # Filtering based on the predicate!  You could easily combine these into a single function
[0, 2, 4]

Monads are basically just piping on steroids, super simple. :slight_smile:
And of course with witnesses and all such you can compose them all as much as you could ever want.

We use a lot of SML here, but getting OCaml in too.

Yeah the enum module is basically an enumeration-focused monad module, should get a more generic version.

I would argue that they require quite a different mindset to absorb - a mindset that is not fostered when primarily doing rote imperative programming work. During my university education I had to maintain some modicum of mathematical competence in order to complete the curriculum. Sadly that skillset was never challenged (and therefore maintained) during subsequent professional work. What I’m saying is that monads are abstract in a mathematical way, which is (at least to me) a different kind of abstract that you are dealing with when you design your in-the-wild software solutions.

So for the time being getting a more intuitive sense of what “monads” are really about is an ongoing process for me.

It was during my intensified exposure to pattern matching with Erlang that I started to see the relationship of Option to ({:ok, value} | :error) and Either (or Result) to ({:ok, value} | {:error, reason}). Shortly thereafter I looked at Kernel.with/1 and all of a sudden bind popped into my head.

The point I’m trying to make is that I really like those ResultMonad, OptionMonad, ListMonad modules - but I also still have enough of a beginner’s mind to realize that they are going to look absolutely alien to Elixir neophytes who don’t have any pre-exposure. (I still hate the name bind because it never helped me uncover the mystery behind it - but it’s not like I have a more descriptive name for it).

Now one has to be careful, while beginner friendliness is important to foster adoption, it can go overboard (I’m looking at you JavaScript).

But in terms of fostering understanding why this approach is a good idea it may be necessary to “fill-in” some much more pedestrian steps in between (not for production use - just for learning/understanding).

For example from

content =
  Enum.find_value(sources, fn source ->
    File.exists?(source) && File.read!(source)
  end) || raise "could not find #{source_file_path} in any of the sources"

to

{:ok, content} =
  Enum.find_value(sources, &Option.from_result(File.read(&1)))
  ~> fn _ -> raise "could not find #{source_file_path} in any of the sources" end
  # Or do `|> unwrap()` here and replace the binding above with just `content =`

there is still the danger of this happening.

So when I approached this in a more pedestrian manner I arrived at this:

defmodule M do

  def bind_option({:ok, value}, fun), do: fun.(value)
  def bind_option(:error, _fun), do: :error

  def unwrap_option({:ok, value}, _on_err), do: value
  def unwrap_option(:error, on_err), do: on_err.()

  def bind_result({:ok, value}, fun), do: fun.(value)
  def bind_result({:error, _reason} = err, _fun), do: err

  def unwrap_result({:ok, value}, _on_err), do: value
  def unwrap_result({:error, reason}, on_err), do: on_err.(reason)

  def some_fun do
    source_file_path = "whatever"
    sources = []

    on_err = fn (_reason) ->
      raise "could not find #{source_file_path} in any of the sources"
    end

    sources
    |> Enum.find_value({:error, :enoent}, &File.read/1)
    |> unwrap_result(on_err)
  end
end

Now unfortunately this example doesn’t benefit from bind directly but I think the juxtaposition of bind and unwrap both using pattern matching helps towards building a more intuitive sense towards what bind is all about (and understanding what those modules are about) - provided one has already accepted pattern matching as part of the everyday toolbox (something that I find one is driven to in Erlang - not so much in Elixir).

Ultimately I find that bind makes these ubiquitous tagged tuples more “pipe friendly” (and Elixir developers love their pipes).

PS: I still believe that the readability of the following (alternate) code suffers more from the inline definition of the anonymous function than the if conditional - I would find Enum.search(sources, &get_file/1) much more readable.

res = 
  Enum.search(sources, fn source ->
    if File.exists?(source) do
      {:ok, File.read!(source)}
    else
      :error
    end
  end) 
content =
  case res do
    :error -> raise "could not find #{source_file_path} in any of the sources"
    {:ok, content} -> content
  end

And I still cannot warm up to using either Kernel.&&/2 nor Kernel.||/2 - how would you even spec’ that in an “intention revealing” manner? This (non-valid) pseudo-spec

@spec || (l,r) :: r when l: (false|nil), r: as_boolean(term())
@spec || (l,_) :: l when l: as_boolean(term())

@spec && (l,_) :: l when l: (false|nil)
@spec && (_,r) :: r when r: as_boolean(term())

makes my skin crawl.

I felt this type of usage was a bad idea when I first encountered it in JavaScript and I haven’t come across an argument yet to convince me otherwise.

1 Like

Heh, typing issues like that do indeed cause that issue. Try thinking of how to implement such an operator in a typed language, like, oh, ocaml (I could use C++ if preferred):

let (||) left right = if left then left else right

Since left is used as a predicate, then it has to be a boolean, so we know this is true:

let (||) (left : bool) right = if left then left else right

And since left is returned as one of the possible branch expressions then the whole function must also return a bool:

let (||) (left : bool) right : bool = if left then left else right

And since the function returns a bool then that means that right must also be a bool:

let (||) (left : bool) (right : bool) : bool = if left then left else right

And that is exactly how HM type inference works, and as you can see a short-circuiting operator like || or && makes no sense like that. The only way that I could work out one to actually work would be to introduce a predicate function like:

let (||) pred left right = if pred left then left else right

So you pass in a function that can test left, if it returns true then left is returned, else it returns right. Fully typed it would be:

let (||) (type a) (pred : a -> bool) (left : a) (right : a) : a =
  if pred left then left else right

Or more succinctly:

let (||) (pred : 'a -> bool) (left : 'a) (right : 'a) : 'a =
  if pred left then left else right

And if the pred were an implicit argument via an implicit module as in OCaml upcoming feature then it could be ‘left out’ of a call if there is an appropriate predicate in scope at the call site, oh look, a typeclass.

But yes, in general the way that Elixir’s (and Erlangs) ||/&& work makes no sense from a typed point of view without some concept of Truthy/Falsey typeclasses or so, which is not explicit in the language and is rather implied (and not overridable so you could not even handle it for your own structures), hence why it feels ‘wrong’ to me as well.

However, notice the OptionMonad that you mentioned and pointed out from my other post:

import OptionMonad
# Given:
left = something()
right = "Vwoop"

# Or shortcut way:
result = left || right
# If way:
result = if(left, do: left, else: right)
# Monad piped way
result = left |> unwrap(fn -> right end)
# Or sans-piping:
result = unwrap(left, fn -> right end)

# And shortcut way
result = left && right
# If way:
result = if(left, do: right, else: left)
# Monad piped way
result = left |> map(fn _ -> right end)
# Or sans-piping:
result = map(left, fn _ -> right end)
# Or with a helper called 'replace' or so:
result = left |> replace(right)
# Or sans-piping:
result = replace(left, right)

pred = do_test()
# A whole short-circuited-shortcut if ternery:
result = pred && left || right
# If way:
result = if(pred, do: left, else: right)
# Monad way is same as if, they operate on 'what' something is, not predicates

The || and && are just monad expressions if the types make sense, like being an Option. :slight_smile:
And considering that, we could make a typed version that works, but the values need to be monads, I’ll use option here for specificness:

let (||) (left : 'a option) (right : 'a) : 'a =
  match left with
  | None -> right
  | Some a -> a

Hmm, that looks awfully like the normal option monad call of default:

let default d = function
| None -> d
| Some v -> v

So if that existed on my OptionMonad module then if you could just replace this:

result = left || right

With left being an option type then do this:

result = left |> default(right)
# Or inline:
result = default(left, right)

Monads appear in the most surprising of places yes? :wink:

1 Like
let (||) pred left right = if pred left then left else right

Once we get to this point I get much calmer because of another issue - the shortcut operators conflate the predicate result and the returned value - at least the ternary operator knows to keep the condition and the value separate.

Strangely enough I probably would feel a bit less tense if the shortcut operators were shortcut functions, e.g.:

some_left_most_truthy(left, right) 
all_right_most_truthy(left, right)

but I guess representing a macro as a function doesn’t make it clear that not all arguments are evaluated.

But I still don’t like that falsy-ness seems to grant false and nil some kind of universal meaning beyond any context. false makes sense in the context of boolean() and nil can make sense in {:ok, nil} (though I think ({:some, value} | :none) is preferable) - but in the absense of context they should just be an atom like any other.

Even so, I think they suffer a severe case of people “staring at the tree and not perceiving the forest.”

1 Like

/me coughs

if(pred, do: left, else: right)

That is so so so wordy, I wish we had one that worked like this:

if(pred, left, right)
# or
if pred, left, right

That would make a great short form and it is entirely doable.

If you look at the CoreErlang output of Elixir when it runs through the compiler you see that it litters things kind of like (in Elixir syntax):

result = case left do false->right; nil->right; []->right; _->left end

Or something like that (I remember being in awe at the number of things it tests and of course it is entirely non-expandable) whenever you do things like this:

result = left || right

There is a lot of weird non-macro magic in Elixir that consistently screws me up on occasion. I think I know ‘most’ of it due to reading it’s CoreErlang output so much, but oy… >.>

Indeed, monads are not a ‘thing’, they are a concept, like a function call or a closure, they can and do apply in many many many areas. They should be embraced. :slight_smile:

1 Like