2

Can the following type T be defined formally in Haskell or another functional language?

Type T contains functions that, given a collection (i.e., a set) of objects from X, assign a number to each object in that collection.

For example, let's say we have a function t from T. Its argument arg must be a collection (set) of objects from X, for example a list of strings ['abc', 'def', 'xyz']. Its return value must be a function r that takes only three possible arguments: 'abc', 'def' or 'xyz', and returns a number. I particular don't want r to accept just any string as an argument in this case.

An example might be "rank function", which, given a collection of objects, somehow assigns a rank to each of them. It doesn't return just a collection of numbers; rather it returns a function that takes any of the members of the original collection, and returns the rank of that member.

Of course, if I change my requirement a little, the things become super simple. I could ask that function t simply takes a collection of objects and return a collection of numbers. This is almost, but not quite the same. Such a definition would require some additional work: I would have to match objects from the input collection to the objects in the output collection. And also it would not be as precise: potentially the returned collection of numbers may not match the input objects (e.g., there may be one number too many).

I would not be surprised if the constraint I'm describing is not expressible as a type constraint, and should be enforced in a different way.

EDIT: I was originally asking also to define a type U which contains functions that take a function from X into numbers, and returns a function of type T. But I didn't explain this well, and it only adds confusion to my question. So it's better to ignore this part of my question.

max
  • 49,282
  • 56
  • 208
  • 355
  • I think `U` is just a specialized [`Functor`](http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#t:Functor). – phipsgabler Sep 18 '12 at 11:15

3 Answers3

1

Edit: In light of an update to the question I have completely rewritten this answer.

Ignoring the emphasis on types for a second and taking a practical approach, lets go about constructing this functionality in Haskell. Firstly, here is a function that, given a function, a list of objects, and an object, applies the function to the object if that object is in the list.

u f list x = assert (x `elem` list) $ f x

(the assert function is from Control.Exception) This can also be achieved with a guard:

u f list x | (x `elem` list) = f x --similar but gives a slightly different error msg

Running some examples in ghci gives the following:

*Main> u (+1) [1,2,3] 1
2
*Main> u (+1) [1,2,3] 2
3
*Main> u (+1) [1,2,3] 3
4
*Main> u (+1) [1,2,3] 4
*** Exception: test.hs:3:14-19: Assertion failed

However, this isn't very safe code as there are no static guarantees that the function won't be called in this way causing a crash.

An alternative approach is to use the Maybe type.

u' f list x = if elem x list then Just (f x) else Nothing

which has the following behaviour:

*Main> u' (+1) [1,2,3] 2
Just 3
*Main> u' (+1) [1,2,3] 3
Just 4
*Main> u' (+1) [1,2,3] 4
Nothing

This still doesn't guarantee statically that the function won't be called in the incorrect way, but it does guarantee statically that any result based on the return value must handle both Just result and Nothing. This also carries the advantage that Maybe is a monad, which can be thought of as computations that can fail. So you can chain calls easily like this

*Main> let func = u' (+1) [1,2,3]
*Main> func 1
Just 2
*Main> func 1 >>= func
Just 3
*Main> func 1 >>= func >>= func
Just 4
*Main> func 1 >>= func >>= func >>= func
Nothing

This seems likely the best practical way of solving a problem like this in Haskell. However, the question asks if there is a type that specifically pins down a function like this. Lets look at the types of these functions:

u  :: Eq x => (x -> a) -> [x] -> x -> a
u' :: Eq x => (x -> a) -> [x] -> x -> Maybe a

Neither of these types demand that it is membership of the list that makes them work or not. The only way I can think of to work this as a type is to consider the collection itself as a type. Call that type col, then we could do

u :: (col -> x) -> col -> x

but not only is this of little practical value, it also doesn't pin down these functions precisely. Neither does an approach based on pairs. In my experience trying to construct highly restrictive types like the type containing only the values in ['abc', 'def', 'xyz'] is not hugely practical.

Vic Smith
  • 3,477
  • 1
  • 18
  • 29
  • If I understand this correctly, I meant `tMap' :: [a] -> a -> [Int]`. Except that `a` here ensures that the type of elements in the list is the same as the type of the non-list argument. What I need, though, is that the non-list argument is *actually one of the elements in the list*. That is, the function that results from `tMap' [1,2,3]` won't complain if it's passed argument `4` (since `4` is the same type as `1`, `2`, `3`). I want it to check that the argument it received is actually from the original list. Sorry for the poor notation. – max Sep 18 '12 at 11:43
  • Lets try and clear this up a bit as I'm now not entirely sure what you are after. As I understood what you were asking, you were wanting to promote in some sense, functions that assign values a number (ie `f :: a -> Int` or `f :: Num n => a -> n` if you want to be more flexible/complicated) to functions from collections of `a` to collections of numbers, (so `g :: [a] -> Int` or `g :: Num n => [a] -> n`). Is this the kind of thing you meant, and how does this fit in with the additional input of a non-collection object? – Vic Smith Sep 18 '12 at 13:08
  • Sorry for the poor explanation in my question, I rewrote it - perhaps it makes more sense now? – max Sep 18 '12 at 16:25
  • Right, I hope I have understood you this time, and have re-written my answer at length :) – Vic Smith Sep 18 '12 at 17:44
  • (Note that `(\x -> x + 1)` is just `(+1)`, which is a little briefer :) ) – huon Sep 18 '12 at 19:23
