The answer to question 1 is quite simple: two options are equivalent - class types can be "desugared" only for data types. The idea was described and argued at http://www.haskellforall.com/2012/05/scrap-your-type-classes.html .
The answer to question 2 is that these two are the only ways to separate the interface from the implementation. The rationale would be something like this:
- Ultimately, the goal is to somehow pass functions - this is because in Haskell there is no other way to implement anything other than functions, so in order to get around the implementations, you need to go around the functions (note that specifications are just types)
- you can either bypass one function or several functions
- to pass a single function, you simply pass that function around or some function wrapped in something to mimic type classes (i.e. give your interface a name (e.g.
CanFoo ) in addition to just a type signature ( a -> Foo ) - to transfer several functions, you simply pass them inside a tuple or record (for example, our
CanFoo , but with a large number of fields); note that a record is only a named tuple type with named fields in this context.
- Whether the transfer of functions explicitly or implicitly (with class types), as has already been demonstrated, is one and the same conceptually [1] .
Here is a simple demonstration of how 2 methods are equivalent:
data Foo = Foo -- using type classes class CanFoo a where foo :: a -> Foo doFoo :: CanFoo a => a -> IO Foo doFoo a = do putStrLn "hello" return $ foo a instance CanFoo Int where foo _ = Foo main = doFoo 3 -- using explicit instance passing data CanFoo' a = CanFoo' { foo :: a -> Foo } doFoo' :: CanFoo' a -> a -> IO Foo doFoo' cf a = do putStrLn "hello" return $ (foo cf) a intCanFoo = CanFoo { foo = \_ -> Foo } main' = doFoo' intCanFoo 3
As you can see, if you use records, your "instances" are no longer scanned automatically, and instead you need to explicitly pass them to the functions they need.
Note also that in the trivial case, the recording method can be reduced to simply passing functions, because passing around CanFoo { foo = \_ -> Foo } really the same as going around wrapped functions \_ -> Foo .
[one]
In fact, in Scala this conceptual equivalence becomes obvious, since the type class in Scala is encoded in terms of the type (for example, trait CanFoo[T] ), a number of values โโof this type and function parameters this type is marked as implicit , which will cause Scala to look for values โโof type CanFoo[Int] on the call site.
// data Foo = Foo case object Foo // data CanFoo t = CanFoo { foo :: t -> Foo } trait CanFoo[T] { def foo(x : T): Foo } object CanFoo { // intCanFoo = CanFoo { foo = \_ -> Foo } implicit val intCanFoo = new CanFoo[Int] { def foo(_: Int) = Foo } } object MyApp { // doFoo :: CanFoo Int -> Int -> IO () def doFoo(someInt: Int)(implicit ev : CanFoo[Int]] = { println("hello") ev.foo(someInt) } def main(args : List[String]) = { doFoo(3) } }
Erik allik
source share