What does the "Just" syntax mean in Haskell?

I surfed the internet for an actual explanation of what this keyword does. Every Haskell tutorial I reviewed just starts using it randomly and never explains what it does (and I looked at many).

Here is the basic code snippet from Real World Haskell that Just uses. I understand what the code does, but I don't understand what the purpose or function of Just .

 lend amount balance = let reserve = 100 newBalance = balance - amount in if balance < reserve then Nothing else Just newBalance 

From what I have observed, this is due to the typing of Maybe , but that is almost all that I managed to find out.

A good explanation of what Just means would be greatly appreciated.

+104
syntax haskell
Sep 15 '13 at 2:08 on
source share
5 answers

This is actually a regular data constructor, which is defined in Prelude, which is a standard library that is automatically imported into each module.

What could be structurally

The definition looks something like this:

 data Maybe a = Just a | Nothing 

This declaration defines a type, Maybe a , which is parameterized by a variable of type a , which means that you can use it with any type instead of a .

Construction and Destruction

The type has two constructors, Just a and Nothing . When a type has several constructors, this means that the type value must be created with only one of the possible constructors. For this type, the value was created using Just or Nothing , there are no other (without errors) possibilities.

Since Nothing does not have a parameter type, when 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 has a type parameter, which means that when used as a constructor, it acts as a function from a type to Maybe a , that is, it has type a → Maybe a

So, type constructors create a value for that type; the other side is when you want to use this value, and it is here that pattern matching comes into play. Unlike functions, constructors can be used in template binding expressions, and in this way you can parse values ​​belonging to types with more than one constructor.

To use the Maybe a value in comparison with the sample, you must provide a template for each constructor, for example, like this:

 case maybeVal of Nothing -> "There is nothing!" Just val -> "There is a value, and it is " ++ (show val) 

In this case, the expression will match the first pattern if the value is Nothing , and the second will match if the value was created using Just . If the second one matches, it also associates the name val with the parameter that was passed to the Just constructor when the value you were comparing with was created.

What could be mean

Maybe you were already familiar with how this works; in fact, there is no magic for Maybe values, it's just a normal Haskell algebraic data type (ADT). But it was used quite a bit because it effectively “lifts” or extends a type, such as the Integer from your example, into a new context in which it has an extra value ( Nothing ) that represents the absence of a value! The type system then requires you to check this extra value before it allows you to get an Integer that might be there. This prevents a significant amount of errors.

Many languages ​​today process this type of value "no value" through NULL references. Tony Hoar, an outstanding computer scientist (he invented Quicksort and is a winner of the Turing Prize), admits this as his "billion-dollar mistake . " Maybe is not the only way to fix this, but it has proven to be an effective way to do this.

Maybe like a functor

The idea of ​​converting one type to another so that operations on the old type can also be converted to work with the new type is the concept of a Haskell type class called Functor , which, Maybe a has a useful instance.

Functor provides a method called fmap that maps functions that range in value from a base type (e.g., Integer ) to functions that range in value from a raised type (e.g. Maybe Integer ). The function converted with fmap to work with the Maybe value works as follows:

 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 

Therefore, if you have a Maybe Integer m_x value and an Int → Int f function, you can execute fmap f m_x to apply the f function directly to Maybe Integer without worrying about whether it got the value or not. In fact, you can apply a whole chain of raised Integer → Integer functions to Maybe Integer values ​​and you only have to worry about explicitly checking Nothing once when you are done.

Maybe like a monad

I'm not sure how familiar you are with the Monad concept, but at least you've already used IO a , and a signature like IO a looks amazingly similar to Maybe a . Although IO is special in that IO that it does not provide you with its constructors and thus can only be "started" by the Haskell runtime system, it is still also a Functor in addition to Monad . In fact, there is an important point in which Monad is just a special kind of Functor with some additional features, but it’s not a place to figure it out.

In any case, monads like IO map types to new types that represent "calculations that yield values", and you can raise functions into Monad types using the fmap -like function itself, called liftM which turns a regular function into "calculation, which leads to the value obtained by evaluating the function. "

