views:

583

answers:

1

haskell programmer. using F#. no typeclasses in F#. what to use when I need typeclasses?

+14  A: 

Do check out this as someone suggested.

I think the short answer is to pass dictionaries-of-operations (as Haskell would under the hood; the witness for the instance).

Or change the design so you don't need typeclasses. (This always feels painful, since typeclasses are the best thing ever and it's hard to leave them behind, but before Haskell and typeclasses came along, people still managed to program for 4 decades previously somehow without typeclasses, so do the same thing those folks did.)

You can also get a little ways with inline static member constraints, but that gets ugly quickly.

Here's a dictionary-of-operations example:

// type class
type MathOps<'t> = { add : 't -> 't -> 't; mul: 't -> 't -> 't }  //'

// instance
let mathInt : MathOps<int> = { add = (+); mul = (*) }

// instance
let mathFloat : MathOps<float> = { add = (+); mul = (*) }

// use of typeclass (normally ops would the 'constraint' to the left of 
// the '=>' in Haskell, but now it is an actual parameter)
let XtimesYplusZ (ops:MathOps<'t>) x y z =   //'
    ops.add (ops.mul x y) z

printfn "%d" (XtimesYplusZ mathInt 3 4 1)
printfn "%f" (XtimesYplusZ mathFloat 3.0 4.0 1.0)
Brian
@Brian: "typeclasses are the best thing ever". I'd love see an example where typeclasses solve a real problem that wasn't already better solved by another technique. Also, your example retrofits the dictionary of operations rather than defining them with the original type definition. Is that possible with type classes?
Jon Harrop
@Jon Harrop: What? Of course it is. Defining type classes and instances is completely separate from defining types. They don't even have to be in the same module. If memory serves me, Scala's version is even more powerful and lets you have locally scoped definitions (but I don't know Scala, so I may be confused).
camccann
@camccann: Ah yes, of course. That's why you cannot rely upon them being statically resolved so your performance might be that of boxing and dispatch instead of an MLA instruction, unpredictably degrading performance by over an order of magnitude as it does in Brian's example?
Jon Harrop
@Jon Harrop - type classes are resolved statically
pelotom
@camccann For the record, yes, Scala let you locally scope definitions. It is more awkward to use, however, and there's little library use of type classes at the moment. Hopefully, both things will get fixed in the future.
Daniel
@Daniel: Thanks! I remembered reading something about it and thinking it sounded useful, particularly for dealing with cases like Haskell's `Monoid` where e.g. `Float` has no less than four obvious and useful instances. One of these days I'll get around to learning Scala...
camccann
@pelotom: Only for a different definition of "resolved statically".
Jon Harrop
Dictionary passing is one implementation of type-classes, it's not the only way. JHC compiler is one example of a (whole-program optimizing) Haskell compiler which does NOT use dictionary passing to implement type-classes.
snk_kid
There seems to be another problem which hasn't been mentioned, it's not just the lack of type-classes. The fact that F# (because of .NET) has no support for higher-kinded polymorphism which means you can not (trivially) implement some of the abstractions possible in languages like Haskell, even with explicit dictionary passing you can't do it. I really hope this is sorted out in .NET/F# in the future.
snk_kid
@snk_kid: Can you give a compelling example where higher kinded polymorphism is useful?
Jon Harrop
@Jon Monad overloading?
Simon Marlow
@Simon: Is that useful outside pure languages?
Jon Harrop
@Jon Harrop: So you're not a fan of computation expressions in F#, I take it?
camccann
@Jon of course it is. Monads aren't just for side-effects you know.
Simon Marlow
@camccann: I do use them but where does monad overloading come into that?
Jon Harrop
@Simon Marlow: what else would monad overloading be useful for outside pure languages?
Jon Harrop
@Jon: The list monad (or other more optimized monads representing nondeterminism) is the most obvious example of a monad which cannot easily be embedded in the "default monad" of an impure language without multi-shot continuations. As soon as you want to define basic "control flow" operations like `mapM`, `filterM`, `sequence` that can operate on either nondeterministic computations or ordinary side-effecting computations, you need to be able to abstract over higher-kinded type constructors.
Reid Barton
@Reid Barton: Do you really want to be able to define control flow constructs such that the same one can run either nondeterministic computations or ordinary side-effecting computations? This sounds exactly like the use of OOP to abstract over kinds of collections that never caught on in OCaml...
Jon Harrop
Sure. I regularly use `replicateM` for things like either reading a list of integers from standard input (`replicateM 5 (readLn :: IO Integer)`) or generating all possible strings of booleans of a given length (`replicateM 5 [False, True]`). If in the latter case I later decide to replace the list monad with a more efficient nondeterminism monad, I don't want to have to rewrite `replicateM`.
Reid Barton
(As for abstracting over kinds of collections, that's never really caught on in Haskell either, not that people haven't tried. It seems that the similarities between collections are outweighed by the little differences between them, such as whether the elements have an intrinsic ordering, whether there are values associated with keys, etc., so that there is not a canonical "best" set of operations on a collection. In contrast the monad interface is very simple and well-defined with solid theoretical underpinnings.)
Reid Barton
Non-determinism is a canonical side effect.
sclv
Give up guys, Jon is Jon. He's always right, everywhere, every time. :-) He won't stop arguing until you accept that.
soc