-2

I need to define a Union Type which the set of the type includes

a -> b OR a -> IO b

a -> b || a -> IO b

or

a -> b | IO b

foo :: (a -> b | IO b) -> IO (R a) -> IO (R b)

I tried data unionType b = b | IO b that does not work.

Not a data constructor: ‘b’parser
No quick fixes available

Is it possible in Haskell?

Functor
  • 582
  • 1
  • 9
  • 3
    I know it seems like we're smart here, and can figure it out without knowing anything else, but it's still polite to include *observed behavior, expected behavior,* and any *error messages* you are getting, in your post. – Robert Harvey Feb 07 '22 at 21:20
  • @RobertHarvey Well, I think the question is concise enough and I also added the proof element of "what I've tried so far". I know someone here don't like the "short question" but what did you feel not "polite"? Length of the question? – Functor Feb 07 '22 at 21:29
  • Q&A is not a Bug report, so *observed behavior, expected behavior, and any error messages* does not fit the quesion formatl. – Functor Feb 07 '22 at 21:30
  • 3
    The thing is, this is an instance of a broader very common beginner question. In order to provide an answer that really addresses the challenges you're facing, we need to understand what they are. What is your current understanding? Why did you end up thinking that? Is there an improvement we can make to standard introductory materials that will make it easier for others in the future? – Carl Feb 07 '22 at 21:38
  • @Carl In my opinion, it'd depend on POV. I am rather an experienced functional programmer in TypeScript (Yes this is possible in TS) and wrote books in Japanese. Here I don't know why a person who asks quesions must prove their programming ability for this simple question. This quesiton is indepndent of the further application. – Functor Feb 07 '22 at 21:44
  • 2
    I would be extremely surprised to see anything of this type in a professional Haskell application. The idiomatic approach is to _always_ return `IO b`, whether or not any IO actually occurred. While it's possible to do this, it wouldn't be good practice. – Louis Wasserman Feb 07 '22 at 21:49
  • Possibly relevant: [*Why do We Need Sum Types?*](https://stackoverflow.com/q/40620913/2751851) – duplode Feb 07 '22 at 21:51
  • Yes it actually always return `IO (R a) -> IO (R b)` The code simply require to accept both `a -> b` and `a -> IO b` What's wrong with this in a professional Hakell applications? – Functor Feb 07 '22 at 21:53
  • 2
    @Functor As we've tried to explain on your previous two questions: the thing that's wrong with it is that it leads to very complicated code, whereas the alternative leads to simple code. Instead of this complicated, unidiomatic thing, simply accept an `a -> IO b`, and leave it to your caller to turn its `a -> b` into an `a -> IO b` before calling your function. – Daniel Wagner Feb 07 '22 at 22:02
  • @DanielWagner Well, thanks for your patience, but I must disagree with your opinion to force what the software design should be. If a function can be genric not to bother caller. It should be done inside of the function to avoid complexity. Your approach occurs complexity explostion. – Functor Feb 07 '22 at 22:06
  • 4
    Having programmed in Haskell professionally for almost a decade now, I can confidently say that empirically, the approach I describe does not cause complexity explosion. – Daniel Wagner Feb 07 '22 at 22:09
  • @DanielWagner Ok, let me confirm, please. Do you suggest to prepare 2 functions(functors) for `f :: a -> b` and `f:: a -> IO b` ? – Functor Feb 07 '22 at 22:47
  • 2
    @Functor No, just one function; `foo :: (a -> IO b) -> IO (R a) -> IO (R b)`. In fact, I would almost certainly additionally drop the `IO` from the other input, making it `foo :: (a -> IO b) -> R a -> IO (R b)`, except in very unusual circumstances. – Daniel Wagner Feb 07 '22 at 23:18
  • @DanielWagner IMO, to modify every pure function to a -> IO a is so much problematic and unacceptable, so I decided to have 2 separated functions for non-IO and IO. This method works and is simple enough. Dropping the `IO` from the other input binds function within `do` thread, and limit the usage, so I won't do that. Thanks anyway. – Functor Feb 08 '22 at 03:43
  • 5
    @Functor If you have a function `f`, then `pure . f` has the appropriate type. I think you will find that, as you use Haskell more, having a flexible function and using something like `(pure .)` at the call-site when needed introduces *much* less complexity. It is often better to use small, flexible/generic functions in combination rather than many specialized functions. Right now, it feels like you are trying to work *against* the language (for instance, here you are actively avoiding the flexibility the language provides). This usually causes more pain, regardless of the language. – David Young Feb 08 '22 at 04:42
  • 1
    @Functor I agree, modifying every pure function to actually claim to do `IO` would indeed be problematic. Instead, when you have a pure function that needs to be used in a monadic context, use `return` (or `pure` -- take a guess why it's named that) to adapt it on the spot, without changing its source. Yes, dropping `IO` from the other input requires the caller to bind the output of their `IO` action. This is usually what you want, anyway, as it gives you greater control over when side effects are done (and how many times, more to the point). – Daniel Wagner Feb 08 '22 at 14:42
  • @DavidYoung Thanks, I understand it's easy to have it by composing the function. I will study more. – Functor Feb 10 '22 at 16:30
  • @DanielWagner Thanks again, I sort of understand now why you suggest `foo :: (a -> IO b) -> R a -> IO (R b)`; however. for non-IO function, I think `foo :: (a -> IO b) -> R a -> R b)` is simpler, but hard to implement to make it work as expected. – Functor Feb 10 '22 at 16:33

2 Answers2

5

In Haskell, a sum type (the standard term for a disjoint union type) requires explicit "constructors" for its components, so you need to write something like:

data BarType b = BarPure b | BarIO (IO b)

This defines a type BarType, and it also defines constructors BarPure and BarIO for use in constructing values of this type:

val1, val2 :: BarType String
val1 = BarPure "pure"
val2 = BarIO getLine

and consuming values of this type via case-matching:

runBar :: BarType b -> IO b
runBar barb = case barb of
  BarPure s -> pure s
  BarIO act -> act
K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • 5
    ...but if all you're going to do is accept one of these as a function argument, then you might as well write `barPure = return; barIO = id` and accept an `IO b` instead of a `BarType b`. – Daniel Wagner Feb 07 '22 at 22:00
2

There are two issues: On the one hand your type must be upper-case, that is UnionType. Second, you need to define type constructors to be able to create a value of said data type. So the closest would be e.g.

data UnionType b = Left b | Right (IO b)

Here I chose the arbitrary names Left and Right for the constructors, but you can use any you like.

If the Left/Right constructors seem familiar to you, that is because Either uses these by default, and can do exactly the same with a slightly "cheaper" type synonym:

type UnionType b = Either b (IO b)

Of course in both cases we have to deconstruct the type to get to the actual values of b and IO b.

EDIT: As @leftroundabout mentioned, it is not very convenient to define a data type with type constructors called Left and Right, as these constructors are already used with the built in Either.

flawr
  • 10,814
  • 3
  • 41
  • 71
  • 4
    Please don't call the constructors `Left` and `Right`. Either simply use `Either b (IO b)` instead of defining any custom data type, which already has the `Left` and `Right` constructors, or define your own and call them different then. – leftaroundabout Feb 07 '22 at 21:43
  • Thanks, can you answer an alternative you can think of? @leftaroundabout – Functor Feb 07 '22 at 21:49
  • @leftaroundabout Thanks for the suggestion, we were thinking the same thing - I just wasn't completely finished the answer yet! – flawr Feb 07 '22 at 21:52