issues with ad-hoc polymorphic typeclass instances

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

issues with ad-hoc polymorphic typeclass instances

Weylin Lam
Hello !

I'm trying to write a polymorphic class instance in function of another class. So far it's pretty hard, I have trouble with headsizes and so on. I actually had to switch to type families pretty fast, and i'm still not out of the woods.

The context:
It concerns "tagless final interpreters" for EDSLs as described in http://okmij.org/ftp/tagless-final/course/lecture.pdf

The technique is fairly simple: instead of using ADTs to encode the operations of the language, we use haskell's class system. Languages become type classes, and interpreters are the instances which give various different meanings to the language expressions, which become simple haskell, ad-hoc polymorphic values.

It has strong advantages. For example, to write some expression in the composition of two languages (two typeclasses), you only need to combine the constraints:
class WriteFile h m where
  writeFile :: h -> String -> m ()
class ReadFile h m where
  readFile :: h -> m String

-- we use Monad m to have access to monadic operations, but the language itself
-- does not care that m be a monad or not.
myExpression :: (WriteFile h m, ReadFile h m, Monad m) => h -> h -> String -> m String
myExpression h h2 str = writeFile h str *> readFile h2

The fusion of both languages is utterly seamless, which is the beauty of typeclasses.

When writing DSLs, there are at least two basic operations that need to be done: combining DSLs to create richer languages (and as just shown, it's really simple with this technique), and translating one DSL into another.

That latter operation between DSLs is a bit more complicated with classes. I haven't found examples of how to do so in the wild (the technique doesn't seem very much used, esp compared with free monads), so if anybody knows how to do it or has references on that, it'd be much appreciated.

Theoretical example of what i'm trying to perform:
class A a where
  subtract :: a -> a -> a
class B a where
  add :: a -> a -> a
  neg :: a -> a

-- interpreter from A to B: (doesn't work of course)
class (B a) => A a where
  subtract x y = add x (neg y)

-- interpreter for B in terms of Int:
instance B Int where
  add = (+)
  neg = negate

expression :: (A a) => a
expression = subtract (subtract 4 5) 6
-- to get it to be interpreted, we need to select the type. usually we use a monomorphic version of id:
asInt = identity :: Int -> Int
intExp = asInt expression :: Int

For the instance B Int to be re-usable as instance/interpreter of (A Int) expressions, we need a way to write an interpretation of A a in terms of any pre-existing interpretation of B b.
Usually there are some issues, the need to wrap the types into newtype wrappers among others.

But in the case i'm trying to solve, it still doesn't work. To see where i'm stuck, see above my lpaste.

Any help or ideas would be very welcome! :) Thanks a lot in advance.

_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.
Reply | Threaded
Open this post in threaded view
|

Re: issues with ad-hoc polymorphic typeclass instances

Jonas Scholl
Do addWithEnv and mulWithEnv need to be functions in a type class?
Couldn't you just define them as normal functions? Then all the problems
with undecidable instances are gone. I also don't see how having them in
a type class helps you in any way if you want to define such a far
reaching instance for it. Are there cases where you would need another
instance? If not, then putting it in a plain function is the way to go.
If yes, then you can not define Op and Env for the type of your OpEnv
instance, which seems counterintuitive. Otherwise you would run into
overlapping instances and I don't know if you want that, probably not.

On 09/02/2017 07:10 AM, Weylin Lam wrote:

