3

I'm playing with my first non-trivial (in my eyes) attempt at something in Haskell. I'm probably going to ask questions about each part to compare my coming-from-long-term-relationship-with-c-like-languages attempt to what you-as-seasoned-functional-programmer might do. Luckily Haskell makes it hard to fall back on straight c-to-haskell code conversion. You have to learn how to do it right - and I want to.

For this part, I have a [2uple] and a 2uple. I want to know if any item in the 2uple is in any of the items in the [2uple].

hasmatch tlst tupm = foldr (\tup acc -> 
                                let tmatch (a,b) (c,d) = a==c || b==c || a==d || b==d in
                                if tmatch tup tupm
                                then (Just True)    -- short out
                                else acc            -- do nothing, keep looping
                          ) Nothing tlst

-- hasmatch [(1,2), (3,4), (5,6)] (5,3) ==> Just True
-- hasmatch [(1,2), (9,4), (7,6)] (5,3) ==> Nothing

I'd prefer to return a plain Bool but no biggie there.

I'm sure there is another way that I would have to grok at for a while to understand. That is good. Looking for takers.

Thanks

user49011
  • 523
  • 1
  • 3
  • 10
  • What do you mean, tuples are not necessary? Do you have a pair of things and a list of pairs? Or do you have something else? – amalloy Jan 11 '22 at 02:27
  • @amalloy I removed the confusing line... – user49011 Jan 11 '22 at 02:38
  • 1
    In any language that supports pairs (`C`, `Haskell`, `Basic`, `ALGOL`, ...): `(5,3) /= (3,5)`. That's really rather the point with tuples. So why in your `==> Just True` result do you want to match `3` in right position of a tuple with `3` in left position of a tuple within the list? Isn't your list really just `Eq a => [a]`? If you were doing this in `C`, what would be the 'business/requirements scenario'? – AntC Jan 11 '22 at 04:06
  • If position in the tuple really is immaterial: flatten the tuples out of `tlst` to a bare list of elements; similarly flatten the pair out of `tupm` to a 2-element list; look for flattened `tupm` being a subset of flattened `tlst`. (Looking for one list a subset of another is a FAQ.) – AntC Jan 11 '22 at 04:15
  • Why return a `Maybe Bool` if the only possible outputs are `Just True` and `Nothing`? Why not just return `True`/`False` respectively? – Robin Zigmond Jan 11 '22 at 07:17
  • IOW you can just replace `Just True` with `True` in your code. – Will Ness Jan 11 '22 at 11:11
  • the test `a==c || b==c || a==d || b==d` can be expressed as ``elem a [c,d] || elem b [c,d]``. or we could write that as ``any (`elem` [c,d]) [a,b]``, *"any of `a`,`b` is an element of `[c,d]`"*. – Will Ness Jan 11 '22 at 11:25

2 Answers2

3

Let's start from your code.

hasmatch tlst tupm =
   foldr (\tup acc -> 
          let tmatch (a,b) (c,d) = a==c || b==c || a==d || b==d in
          if tmatch tup tupm
          then (Just True)    -- short out
          else acc            -- do nothing, keep looping
         ) Nothing tlst

Since you say you prefer a boolean result, we can switch to that. Indeed, a boolean would be better since in the above code we never return Just False.

hasmatch tlst tupm =
   foldr (\tup acc -> 
          let tmatch (a,b) (c,d) = a==c || b==c || a==d || b==d in
          if tmatch tup tupm
          then True    -- short out
          else acc     -- do nothing, keep looping
         ) False tlst

Now, if x then True else y is simply x || y.

hasmatch tlst tupm =
   foldr (\tup acc -> 
          let tmatch (a,b) (c,d) = a==c || b==c || a==d || b==d in
          tmatch tup tupm || acc
         ) False tlst

If you wonder, || will not evaluate acc when we find a match. This is the same lazy semantics as C short-circuiting. Compared to C, the main difference is that a boolean variable acc can represent the unevaluated boolean result, while in C it's always a fully evaluated boolean value.

Now, tmatch uses the same second argument, tupm, so we can inline that, and pattern-match early:

hasmatch tlst (c,d) =
   foldr (\tup acc -> 
          let tmatch (a,b) = a==c || b==c || a==d || b==d in
          tmatch tup || acc
         ) False tlst

We can even move the let outside to improve readability.

hasmatch tlst (c,d) =
   let tmatch (a,b) = a==c || b==c || a==d || b==d 
   in foldr (\tup acc -> tmatch tup || acc) False tlst

We can also use a where here:

hasmatch tlst (c,d) = foldr (\tup acc -> tmatch tup || acc) False tlst
   where tmatch (a,b) = a==c || b==c || a==d || b==d

Finally, we can exploit a library function: any. Calling any p list evaluates to True iff there is any element in list that satisfies property p. Our code becomes:

hasmatch tlst (c,d) = any tmatch tlst
   where tmatch (a,b) = a==c || b==c || a==d || b==d

That's it.

Note that there are alternatives to the above approach. One could be turning the list of tuples [(a,b), (c,d), ... into a list of all the components [a,b,c,d,..., and working with that.

hasmatch tlst (c,d) = any tmatch [ x | (a,b) <- tlst, x <- [a,b]]
   where tmatch x = x==c || x==d
chi
  • 111,837
  • 3
  • 133
  • 218
  • This is really great. Step by step set of improvements. An example of me missing something simple (other than the True/False thing) is I have read about ```any```. Learning doesn't sink in by reading and I was actually hoping for something that might lose the ```foldr``` just to show me an alternative. Thanks for taking the time. – user49011 Jan 11 '22 at 14:06
3

Based on chi's answer you may be better off defining a datatype with this equality?

{-# Language DerivingStrategies       #-}
{-# Language InstanceSigs             #-}
{-# Language StandaloneKindSignatures #-}

import Data.Kind (Type)

type AnyTuple :: Type -> Type
data AnyTuple a = a :×: a
  deriving stock Show

instance Eq a => Eq (AnyTuple a) where
  (==) :: AnyTuple a -> AnyTuple a -> Bool
  a1:×:b1 == a2:×:b2 = or $ liftA2 (==) [a1, b1] [a2, b2]

Then the function you seek is a standard library function like elem or find

>> 5:×:3 `elem` [1:×:2, 3:×:4, 5:×:6]
True
>> 5:×:3 `elem` [1:×:2, 9:×:4, 7:×:6]
False

>> find (== 5:×:3) [1:×:2, 3:×:4, 5:×:6]
Just (3 :×: 4)
>> find (== 5:×:3) [1:×:2, 9:×:4, 7:×:6]
Nothing
Iceland_jack
  • 6,848
  • 7
  • 37
  • 46
  • This will certainly come in handy down the road. I did have some thoughts about going in this direction, but from scratch, a bit beyond me at this point. Thanks. – user49011 Jan 11 '22 at 14:10