Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to define a higher-order "opposite" function on a case-by-case basis?

Tags:

haskell

I am a Haskell novice. I am trying to create a mini-language within Haskell, and would like if possible to have a higher-order function called opp (short for "opposite") that converts a number of familiar functions into their obvious opposites. For example, opp succ would be the function pred, opp head would be last, and so on. I don't have some general definition of what it means to convert a function into its opposite: I just want to pick a few key examples and declare what their opposites. So I want a highly polymorphic function that is hardly ever defined.

The difficulty seems to be that I want to recognise the functions by their names rather than by their essences (so to speak). One manifestation of that difficulty is that if I write

opp succ = pred

then Haskell treats succ as a variable and therefore gives me a constant function that always takes the value pred. What I really want is to say something more like, "If you ever see the string opp succ then think of it as another name for pred." But after searching around for quite a while, I can't find out how to do that (if it's possible at all).

To summarize, I would like to define a function

opp :: (a -> b) -> (a -> b)

by saying things like

opp succ = pred

opp pred = succ

opp head = last

opp last = head

and adding to this list whenever I feel like it. Obviously I can't do it like that, but is there some non-horrible way of achieving the same effect?

like image 856
user15553 Avatar asked Jun 16 '14 10:06

user15553


2 Answers

Yes you can, however you require RankNTypes in order to have a nice implementation.

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE RankNTypes #-}
module Opposites where

class Opposite f where
  makeOpposite :: (a -> b) -> (a -> b) -> f a b


data FunctionAndOpposite a b = FunctionAndOpposite (a -> b) (a -> b)

instance Opposite (->) where
  makeOpposite = const

instance Opposite FunctionAndOpposite where
  makeOpposite = FunctionAndOpposite

opp :: FunctionAndOpposite a b -> a -> b
opp (FunctionAndOpposite _ f) = f

type a :<-> b = forall f. Opposite f => f a b

succ' :: Enum a => a :<-> a
succ' = makeOpposite succ pred

pred' :: Enum a => a :<-> a
pred' = makeOpposite pred succ

head' :: [a] :<-> a
head' = makeOpposite head last

last' :: [a] :<-> a
last' = makeOpposite last head

Example usage:

> head' [1,2,3]
1
> opp head' [1,2,3]
3

How it works

Firstly, there is the Opposite class. This just describes an f a b that can be constructed out of two functions of (a -> b) (the normal and opposite functions).

Next, a data type FunctionAndOpposite is defined. This just stores the two functions. Now both the standard function, and this are made instances of the class Opposite. The function instance just discards the opposite function.

Now the opp function can be defined. This just takes the second function out of a FunctionAndOpposite.

Finally we get the line that brings it all together:

type a :<-> b = forall f. Opposite f => f a b

what this defines is a type synonym which takes two input types a and b, then returns a type f a b, where f can be any Opposite (depending on what is needed).

(the :<-> is just a fancy name for the type enabled with TypeOperators).

Consider the code head' [1,2,3]. head' has the type forall f. Opposite f => f [a] a. However, as it is being used as a function application (since it has an argument straight afterwards), f must be ->. Therefore, the function implementation of Opposite is used, returning the first argument to makeOpposite, which is head (great!).

However, lets say opp head' [1,2,3] is called. opp has the type FunctionAndOpposite a b -> a -> b, so f must be FunctionAndOpposite. Therefore, the FunctionAndOpposite instance of Opposite is called, creating the data type using both arguments to makeOpposite. opp then pulls out the second function, which is last.


As an aside, this is done using a similar technique used in the lens library for Isomorphism. An isomorphism is basically a pair of functions that are inverses of each other. Eg (+2) and (-2), reverse and itself. This is probably a more useful concept in general, as it allows you to run operations on a data type over a transformation. See Control.Lens.Iso for more details, however note that to understand this you would need to understand the lens concepts, which is a pretty big job.

like image 190
David Miani Avatar answered Nov 28 '22 17:11

David Miani


If we reformulate your question into this form the problem may become more clear to you:

opp x | x == succ = pred
      | x == pred = succ

This will give you the error that (a -> b) has no Eq instance, which it cannot have since equality for functions is undefined.

A solution to this would be to define a separate datatype that you can pattern match on.

like image 37
nulvinge Avatar answered Nov 28 '22 18:11

nulvinge