1. 13
  1. 15

    Now Go users can clamour for HKTs just like Rust users.

    1. 3

      What’s an HKT?

      1. 7

        To elaborate on the other comment - higher kinded types refer to being able to make other parts of the type generic - we can talk about mapMaybe : (a -> b) -> Maybe<a> -> Maybe<b> but without higher kinded types, you can’t talk about genericMap : Mappable m => (a -> b) -> <m><a> -> <m><b> where m could be Maybe or List or Array or IO. In Haskell, we have the Functor class:

        class Functor f where
            fmap :: (a -> b) -> f a -> f b
        

        where any type f which can implement this interface, and obey a few simple laws (that fmap identity is just identity so the shape of the structure isn’t changed by fmap), means that f is a Functor;

        instance Functor Maybe where
            fmap f Nothing = Nothing
            fmap f (Just a) = Just (f b)
        
        instance Functor [] where -- Instance for lists
            fmap f xs = map f XS
        
        etc.
        

        We can now write algorithms which generalise over things which are Functors, or Monads, such as

        -- Run some monadic action N times
        replicateM :: Monad m => Int -> m a -> m [a] 
        
        -- Run an IO action three times
        >>> replicateM 3 (putStrLn "Hello!") 
        Hello!
        Hello!
        Hello!
        [(),(),()] -- The [()] returned by replicateM - since (putStrLn "..") has type IO () 
                   -- the Hello!s are the result of evaluating those actions
        
        -- Not super useful for Maybe, but it still works
        >>> replicateM 3 (Just True)
        

        Just [True,True,True] – Shows that the Maybe monad is the “fail if anything fails” monad, not just a fancy null >>> replicateM 3 Nothing Nothing

        -- make all combinations of the elements of a list of length three
        >>> > replicateM 3 "ABC"
        ["AAA","AAB","AAC","ABA","ABB","ABC","ACA","ACB","ACC",
        "BAA","BAB","BAC","BBA","BBB","BBC","BCA","BCB","BCC",
        "CAA","CAB","CAC","CBA","CBB","CBC","CCA","CCB","CCC"]
        

        the implementation for replicateM is defined only once, and uses the behaviour of which ever monad you provide it with.

        Not sure if that cleared anything up, but the take away is probably the

        genericMap : Mappable m => (a -> b) -> <m><a> -> <m><b>
        

        example, where you want to be able to talk not just about generic values within a type, but also generic structures themselves, where you may not care what their generic parameters actually are, as in this case.

        1. 3

          I appreciate the detailed explanation, although I have encountered HKTs recently while learning OCaml - I just didn’t recognize the acronym (and the results of a Google search were dominated by Hong Kong Telecom).

        2. 2

          Higher kinded types

        3. 1

          And TypeScript!

        4. 6

          I highly recommend this talk by Ron Pressler from Project Loom: Ron Pressler - Pull Push: Please stop polluting our imperative languages with pure concepts

          I argues that rich support for continuations/effects are a better fit for the imperative paradigm than bringing in monads from the functional world.

          1. 1

            I’m excited to watch the talk, and hope he addresses the fact that many imperative languages don’t have tail call optimization making it hard to support this stuff, without trampolines? I guess JavaScript isn’t required to have TCO either, and we’ve been fine there with nested callbacks… generally. Though, maybe the setTimeout trick is all too effective there…

            1. 1

              I don’t think this talk goes too deep into implementation. He has other talks and documents about how Project Loom is bringing all this to Java.

          2. 5

            Nice work. It’s unfortunate that Go can’t enforce the various laws.

            1. 8

              ? FWIW Haskell doesn’t enforce the Monad laws either, they’re documented as a convention that authors of monad instances are expected to follow.

              Offhand I think there might be some RULES pragmas in GHC which assume that the monad laws do hold, so they can do things like replace x >>= return with x

              1. 1

                Sure. There are languages that can enforce the laws; I think Idris might have lawful typeclasses.

                1. 4

                  I feel this is a slightly unfair criticism of Go when it only just got generics a scant couple of months ago! ;)

              1. 4

                The author mentions a performance loss. My benchmarks suggest the opposite.

                I often write badly performing code and it’s ok. Even a 30% CPU overhead is often cheaper than an unreadable code.

                In some cases, using FP patterns or data structure makes the code more readable, since it abstracts a lot of recurring things.

                We don’t have to use it everywhere. But sometimes it helps a lot.

              2. 1

                Thanks for pushing this forward. Your tooling is pushing the envelope, in a good way.

                That first FlatMap example isn’t very readable to my eye, I think I prefer plain verbose Go. It might look better should Go ever get more terse function syntax.

                1. 1

                  What is the extra bool return in the callbacks passed to Map and such for?

                  If you made stand-alone functions, rather than methods, you could implement e.g. versions of Map() which can change the type parameter. Though the trade-off is that then you can’t put these behind an interface. You could do both though I guess.

                  The fact that you have Then on Future but FlatMap on everything else seems sub-optimal; if the library has Monad in the title it seems like it should at least use consistent naming for the key monad operation.

                  Also, there’s no real reason to bake error handling into Future; it’s only there in JS because the base language had exceptions, but go doesn’t. If you want a future that can fail just do Future[Result[T]].

                  Also, IO seems pointless given that the language doesn’t enforce purity in the first place.

                  …this whole thing feels very under-designed, like the APIs were just transliterated from other languages, without much thought given to how they would interact with Go, or even each other.