1

T - Tagging

OK, I'd like to rename your types to help understand them. T assigns numbers to values, and since I'm not sure what the structure of the container is (and would like to remain flexible), I'll pop the value and the number in a pair (x,n). Let's call that tagging, so reaname T Tagger, so assuming I know your collection type is Coll a collection of Xs would have type Coll X, so I'll need

type CollTaggerXInt = Coll X -> Coll (X,Int)

which makes a type synonym for the functions you want.

But what if Int's too small and you want to use Integer, or Double or some other numeric type?

data Num n => CollTaggerX n = CollTaggerX (Coll X -> Coll (X,n))

This means that you can tag X values with any fixed type of numeric data. (Num n => is a datatype constraint that asserts that n has to be a numeric type.) The CollTaggerX on the right hand side ensures type safety by wrapping your tagging function in a lightweight constructor. We need to use data instead of type because I've parameterised by n.

I'd be inclined to replace the fixed type X and the collection type Coll with type parameters (like generics in some languages eg Java) to improve code reuse:

data (Functor coll,Num n) => Tagger coll x n = Tgr (coll x -> coll (x,n))

So now we've insisted that the collection type is a Functor, so we can use fmap to apply functions pointwise to collections (crucial in your case, and any collection type will be an instance of Functor).

I'm happiest with that definition of Tagger for your T, but you can use CollTaggerXInt if you have just one possibility for coll, x and n.

U - Making Taggers

Your U type is for turning elementwise taggers into collection taggers. I feel like calling that Lifting, rather than U. If you're using CollTaggerXInt, you can use a type synonym again:

type LiftXIntToTagger = (X -> Int) -> Coll X -> Coll (X,Int)

or if you're using the more flexible Tagger definition, you could write

data (Functor coll,Num n) => Lifter coll x n = Lifter ((x -> n) -> coll x -> coll (x,n))

but this all feels crazy to make a type for this, because if you've got a pointwise function, you can allready lift it using fmap which works on your coll type anyway:

fmap :: Functor f => (a -> b) -> f a -> f b

So we can use this with coll as f, x as a and (x,n) as b, and define

liftT :: (Functor coll,Num n) => (x -> n) -> coll x -> coll (x,n)
liftT f = fmap tag   where 
      tag x = (x,f x)

If you want to define your type, OK, but I think liftT might be the only sensible function in your type U.

Rank - U+Context

Now I think your rank example is useful, so let's investigate that. A rankfunction needs to examine all elements of the collection, so let's give it the whole collection as it's first parameter, so rankfunction :: coll x -> x -> n (within the context (Functor coll,Num n)).

liftInContext :: (Functor coll,Num n) => (coll x -> x -> n) -> coll x -> coll (x,n)
liftInContext rankfunction mycoll = liftT (rankfunction mycoll) mycoll

Here the function (rankfunction mycoll) passes the rankfunction its first parameter - the whole collection - before using liftT to apply it to each element. This is called partial evaluation, and is very handy for this sort of thing.

AndrewC
  • 32,300
  • 7
  • 79
  • 115
  • Would it be possible to enforce the restriction that a function of type `CollTaggerXInt` must, given a collection `coll`, return pairs where the first element must always be a member of `coll`? And in fact, every member of `coll` shows up exactly once in the first position in the returned pairs? – max Sep 18 '12 at 12:05
  • Yes, if you use `data` rather than `type` and insist that values of `CollTaggerXInt` are only made using `liftT` or `liftIncontext`. You can achieve this by putting all these functions and types in a module which doesn't export the constructors (`Tgr`), just the functions and types (`Tagger`). You'll need some other functions to give you the resulting collections back out, or for more safety, wrap them in your own `data` type with unexported constructors. – AndrewC Sep 18 '12 at 12:25
  • @max: the use of the functions `liftT` and/or `liftIncontext` ensure the 1-to-1 restriction you ask for, as they pass the `x` through unchanged. You don't need fancy type definitions if you restriuct to just using those two functions, they have the type `U` you need. – AndrewC Sep 18 '12 at 15:04
  • Thank you, I'll read a little more about Haskell, to make sure I understand your examples correctly. – max Sep 18 '12 at 16:30
0

If you want a function that takes a value and returns a type (among other things), you're going to need dependent types. You can simulate dependent types in Haskell, but it ain't pretty.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380