You probably guessed (if you read this far) that Maybe it is also Monad . It represents "calculations that may not return a value." As in the fmap example, this allows you to do a whole bunch of calculations without having to explicitly check for errors after each step. And in fact, like a Monad instance of Monad , the calculation of Maybe values ​​ends as soon as Nothing is encountered, so it's kind of an immediate interrupt or a useless return in the middle of the calculation.

Could you write maybe

As I said, there is nothing inherent to the Maybe type that is built into the language syntax or runtime system. If Haskell did not provide it by default, you can provide all its functions yourself! In fact, you could still write it yourself, with different names, and get the same functionality.

I hope you understand the Maybe type and its constructors now, but if something is unclear, let me know!

+180
Sep 15 '13 at 5:37 on
source share

Most of the current answers are very technical explanations of how Just and friends work; I thought that I could try to explain why this is necessary.

Many languages ​​have a null value that can be used in place of the real value, at least for some types. This caused a lot of people who were very angry and were widely regarded as a bad move. However, it is sometimes useful to have a value like null to indicate the absence of a thing.

Haskell solves this problem by explicitly specifying places where you can have Nothing (its version is null ). Basically, if your function usually returns a type of Foo , it should instead return a type of Maybe Foo . If you want to indicate that there is no value, return Nothing . If you want to return the value of 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 need Just .

Nothing magical in Maybe ; it is built on a system like Haskell. This means that you can use all the usual Haskell pattern matching the tricks with it.

+33
Mar 12 '14 at 8:14
source share

Given type t , the value of Just t is an existing value of type t , where Nothing represents the inability to reach the value or the case where the value makes sense.

In your example, having a negative balance does not make sense, so if this happens, it is replaced by Nothing .

In another example, this can be used in division, which defines a division function that takes a and b , and returns Just a/b if b nonzero and Nothing otherwise. It is often used like this, as a convenient alternative to exceptions, or as your previous example, to replace values ​​that don't make sense.

+13
Sep 15 '13 at 2:14
source share

The generic function a-> b can find a value of type b for any possible value of type a.

In Haskell, not all functions are shared. In this particular case, the lend function lend not complete - it is not defined for the case when the balance is less than a reserve (although, for my taste, it would be more reasonable not to allow newBalance to be less than a reserve - as is, you can take 101 out of balance one hundred).

Other projects that deal with inaccurate functions:

  • throw exception when checking input value does not match range
  • returns a special value (primitive type): the selected choice is a negative value for integer functions that are designed to return natural numbers (for example, String.indexOf - when a substring is not found, the returned index is usually intended to be negative)
  • returns a special value (pointer): NULL or some such
  • silently return without any action: for example, lend can be recorded to return the old balance if the condition for lending is not fulfilled
  • return a special value: Nothing (or Left wrapping some error description object)

These are necessary design restrictions in languages ​​that cannot ensure the integrity of functions (for example, Agda can, but it leads to other complications, for example, to the fact that they become unclean).

The problem with returning a special value or throwing exceptions is that it is easy for the caller to rule out the possibility of processing such a possibility.

The problem with silent rejection is also obvious - you limit what the caller can do with the function. For example, if lend returns the old balance, the caller is not able to find out if the balance has changed. This may or may not be a problem, depending on the intended purpose.

The Haskell solution forces the calling function of the partial function to process the type type Maybe a or Either error a because of the return type of the function.

This lend method, as defined, is a function that does not always calculate a new balance — under certain circumstances, a new balance is not determined. We signal this circumstance to the caller, either by returning the special value Nothing, or by completing a new balance in Just. Now the caller has freedom of choice: either handle the refusal to lend in a special way, or ignore and use the old balance - for example, maybe oldBalance id $ lend amount oldBalance .

+2
Sep 16 '13 at 12:39 on
source share

The function if (cond :: Bool) then (ifTrue :: a) else (ifFalse :: a) must be of the same type ifTrue and ifFalse .

So, when we write then Nothing , we should use the Maybe a type in else f

 if balance < reserve then (Nothing :: Maybe nb) -- same type else (Just newBalance :: Maybe nb) -- same type 
-2
Sep 15 '13 at 16:44
source share



All Articles