I was reading an interesting article about monad, 'pipes' and being a 'red pipe'. From what it seems it is saying function parameters or classes are types of pipes to make sure everything fits. Then it goes about being a red pipe and must always be a red pipe and purity. This part I didn't understand
Why can't a 'red pipe' become a 'blue pipe'? Can a red pipe be converted to a plain pipe? What does this have to do with purity? and can I think of this being something like class RedPipe: PlainPipe { /* same interface and implementation here*/ } ?
In Haskell, the type of a function tells you exactly what it does and, more importantly, what it does NOT do. This makes it easier to reason about Haskell code.
For example, if I have a function of the following type:
f :: Int -> Int
I know that it takes an Int as input and produces an Int as output, and it does nothing else. Importantly, I know it has no side effects.
If I have a function of type:
g :: Double -> State Int Double
... that function takes a Double as an argument and produces a way to produce a Double, but only if I allow it to consult or modify some Int state.
If I have a function of type:
h :: Int -> Maybe String
I know that this function takes an Int and might produce a String, or it might fail, producing nothing. Notice that none of the previous two functions returned Maybe, meaning that they could NOT fail. Haskell does not permit failure (i.e. nullable values) by default.
If I have a function of type:
i :: String -> IO ()
I know that this function takes a String and can be run to produce side effects. Note that none of the previous functions had an IO in their type, meaning that they could not produce side effects. Haskell does not permit side effects by default. You must explicitly opt in to them in the type.
This means that we can look at the types of functions or values and immediately understand what features they are using. So, for example, if I see a Maybe in the type, I know there is a potential to fail, and if I don't, then I know there is no potential to fail. Similarly, if I see an IO in the type, then I know there is a potential for side effects, but if I see no IO in the type, then there is no potential for side effects.
In mainstream languages, you don't have this ability to selectively "opt-in" to features. All features are on by default, all the time, meaning that you must always check for null because you have no way to guarantee that some function didn't fail, and you must always run tests because you have no way to guarantee that some function didn't implicitly modify the state of your system.
Haskell lets you restrict the behaviors of functions to give you finer grained control over their "permissions", which makes it easier to scale to large programs without bugs and also makes it easier to read and understand Haskell code because the type enumerates the full range of the code's behaviors, so you never have to understand more than what the type permits.
The author is using the terms "red pipe" and "blue pipe" as a metaphor. As far as I know, that's not widely used terminology. I think the key point of his article is that type safety provided by languages such as Java help to catch some kinds of programmer mistakes, isolating pure from impure functions can catch even more. Using one of his examples:
square :: Double -> Double
This type signature tells me that
The function square doesn't have any side effects. It's not going to sneak off and update a database, or print something to the screen, or change some state data, or otherwise surprise me. (In Java, or C, for example, I would have to read the function and any functions it calls, or rely on the documentation, to know what the function does.)
Every time I call it with a particular value, say, square 5, I will get exactly the same result. This is called referential transparency, and it allows the compiler to do some optimisation because it knows that this value will never change, so it only needs to be calculated once.
Related
Assume code below. Is there a quicker way to get the contextual values out of findSerial rather than writing a function like outOfContext?
The underlying question is: does one usually stick within context and use Functors, Applicatives, Monoids and Monads to get the job done, or is it better to take it out of context and apply the usual non-contextual computation methods. In brief: don't want to learn Haskell all wrong, since it takes time enough as it does.
import qualified Data.Map as Map
type SerialNumber = (String, Int)
serialList :: Map.Map String SerialNumber
serialList = Map.fromList [("belt drive",("BD",0001))
,("chain drive",("CD",0002))
,("drive pulley",("DP",0003))
,("drive sprocket",("DS",0004))
]
findSerial :: Ord k => k -> Map.Map k a -> Maybe a
findSerial input = Map.lookup input
outOfContext (Just (a, b)) = (a, b)
Assuming I understand it correctly, I think your question essentially boils down to “Is it idiomatic in Haskell to write and use partial functions?” (which your outOfContext function is, since it’s just a specialized form of the built-in partial function fromJust). The answer to that question is a resounding no. Partial functions are avoided whenever possible, and code that uses them can usually be refactored into code that doesn’t.
The reason partial functions are avoided is that they voluntarily compromise the effectiveness of the type system. In Haskell, when a function has type X -> Y, it is generally assumed that providing it an X will actually produce a Y, and that it will not do something else entirely (i.e. crash). If you have a function that doesn’t always succeed, reflecting that information in the type by writing X -> Maybe Y forces the caller to somehow handle the Nothing case, and it can either handle it directly or defer the failure further to its caller (by also producing a Maybe). This is great, since it means that programs that typecheck won’t crash at runtime. The program might still have logical errors, but knowing before even running the program that it won’t blow up is still pretty nice.
Partial functions throw this guarantee out the window. Any program that uses a partial function will crash at runtime if the function’s preconditions are accidentally violated, and since those preconditions are not reflected in the type system, the compiler cannot statically enforce them. A program might be logically correct at the time of its writing, but without enforcing that correctness with the type system, further modification, extension, or refactoring could easily introduce a bug by mistake.
For example, a programmer might write the expression
if isJust n then fromJust n else 0
which will certainly never crash at runtime, since fromJust’s precondition is always checked before it is called. However, the type system cannot enforce this, and a further refactoring might swap the branches of the if, or it might move the fromJust n to a different part of the program entirely and accidentally omit the isJust check. The program will still compile, but it may fail at runtime.
In contrast, if the programmer avoids partial functions, using explicit pattern-matching with case or total functions like maybe and fromMaybe, they can replace the tricky conditional above with something like
fromMaybe 0 n
which is not only clearer, but ensures any accidental misuse will simply fail to typecheck, and the potential bug will be detected much earlier.
For some concrete examples of how the type system can be a powerful ally if you stick exclusively to total functions, as well as some good food for thought about different ways to encode type safety for your domain into Haskell’s type system, I highly recommend reading Matt Parsons’s wonderful blog post, Type Safety Back and Forth, which explores these ideas in more depth. It additionally highlights how using Maybe as a catch-all representation of failure can be awkward, and it shows how the type system can be used to enforce preconditions to avoid needing to propagate Maybe throughout an entire system.
The type Maybe a represents a computation that could fail, with the semantics that we don’t care about exactly how it failed. If the computation succeeds then any value of type a might be returned.
What about the inverse case, where the computation could fail for any number of reasons (and we want to preserve that information) but success doesn’t involve any information other than “yes, it succeeded”? I can think of two obvious ways to encode this kind of computation:
Maybe e, where Just e represents a failure and Nothing represents a success. This is so contrary to the usual use of Maybe that I would be reluctant to use it.
Either e (), where Left e represents a failure and Right () represents a success. This has the advantage of being explicit, but has the disadvantage of… being explicit. Writing () feels awkward, especially outside the context of a type signature.
Does Haskell have a more idiomatic way to represent “multiple failure cases but only one success case”?
Without seeing the actual code it is actually difficult to understand what you mean by failure. If it's a pure function then I don't see what using Maybe would be a problem. I never really see Nothing as failure but just as it is : Nothing. Depending on the context , I either return Nothing as well or, use a default value and carry on. I understand that it can be seen as a failure, but it more depends on the point of view
if the caller than the function itself.
Now, you want to represent a computation which can fails but returns nothing. If it is a pure function, that doesn't make sense. You function being pure, nothing has happened (no side effect) and you don't get a result. So in case of success, you actually computed nothing : that's not a success, that's nothing. ATHI If you fail, you've got a reason why it failed. That's no different from a simple check returning a Maybe.
For example you might need to check that a domain is not in a blacklist. For that you do a lookup in a list : Nothing means it's fine even though it means it's from your point of view and failure and need to stop your computation. The same code can be used to check your domain belongs to a white list. in that case Nothing is a failure : just depends on the context.
Now, if you are running a monadic action (like saving a file or something) it makes sense to return nothing but different failures can happened (disk full, path incorrect, etc). The standard signature for an IO which we don't care about the result is IO (), so you can either go for IO (Either e ()) (everybody will understand it) or go for IO () and raises exception (if they are genuinely exceptional).
A short way to go about this would be to use Either e () along with the pattern synonym
pattern Success :: Either e () -- Explicit type signature not necessary
pattern Success = Right ()
You could also include some other things as well, if it improves readability, such as
type FailableWith e = Either e ()
pattern FailedWith :: e -> FailableWith e
pattern FailedWith x = Left x
Unlike Maybe, Either has the advantage of having all the existing machinery already in place: the Functor, Applicative, Monad, Foldable, Traversable, Semigroup and even Ord (Left x < Right y should always hold) instances will likely already behave exactly how you would want for error handling. Generally, for this particular situation, Maybe will tend to do the opposite of what you want (usually you want to continue on a success and stop after the first failure, which is the opposite of what most of the Maybe machinery will provide for this scenario).
Its not clear from the question how the computation might fail.
If it is something like a compiler, which might produce a lot of error messages (rather than halting on the first one) then you want something like:
type MyResult a = Either [Error] a
which either succeeds with a result or fails with a list of reasons.
On the other hand if you have a non-deterministic computation where each variation might succeed or fail then you want something more like:
type MyResult a = [Either Error a]
Then search the list of results. If you find a Right then return it, otherwise assemble the list of Lefts.
It's always said that Haskell's type system prevents impure code from polluting pure code, since you always have to specify IO in the type signature. However, is that a consequence of the type system itself, or is it really just that IO(..) isn't exported?
Basically, is it not true that, if the type constructor was available, something like this could be done?
ioToPure :: IO a -> a
ioToPure (IO ioValue) = ioValue
Yes, that is in a sense true.
The type system in and of itself doesn't know anything about IO nor does it need to. It are different language features that hide the real representation of an IO action from the users, so that it is impossible to "just run" an IO action.
So, the truth is, that the IO security in Haskell and comparable languages is a combined result of several language features and properties, most prominently:
a strong type system that doesn't accept "shut up, I know better" from the user.
the property that everything is typed. What I mean here is that in impure languages, you have "statements" and even if the type system is strong, it doesn't look past the next semicolon. Whereas in Haskell, we only have expressions, and every bit of every expression gets a type, and influences, in the end, the type of the function the expression occurs in.
The language features that hide type representation.
Nevertheless, I think that the expression "the type system makes sure that impure and pure code is separated" is a harmless simplification.
Sure, exporting low-level primitives could allow side effects everywhere.
And, yes, you could obtain purity only by avoiding to export each dangerous thing. No type-level machinery is needed.
However, with no type constraints everything IO-related would be dangerous. So we would completely disallow IO. Not really useful.
With the help of the type system, instead, we can export some "dangerous" IO-actions, knowing that they can be executed only in "controlled" places, which have to carry an IO tag in their type. Making them no longer dangerous.
So, purity comes from a combination of static guarantees and careful exporting.
I'm sure it's not, but I've received the type IO FileOffset from System.Posix functions, and I can't figure out what I can do with it. It seems like it's just a rename of type COFF, which seems to be just a wrapper for Int64, and in fact when I get it in GHCI, I can see the number that the IO FileOffset corresponds to. However, I can't add it to anything else, print it out (except for through the interpreter), or even convert it to another type. It seems to be immune to show.
How can I actually use this type? I'm new to Haskell so I'm sure I'm missing something fundamental about types and possibly the documentation.
As discussed in numerous other questions, like this, there is never anything you can do with an IO a value as such – except bind it in another IO computation, which eventually has to be invoked from either main or ghci. And this is not some stupid arbitrary restriction of Haskell, but reflects the fact that something like a file offset can impossibly be known without the program first going “out into the world”, doing the file operation, coming back with the result. In impure languages, this kind of thing just suddenly happens when you try to evaluate an IO “function”, but only because half a century of imperative programming has done it this way doesn't mean it's a good idea. In fact it's a cause for quite a lot of bugs in non-purely functional languages, but more importantly it makes it way harder to understand what some library function will actually do – in Haskell you only need to look at the signature, and when there's no IO in it, you can be utterly sure1 it won't do any!
Question remains: how do you actually get any “real” work done? Well, it's pretty clever. For beginners, it's probably helpful to keep to this guideline:
An IO action always needs to be evaluated in a do block.
To retrieve results from such actions, use the val <- action syntax. This can stand anywhere in a do block except at the end. It is equivalent to what procedural languages write as var val = action() or similar. If action had a type IO T, then val will have simply type T!
The value obtained this way can be used anywhere in the same do block below the line you've obtained it from. Quite like in procedural languages.
So if your action was, say,
findOffsetOfFirstChar :: Handle -> Char -> IO FileOffset
you can use it like this:
printOffsetOfQ :: Handle -> IO ()
printOffsetOfQ h = do
offset <- findOffsetOfFirstChar h 'Q'
print offset
Later on you'll learn that many of these dos arent really necessary, but for the time being it's probably easiest to use them everywhere where there's IO going on.
1Some people will now object that there is a thing called unsafePerformIO which allows you to do IO without the signature telling so, but apart from being, well, unsafe, this does not actually belong to the Haskell language but to its foreign function interface.
I have scoured the internet for an actual explanation of what this keyword does. Every Haskell tutorial that I have looked at just starts using it randomly and never explains what it does (and I've looked at many).
Here's a basic piece of code from Real World Haskell that uses Just. I understand what the code does, but I don't understand what the purpose or function of Just is.
lend amount balance = let reserve = 100
newBalance = balance - amount
in if balance < reserve
then Nothing
else Just newBalance
From what I have observed, it is related to Maybe typing, but that's pretty much all I have managed to learn.
A good explanation of what Just means would be very much appreciated.
It's actually just a normal data constructor that happens to be defined in the Prelude, which is the standard library that is imported automatically into every module.
What Maybe is, Structurally
The definition looks something like this:
data Maybe a = Just a
| Nothing
That declaration defines a type, Maybe a, which is parameterized by a type variable a, which just means that you can use it with any type in place of a.
Constructing and Destructing
The type has two constructors, Just a and Nothing. When a type has multiple constructors, it means that a value of the type must have been constructed with just one of the possible constructors. For this type, a value was either constructed via Just or Nothing, there are no other (non-error) possibilities.
Since Nothing has no parameter type, when it's used as a constructor it names a constant value that is a member of type Maybe a for all types a. But the Just constructor does have a type parameter, which means that when used as a constructor it acts like a function from type a to Maybe a, i.e. it has the type a -> Maybe a
So, the constructors of a type build a value of that type; the other side of things is when you would like to use that value, and that is where pattern matching comes in to play. Unlike functions, constructors can be used in pattern binding expressions, and this is the way in which you can do case analysis of values that belong to types with more than one constructor.
In order to use a Maybe a value in a pattern match, you need to provide a pattern for each constructor, like so:
case maybeVal of
Nothing -> "There is nothing!"
Just val -> "There is a value, and it is " ++ (show val)
In that case expression, the first pattern would match if the value was Nothing, and the second would match if the value was constructed with Just. If the second one matches, it also binds the name val to the parameter that was passed to the Just constructor when the value you're matching against was constructed.
What Maybe Means
Maybe you were already familiar with how this worked; there's not really any magic to Maybe values, it's just a normal Haskell Algebraic Data Type (ADT). But it's used quite a bit because it effectively "lifts" or extends a type, such as Integer from your example, into a new context in which it has an extra value (Nothing) that represents a lack of value! The type system then requires that you check for that extra value before it will let you get at the Integer that might be there. This prevents a remarkable number of bugs.
Many languages today handle this sort of "no-value" value via NULL references. Tony Hoare, an eminent computer scientist (he invented Quicksort and is a Turing Award winner), owns up to this as his "billion dollar mistake". The Maybe type is not the only way to fix this, but it has proven to be an effective way to do it.
Maybe as a Functor
The idea of transforming one type to another one such that operations on the old type can also be transformed to work on the new type is the concept behind the Haskell type class called Functor, which Maybe a has a useful instance of.
Functor provides a method called fmap, which maps functions that range over values from the base type (such as Integer) to functions that range over values from the lifted type (such as Maybe Integer). A function transformed with fmap to work on a Maybe value works like this:
case maybeVal of
Nothing -> Nothing -- there is nothing, so just return Nothing
Just val -> Just (f val) -- there is a value, so apply the function to it
So if you have a Maybe Integer value m_x and an Int -> Int function f, you can do fmap f m_x to apply the function f directly to the Maybe Integer without worrying if it's actually got a value or not. In fact, you could apply a whole chain of lifted Integer -> Integer functions to Maybe Integer values and only have to worry about explicitly checking for Nothing once when you're finished.
Maybe as a Monad
I'm not sure how familiar you are with the concept of a Monad yet, but you have at least used IO a before, and the type signature IO a looks remarkably similar to Maybe a. Although IO is special in that it doesn't expose its constructors to you and can thus only be "run" by the Haskell runtime system, it's still also a Functor in addition to being a Monad. In fact, there's an important sense in which a Monad is just a special kind of Functor with some extra features, but this isn't the place to get into that.
Anyway, Monads like IO map types to new types that represent "computations that result in values" and you can lift functions into Monad types via a very fmap-like function called liftM that turns a regular function into a "computation that results in the value obtained by evaluating the function."
You have probably guessed (if you have read this far) that Maybe is also a Monad. It represents "computations that could fail to return a value". Just like with the fmap example, this lets you do a whole bunch of computations without having to explicitly check for errors after each step. And in fact, the way the Monad instance is constructed, a computation on Maybe values stops as soon as a Nothing is encountered, so it's kind of like an immediate abort or a valueless return in the middle of a computation.
You Could Have Written Maybe
Like I said before, there is nothing inherent to the Maybe type that is baked into the language syntax or runtime system. If Haskell didn't provide it by default, you could provide all of its functionality yourself! In fact, you could write it again yourself anyway, with different names, and get the same functionality.
Hopefully you understand the Maybe type and its constructors now, but if there is still anything unclear, let me know!
Most of the current answers are highly technical explanations of how Just and friends work; I thought I might try my hand at explaining what it's for.
A lot of languages have a value like null that can be used instead of a real value, at least for some types. This has made a lot of people very angry and been widely regarded as a bad move. Still, it's sometimes useful to have a value like null to indicate the absence of a thing.
Haskell solves this problem by making you explicitly mark places where you can have a Nothing (its version of a null). Basically, if your function would normally return the type Foo, it instead should return the type Maybe Foo. If you want to indicate that there's no value, return Nothing. If you want to return a value bar, you should instead return Just bar.
So basically, if you can't have Nothing, you don't need Just. If you can have Nothing, you do need Just.
There's nothing magical about Maybe; it's built on the Haskell type system. That means you can use all the usual Haskell pattern matching tricks with it.
Given a type t, a value of Just t is an existing value of type t, where Nothing represents a failure to reach a value, or a case where having a value would be meaningless.
In your example, having a negative balance doesn't make sense, and so if such a thing would occur, it is replaced by Nothing.
For another example, this could be used in division, defining a division function that takes a and b, and returns Just a/b if b is nonzero, and Nothing otherwise. It's often used like this, as a convenient alternative to exceptions, or like your earlier example, to replace values that don't make sense.
A total function a->b can find a value of type b for every possible value of type a.
In Haskell not all functions are total. In this particular case function lend is not total - it is not defined for case when balance is less than reserve (although, to my taste it would make more sense to not permit newBalance to be less than reserve - as is, you can borrow 101 from a balance of 100).
Other designs that deal with non-total functions:
throw exceptions upon checking input value does not fit the range
return a special value (primitive type): favourite choice is a negative value for integer functions that are meant to return Natural numbers (for example, String.indexOf - when a substring is not found, the returned index is commonly designed to be negative)
return a special value (pointer): NULL or some such
silently return without doing anything: for example, lend could be written to return old balance, if the condition for lending is not met
return a special value: Nothing (or Left wrapping some error description object)
These are necessary design limitations in languages that cannot enforce totality of functions (for example, Agda can, but that leads to other complications, like becoming turing-incomplete).
The problem with returning a special value or throwing exceptions is that it is easy for the caller to omit handling of such a possibility by mistake.
The problem with silently discarding a failure is also obvious - you are limiting what the caller can do with the function. For example, if lend returned old balance, the caller has no way of knowing if balance has changed. It may or may not be a problem, depending on the intended purpose.
Haskell's solution forces the caller of a partial function to deal with the type like Maybe a, or Either error a because of the function's return type.
This way lend as it is defined, is a function that doesn't always compute new balance - for some circumstances new balance is not defined. We signal this circumstance to the caller by either returning the special value Nothing, or by wrapping the new balance in Just. The caller now has freedom to choose: either handle the failure to lend in a special way, or ignore and use old balance - for example, maybe oldBalance id $ lend amount oldBalance.
Function if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a) must have the same type of ifTrue and ifFalse.
So, when we write then Nothing, we must use Maybe a type in else f
if balance < reserve
then (Nothing :: Maybe nb) -- same type
else (Just newBalance :: Maybe nb) -- same type