> Hello !
>
> I'm trying to write a polymorphic class instance in function of another
> class. So far it's pretty hard, I have trouble with headsizes and so on.
> I actually had to switch to type families pretty fast, and i'm still not
> out of the woods.
> http://lpaste.net/2650337298029215744
> <http://lpaste.net/2650337298029215744>
>
> The context:
> It concerns "tagless final interpreters" for EDSLs as described
> in http://okmij.org/ftp/tagless-final/course/lecture.pdf
> <http://okmij.org/ftp/tagless-final/course/lecture.pdf>
> A comparison with free
> monads: http://yowconference.com.au/slides/yowlambdajam2016/Hopkins-StopPayingForFreeMonads.pdf
> <http://yowconference.com.au/slides/yowlambdajam2016/Hopkins-StopPayingForFreeMonads.pdf>
>
> The technique is fairly simple: instead of using ADTs to encode the
> operations of the language, we use haskell's class system. Languages
> become type classes, and interpreters are the instances which give
> various different meanings to the language expressions, which become
> simple haskell, ad-hoc polymorphic values.
>
> It has strong advantages. For example, to write some expression in the
> composition of two languages (two typeclasses), you only need to combine
> the constraints:
> class WriteFile h m where
>   writeFile :: h -> String -> m ()
> class ReadFile h m where
>   readFile :: h -> m String
>
> -- we use Monad m to have access to monadic operations, but the language
> itself
> -- does not care that m be a monad or not.
> myExpression :: (WriteFile h m, ReadFile h m, Monad m) => h -> h ->
> String -> m String
> myExpression h h2 str = writeFile h str *> readFile h2
>
> The fusion of both languages is utterly seamless, which is the beauty of
> typeclasses.
>
> When writing DSLs, there are at least two basic operations that need to
> be done: combining DSLs to create richer languages (and as just shown,
> it's really simple with this technique), and translating one DSL into
> another.
>
> That latter operation between DSLs is a bit more complicated with
> classes. I haven't found examples of how to do so in the wild (the
> technique doesn't seem very much used, esp compared with free monads),
> so if anybody knows how to do it or has references on that, it'd be much
> appreciated.
>
> Theoretical example of what i'm trying to perform:
> class A a where
>   subtract :: a -> a -> a
> class B a where
>   add :: a -> a -> a
>   neg :: a -> a
>
> -- interpreter from A to B: (doesn't work of course)
> class (B a) => A a where
>   subtract x y = add x (neg y)
>
> -- interpreter for B in terms of Int:
> instance B Int where
>   add = (+)
>   neg = negate
>
> expression :: (A a) => a
> expression = subtract (subtract 4 5) 6
> -- to get it to be interpreted, we need to select the type. usually we
> use a monomorphic version of id:
> asInt = identity :: Int -> Int
> intExp = asInt expression :: Int
>
> For the instance B Int to be re-usable as instance/interpreter of (A
> Int) expressions, we need a way to write an interpretation of A a in
> terms of any pre-existing interpretation of B b.
> Usually there are some issues, the need to wrap the types into newtype
> wrappers among others.
>
> But in the case i'm trying to solve, it still doesn't work. To see where
> i'm stuck, see above my lpaste.
>
> Any help or ideas would be very welcome! :) Thanks a lot in advance.
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> To (un)subscribe, modify options or view archives go to:
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
> Only members subscribed via the mailman list are allowed to post.
>


_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.

signature.asc (499 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Fwd: issues with ad-hoc polymorphic typeclass instances

Weylin Lam

---------- Forwarded message ----------
From: Weylin Lam <[hidden email]>
Date: Sat, Sep 2, 2017 at 8:37 AM
Subject: Re: [Haskell-cafe] issues with ad-hoc polymorphic typeclass instances
To: Jonas Scholl <[hidden email]>


i actually hadn't thought of that! It's an good idea, indeed.

The issue is, in terms of DSLs, there'd be no way to prevent a potential mix between the more abstract language and its translation, because expressions are connected to their DSLs via constraints, and here without a class for the language OpEnv, there'd be no way to prevent say the use of ask mixed with addWithEnv, since to use the latter i'd need to put a (Op e, OpEnv e m) constraint...

Also, without class i simply couldn't define and use the language OpEnv as is, regardless of the existence of its implementation in terms of Op and Env. Sure for my trivial example it's not very important, but it *is* just a trivial example, it doesn't mean it's always irrelevant... at least i think so.

On Sat, Sep 2, 2017 at 7:48 AM, Jonas Scholl <[hidden email]> wrote:
Do addWithEnv and mulWithEnv need to be functions in a type class?
Couldn't you just define them as normal functions? Then all the problems
with undecidable instances are gone. I also don't see how having them in
a type class helps you in any way if you want to define such a far
reaching instance for it. Are there cases where you would need another
instance? If not, then putting it in a plain function is the way to go.
If yes, then you can not define Op and Env for the type of your OpEnv
instance, which seems counterintuitive. Otherwise you would run into
overlapping instances and I don't know if you want that, probably not.

On 09/02/2017 07:10 AM, Weylin Lam wrote:
> Hello !
>
> I'm trying to write a polymorphic class instance in function of another
> class. So far it's pretty hard, I have trouble with headsizes and so on.
> I actually had to switch to type families pretty fast, and i'm still not
> out of the woods.
> http://lpaste.net/2650337298029215744
> <http://lpaste.net/2650337298029215744>
>
> The context:
> It concerns "tagless final interpreters" for EDSLs as described
> in http://okmij.org/ftp/tagless-final/course/lecture.pdf
> <http://okmij.org/ftp/tagless-final/course/lecture.pdf>
> A comparison with free
> monads: http://yowconference.com.au/slides/yowlambdajam2016/Hopkins-StopPayingForFreeMonads.pdf
> <http://yowconference.com.au/slides/yowlambdajam2016/Hopkins-StopPayingForFreeMonads.pdf>
>
> The technique is fairly simple: instead of using ADTs to encode the
> operations of the language, we use haskell's class system. Languages
> become type classes, and interpreters are the instances which give
> various different meanings to the language expressions, which become
> simple haskell, ad-hoc polymorphic values.
>
> It has strong advantages. For example, to write some expression in the
> composition of two languages (two typeclasses), you only need to combine
> the constraints:
> class WriteFile h m where
>   writeFile :: h -> String -> m ()
> class ReadFile h m where
>   readFile :: h -> m String
>
> -- we use Monad m to have access to monadic operations, but the language
> itself
> -- does not care that m be a monad or not.
> myExpression :: (WriteFile h m, ReadFile h m, Monad m) => h -> h ->
> String -> m String
> myExpression h h2 str = writeFile h str *> readFile h2
>
> The fusion of both languages is utterly seamless, which is the beauty of
> typeclasses.
>
> When writing DSLs, there are at least two basic operations that need to
> be done: combining DSLs to create richer languages (and as just shown,
> it's really simple with this technique), and translating one DSL into
> another.
>
> That latter operation between DSLs is a bit more complicated with
> classes. I haven't found examples of how to do so in the wild (the
> technique doesn't seem very much used, esp compared with free monads),
> so if anybody knows how to do it or has references on that, it'd be much
> appreciated.
>
> Theoretical example of what i'm trying to perform:
> class A a where
>   subtract :: a -> a -> a
> class B a where
>   add :: a -> a -> a
>   neg :: a -> a
>
> -- interpreter from A to B: (doesn't work of course)
> class (B a) => A a where
>   subtract x y = add x (neg y)
>
> -- interpreter for B in terms of Int:
> instance B Int where
>   add = (+)
>   neg = negate
>
> expression :: (A a) => a
> expression = subtract (subtract 4 5) 6
> -- to get it to be interpreted, we need to select the type. usually we
> use a monomorphic version of id:
> asInt = identity :: Int -> Int
> intExp = asInt expression :: Int
>
> For the instance B Int to be re-usable as instance/interpreter of (A
> Int) expressions, we need a way to write an interpretation of A a in
> terms of any pre-existing interpretation of B b.
> Usually there are some issues, the need to wrap the types into newtype
> wrappers among others.
>
> But in the case i'm trying to solve, it still doesn't work. To see where
> i'm stuck, see above my lpaste.
>
> Any help or ideas would be very welcome! :) Thanks a lot in advance.
>
>
> _______________________________________________
> Haskell-Cafe mailing list
> To (un)subscribe, modify options or view archives go to:
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
> Only members subscribed via the mailman list are allowed to post.
>



_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.



_______________________________________________
Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Only members subscribed via the mailman list are allowed to post.