tokiwa-software / fuzion

The Fuzion Language Implementation
https://fuzion-lang.dev
GNU General Public License v3.0
46 stars 11 forks source link

Should `Lazy` inherit from `monad`? #1807

Open michaellilltokiwa opened 1 year ago

michaellilltokiwa commented 1 year ago

See here: https://blog.ploeh.dk/2022/05/30/the-lazy-monad/

@tokiwa-software/developers Do you think this is useful?

maxteufel commented 1 year ago

I played around with this, and work with the following diff:

diff --git a/lib/Function.fz b/lib/Function.fz
index 828c1148..bfd2ef68 100644
--- a/lib/Function.fz
+++ b/lib/Function.fz
@@ -27,11 +27,11 @@
 #
 # R is the result type of the function, A are the argument types of the function
 #
-public Function(R type, A type...) ref is
+public Function(R type, FA type...) ref is

   # call this function on given arguments A...
   #
-  public call(a A) R is abstract
+  public call(a FA) R is abstract

 # NYI: would be nice to have schönfinkeling defined here, could work like this:
diff --git a/lib/Lazy.fz b/lib/Lazy.fz
index 7d094a84..fec420ca 100644
--- a/lib/Lazy.fz
+++ b/lib/Lazy.fz
@@ -23,4 +23,17 @@

 # Lazy
 #
-public Lazy(T type) ref : Function T is
+public Lazy(T type) ref : Function T, monad T (Lazy T) is
+
+  # monadic operator
+  #
+  public bind(B type, f T -> Lazy B) Lazy B is
+    v := call
+    (() ->
+      application Lazy B := f v
+      application)
+
+
+  # return function
+  #
+  public fixed type.return (a T) Lazy T is () -> a

The renaming in Function is necessary because both Function and monad currently define a conflicting type parameter A.

There are multiple issues with this:

  1. It does not compile with check-conditions enabled. See #1811.
  2. This would not work anyway, because I cannot access the call feature of the old instance of Lazy inside the new instance of Lazy (would be resolved by the introduction of a method to reference the outer feature).
  3. This would not work anyway, II, because when using this, as in the example below, the Lazy x gets evaluated before the bind gets called, so there is a "Could not find called feature" error.

The example:

ex_lm is
  x Lazy i32 := (() ->
    say "eval x"
    42)

  slow_to_string i32 -> Lazy String := (x ->
    say "eval slow_to_string outside"
    (() ->
      say "eval slow_to_string inside"
      "fourty-two"))

  y Lazy String := x.bind slow_to_string

  say "y defined before this"

  say y
-:12:22: error 1: Could not find called feature
  y Lazy String := x.bind slow_to_string
---------------------^^^^
Feature not found: 'bind' (one argument)
Target feature: 'i32'
In call: 'ex_lm.this.x.call.bind slow_to_string'