Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Could you provide examples (beyond generics) where 'composable complexity' helped finagle achieve a goal?

My experience with Scala has been that an author's 'composable complexity' becomes a reader's 'wtf is this complexity'.

Go's philosophy has been to avoid complexity altogether in most of the tools they give you, forcing you to write lowest-common-denominator code that anyone can easily understand. Generics are sorely missed, but I really don't see additional expressive power from most scala features, just additional things I need to keep in my head for an occasional 5-line savings in the middle of some method (which IMO doesn't change the complexity equation at all). Would love to see some beautiful examples that change my mind.



Clarity of thought is rare. The ability to express those thoughts as code is even more rare.

Go's approach is to makes it easy to say certain things. Goroutines are great. But it also makes it difficult to say complicated things like List<T>. Good programmers suffer, but really it's just a few more keypresses, ala Java.

Scala makes it possible to say even more abstract things like T<int>. Not many programmers can really explain a higher kinded type, or why such a thing is useful. Novice programmers say silly things because they don't really know how to apply all of this abstract power.

Go is pretty wonderful because it lets programmers of varying ability work together effectively. Scala is teaching people new abstractions. Are they worth it? Maybe not in Scala. However, I think languages with powerful type systems will ultimately win, because you can lean hard on the compiler to guarantee correctness.


You forgot the other drawbacks that complicated type systems have: hellish compiler messages and long compile times. Those don't go away just because you learn more about the language.

I also dispute the idea that only novices abuse complicated type systems. Actually I think it's the reverse... only people who know the language well can create something truly horrible. Don't believe me? Look at any of the C++ STL header files and experience Biij.

Of course, just because they can doesn't mean they will. But in a world dominated by people of average intelligence, working around the programming equivalent of a gas lamp with an open flame just isn't a good idea.


> drawbacks that complicated type systems have: hellish compiler messages and long compile times

Scala has incredibly nice compiler messages and you can see Rust for an example of a language with complex types and short compile times.

These issues have _nothing_ to do with complicated type systems - you're projecting the failures of the C++ template system - failures which stem in fair part from the fact that it's not really a type system but simply a templater.

Finally, it's worth recognising that Scala goes to lengths to keep messy features inside their own compilation unit - for example, macros appear as functions to the outside and complex features such as implicit conversions must be explicitly imported for use.

I tried building our project in Go and Node.js explicitly to make it as easy as possible for people to contribute, irrespective of their background. It turned out that the additional complexity I had to pump into abstractions to make it happen made the code just as complex to understand.

It's quite possible that projects just have a natural complexity level and it's worth picking the language to match. There are certainly different times for different languages.


As much as I like Rust, I have to say that it's not advancing a compelling argument for "fast compilers with complex types" (which isn't to say I don't think it can be done, just that Rust is by no means fast at compiling, and really only gets away with the slowness it has because its main competition still lacks a proper module system).


Rust's type system has nothing to do with any slowness in its compiler. There's a `time-passes` flag that you can pass to the compiler to see where it spends its time compiling any given program. Do so and you'll see that the overwhelming majority of Rust's compilation time is spent inside LLVM generating code.


Go libraries are typically highly composable because they operate mostly on primitives or strictly defined common interfaces. Orthogonality is a key feature.


I agree with you completely, yet what could prevent the gokit team from starting with the definition of very simple , orthogonal, interfaces and maybe add the very few new types required, to immitate the go stdlib.

I'm quite sure the result will be quite different from what those kind of frameworks look like in other languages, but that could be interesting still.


That's what I'm hoping for. I imagine many of the services could be designed in a manner similar to database/sql, with its driver interfaces.


If I recall correctly those libraries aren't type safe and composable, but perhaps I'm remembering wrong.


They are type safe, except for the unsafe package but it's hard not to know what you're getting into with that one.

One of the best examples of composability is the Reader and Writer interface. They're designed to operate on slices of bytes, so a package intended to read from a Reader can read from files, stdin, networks, sockets, and a myriad other systems with absolutely no changes to the package's code or its implementation.


How would you do decode this json in a type safe way using Go?

    {"purchaseType": "Rent","price": 0.99,"title": "inception"}
Here is an implementation in Haskell:

    {-# LANGUAGE DeriveGeneric #-}
    {-# LANGUAGE OverloadedStrings #-}
    
    import Data.Aeson
    import GHC.Generics
    import qualified Data.ByteString.Lazy as BL
    
    data PurchaseType = Free | Rent | Buy | Subscriber deriving (Show, Generic)
    instance FromJSON PurchaseType
    instance ToJSON PurchaseType
    
    data Media = Media { title :: String, price :: Float, purchaseType :: PurchaseType} deriving (Show, Generic)
    
    instance FromJSON Media
    instance ToJSON Media
    
    jsonData = "{\"title\": \"inception\", \"price\": 0.99, \"purchaseType\": \"Rent\"}"
   
    main = print $ (decode jsonData :: Maybe Media)
    -- running main gets:
    -- λ> main
    -- Just (Media {title = "inception", price = 0.99, purchaseType = Rent})
EDIT: Spec Change, boss wants an order confirmation message!

    orderConfirmationMessage (Media mediaTitle _ Rent) = putStrLn ("Thanks for renting " ++ mediaTitle)
Then we recompile and... what's this?

    Main.hs:19:1-100: Warning: …
        Pattern match(es) are non-exhaustive
        In an equation for ‘orderConfirmationMessage’:
            Patterns not matched:
                Media _ _ Free
                Media _ _ Buy
                Media _ _ Subscriber
Good thing GHC had told us Free/Buy/Subscriber's needed messages instead of our boss! In fact, had I been using -Wall this would be a compile time error.

Getting to the point, my real questions are:

1. What is Go's way of ensuring your function handles cases like this, or is there an idiomatic way of avoiding it?

2. How do you discern between MovieType's without resorting to just using Strings and compromising type safety?


How about an array of Media?

    package main

    import (
        "encoding/json"
        "fmt"
    )

    type Media struct {
        Title        string
        Price        float64
        PurchaseType string
    }

    func main() {
        jsonData := []byte(`
            [
                {"purchaseType":"Rent","price":0.99,"title":"Inception"},
                {"purchaseType":"Free","price":0.00,"title":"Johnny Mnemonic"},
                {"purchaseType":"Buy","price":17.99,"title":"John Wick"}
            ]
        `)
        var media []Media
        err := json.Unmarshal(jsonData, &media)
        if err != nil {
            fmt.Println("error: ", err)
        }
        fmt.Printf("%+v\n", media)
    }
Output:

    $ go run main.go
    [{Title:Inception Price:0.99 PurchaseType:Rent}
    {Title:Johnny Mnemonic Price:0 PurchaseType:Free}
    {Title:John Wick Price:17.99 PurchaseType:Buy}]
Edit: Regarding the MediaType: http://stackoverflow.com/questions/14426366/what-is-an-idiom...


PurchaseType doesn't need to be stringly typed. See https://talks.golang.org/2015/json.slide#1.

    type PurchaseType byte
    
    const (
        Free PurchaseType = iota
        Rent
        Buy
    )
    
    func (t *PurchaseType) UnmarshalJSON(data []byte) error {
        var s string
        if err := json.Unmarshal(data, &s); err != nil {
            return fmt.Errorf("purchase type should be a string, got %s", data)
        }
        have, ok := map[string]PurchaseType{"Free": Free, "Rent": Rent, "Buy": Buy}[s]
        if !ok {
            return fmt.Errorf("invalid purchase type %q", s)
        }
        *t = have
        return nil
    }
    
That's enough to give this output:

    [{Inception 0.99 Rent} {Johnny Mnemonic 0 Free} {John Wick 17.99 Buy}]
http://play.golang.org/p/dyNVlgmFtu


Check out my update if you haven't seen it already.

tl;dr The problem for me personally is when you start making functions based on the type of media and sacrifice type safety if MediaType is a string.


There is no exhaustive pattern matching in golang. And you would have to define your own unmarshall function that translates "Rent" into, perhaps, a const. So, yes, type safety in golang is not as powerful as it is in Haskell. I think we already knew that though didn't we :)


> So, yes, type safety in golang is not as powerful as it is in Haskell. I think we already knew that though didn't we :)

That is just a really big one for me I wanted to share and get others opinion on. I guess I just feel like a lot of people don't realize how much having that stringly typed hole means. Perhaps others just disagree.

I would love to hear a counter argument for why others don't think this matters.


I've been coding professionally for 16 years. I've written a lot of this kind of code:

    switch (someEnum) {
      case foo:
        <something>
      case bar:
        <something>
      default:
        throw DeveloperScrewedUpException("Unknown enum val: " + someEnum)
    }
...You know how often I've seen those exceptions? Never. Not once. I'm not saying it can't happen, and I'm not saying that I wouldn't want a compiler error to tell me I missed a value... but I am saying that it just doesn't actually happen all that often in my experience, especially with decent tests. And yes, I've worked on systems for many years where they've evolved and things have changed significantly, so it's not just that I've only worked on brand new projects.


> If I recall correctly those libraries aren't type safe and composable

You may be confusing interfaces (which enforce type compatibility) and the empty interface{}, which is effectively a dynamic type in the Go world.


Take a look at the Stack class (an example of a free monad) and how it's used to compose clients:

https://github.com/twitter/finagle/blob/master/finagle-core/...

https://github.com/twitter/finagle/blob/master/finagle-core/...

http://twitter.github.io/finagle/guide/Clients.html

A client is a layer of various components like load balancers and stats recorders, but they're also all configurable. Only using the expressiveness of scala could such a data structure be created to compose all of these pieces, yet still keep them modifiable and type-safe.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: