Skip to content

Instantly share code, notes, and snippets.

@graue
Created January 3, 2013 08:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save graue/4441792 to your computer and use it in GitHub Desktop.
Save graue/4441792 to your computer and use it in GitHub Desktop.
Find first even number in a list (generic version)
extern mod std;
use num::Num;
// Based on some example code by Armin Ronacher
fn find_even<T:Copy Num cmp::Eq>(vec: &[T]) -> Option<T> {
let zero: T = Num::from_int(0);
let two: T = Num::from_int(2);
for vec.each |x| {
if *x % two == zero {
return Some(*x);
}
}
None
}
fn main() {
io::println(match find_even(@[1,2,3,5,7,9,10]) {
Some(x) => int::str(x),
None => ~"No even numbers"
});
}
@elimisteve
Copy link

Non-generic version:

package main

func findEven(vec []int) *int{
    for _, x := range vec {
        if x % 2 == 0 {
            return &x
        }
    }
    return nil
}

func main() {
    if x := findEven([]int{1,2,3,5,7,9,10}); x == nil {
        println("No even numbers\n")
    } else {
        println(*x)
    }
}

@elimisteve
Copy link

I'd be happier to show off non-toy examples, which will give us a much better idea how good these languages are in practice, not just in theory or with trivial examples.

Scripting languages will always win the battle of most concise one-liners, for example, but they're not good for writing large programs.

@graue
Copy link
Author

graue commented Jan 3, 2013

Yeah I know. I was just curious to compare the features. So Go doesn't make explicit the fact that the function can return no result? (nil is just implicitly allowed as a return value for anything?) That's something I like about Rust (and Haskell... the Option<T> type in Rust is just like Haskell's Maybe). Seems less safe not having it.

What about a generic version, one that works also on floats? Is it not possible to write in Go?

@jeena
Copy link

jeena commented Jan 3, 2013

I just can't resist showing how much shorter, easier to read and elegant a (recursive) solution of this in Erlang is:

find_even([]) -> [];
find_even([X|Xs]) when X rem 2 == 0 -> [X] ++ find_even(Xs);
find_even([_|Xs]) -> find_even(Xs).

main() ->
    case find_even([1,2,3,4,5,6,7,8,9]) of
        [] -> io:format("No even numbers");
        Xs -> io:format("~p", [Xs])
    end.

(And yes in Haskell it is even more elegant)

@elimisteve
Copy link

Go functions either return something or they don't -- there's no option here. It's not like Python, either, where if you don't explicitly return anything, None is returned. It seems more safe not having it to me, because you know what you're getting (and its type). nil cleanly handles this case in Go, because, the nil literal can be returned wherever a pointer to any type should be; you don't have to manually cast/convert it to the necessary type.

Go doesn't have generics, so the "generic" version won't look like Rust's. There are a couple ways to do it (using interface{} values (which can be anything) or creating an interface with Mod and/or Eq methods and using those to compare different types of values to each other), but none as succinct as Rust's.

Go's language designers refused to add features that look good in toy examples, but not real-world ones. This made people frustrated on the mailing list, because they felt like they had clear examples of why feature X should be added, but it wasn't. As a result, Go is great at writing real-world code, but admittedly not as succinct for writing trivial code.

Let me know when you want to compare more substantial code samples.

P.S. ...I'll still write a version though so you can see what it looks like in Go :-)

@elimisteve
Copy link

package main

import "fmt"

type Number interface {
    Mod(n int64) int64
}

type MyNum int64

func (num MyNum) Mod(n int64) int64 {
    if n <= 0 { return -1 }
    return int64(num) % n
}

// "Generic" findEven function, accepting a slice (array) of items
// that implement the `Number` interface. _Any type with a `Mod`
// method that accepts and returns an int64 implicitly satisfies the
// `Number` interface.
func findEven(vec []Number) Number {
    for _, x := range vec {
        if x.Mod(2) == 0 {
            return x
        }
    }
    return nil
}

func main() {
    x := findEven([]Number{MyNum(1),MyNum(2),MyNum(3),MyNum(4)})
    if x != nil {
        fmt.Printf("%d\n", x)
        return
    }
    fmt.Printf("No even numbers\n")
}

@graue
Copy link
Author

graue commented Jan 3, 2013

Go functions either return something or they don't -- there's no option here. It's not like Python, either, where if you don't explicitly return anything, None is returned. It seems more safe not having it to me, because you know what you're getting (and its type). nil cleanly handles this case in Go, because, the nil literal can be returned wherever a pointer to any type should be; you don't have to manually cast/convert it to the necessary type.

Perhaps I wasn't clear. The part I've bolded is the part I object to. If you have a function returning a pointer type, and you forget to check for nil, the program blows up, right?

To my mind, a pointer is one type, and nil is a different type. (And this is actually how the dynamic languages I'm used to treat it — in Python, PHP, Lua, JavaScript, etc., the nil or None or undefined value is considered a different type.) A Go function that can either return a valid pointer, or nil, is dynamically typed in a sense. There are two different types that it could return.

Rust handles this by having a generic type called Option. For any type T, the type Option<T> either represents Some(T) or just None. So you can't simply forget to check against nil. You see the Option<...> in the function's type signature and you know you have to explicitly use the Some and None constructors.

This kind of annotation has been done unofficially for years. In C, you document a function and you say in the documentation, “This function returns NULL if...”, and other developers read that, and know they need to check for NULL. There was even a lint package I once tried out that gave you a special way to mark functions as “may return NULL” or “never returns NULL” and it would use that to give you warnings.

But no one uses that silly lint stuff and no one has time to read documentation. It just seems obvious to me that in a statically typed language designed for reliability, this annotation should be explicit and part of the language. That way you avoid having a nil pointer make your program blow up. So I'm actually very disappointed to learn that Go allows nil anywhere a pointer is expected. From a modern language I would expect better safety.

@elimisteve
Copy link

I don't see a significant difference.

In Rust, when... [y]ou see the Option<...> in the function's type signature and you know you have to explicitly use the Some and None constructors.

In Go, when you see a *T in the function's type signature, you know you have to explicitly check for nil being returned.

If you don't, yes, deferencing a null pointer will blow things up. What happens in Rust?

@graue
Copy link
Author

graue commented Jan 3, 2013

In other words you just have to check every pointer, always?

But what if you check the pointer against nil on line 37, and then assume it's non-nil and dereference it on line 52, and a later pull request accidentally deletes the check on line 37? Now your assumption is wrong and stuff blows up.

In Rust it is, as I understand it, impossible to have a pointer that will blow up if dereferenced. If you have a &T you know that it isn't nil/null/undefined, the check (if necessary) has already been done. That's encoded in the type system. It's a big safety feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment