Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reuse same function body for multiple type signatures

Tags:

haskell

I have a function like so:

test :: [Block] -> [Block]
test ((Para foobar):rest) = [Para [(foobar !! 0)]] ++ rest
test ((Plain foobar):rest) = [Plain [(foobar !! 0)]] ++ rest

Block is a datatype that includes Para, Plain and others. What the function does is not particularly important, but I notice that the body of the function ([Para [(foobar !! 0)]] ++ rest) is the same for both Para and Plain, except that the constructor used is the type of foobar.

Question: is there someway to concisely write this function with the two cases combined? Something like

test :: [Block] -> [Block]
test ((ParaOrPlain foobar):rest) = [ParaOrPlain [(foobar !! 0)]] ++ rest

where the first ParaOrPlain matches either Para foobar or Plain foobar and the second ParaOrPlain is Para or Plain respectively.

Note that Block can also be (say) a BulletList or OrderedList and I don't want to operate on those. (edit: test x = x for these other types.)

The key is that I don't want to replicate the function body twice since they're identical (barring the call to Para or Plain).

I get the feeling I could use Either, or perhaps my own data type, but I'm not sure how to do it.


Edit To clarify, I know my function body is cumbersome (I'm new to Haskell) and I thank various answerers for their simplifications.

However at the heart of the problem, I wish to avoid the replication of the Para and Plain line. Something like (in my madeup language...)

# assume we have `test (x:rest)` where `x` is an arbirtrary type
if (class(x) == 'Para' or class(x) == 'Plain') {
    conFun = Plain
    if (class(x) == 'Para')
        conFun = Para
    return [conFun ...]
}
# else do nothing (ID function, test x = x)
return x:rest

i.e., I want to know if it's possible in Haskell to assign a constructor function based on the type of the input parameter in this way. Hope that clarifies the question.

like image 717
mathematical.coffee Avatar asked Dec 26 '22 02:12

mathematical.coffee


2 Answers

One approach is to use a (polymorphic) helper function, e.g.:

helper ctor xs rest = ctor [ head xs ] : rest

test :: [Block] -> [Block]
test ((Para xs):rest)  = helper Para xs rest
test ((Plain xs):rest) = helper Plain xs rest
test bs                = bs

Note that Para and Plain are just functions of type [whatever] -> Block.

like image 67
ErikR Avatar answered Jan 08 '23 01:01

ErikR


Assuming a data type declaration in the form:

data Block
    = Para { field :: [Something] }
    | Plain { field :: [Something] }
    ...

You can simply use a generic record syntax:

test :: [Block] -> [Block]
test (x:rest) = x { field = [(field x) !! 0] } : rest

Live demo

like image 43
Shoe Avatar answered Jan 08 '23 02:01

Shoe