-
Notifications
You must be signed in to change notification settings - Fork 145
LYAH adaptions for Frege
This document is for those who want to learn Frege by going through Learn you a Haskell while doing the exercises in Frege. It will help you to deal with the small differences between both languages and the tools used.
Note: there is also Learn you a Frege
Whenever there is an example, explanation or exercise that doesn’t work the same way in Frege as described in LYAH, you will find some remarks here. Chapter and section titles will be included for better orientation, as well as a short quote to establish the context for our comments. This is so that if you stumble upon some difficulty, say, in Chapter 2 “Starting out” and Section “Texas ranges”, you would easily find whether there is something here under those headings.
Nobody is perfect, and so are the authors of this wiki page. If you miss something, find something inaccurate or not detailed enough, please let us know by opening an issue in the Frege project.
This wiki page is done in admiration of the LYAH author, Miran Lipovača, who licensed his work with the Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. This allows us to build upon his material, provided that the same license applies to the derived work, which we herewith declare to be the case.
The objective in the development of Frege was that it should be a practical language on the Java platform while being as close to Haskell as possible. This means in particular, that interoperability with Java and other JVM languages should be fluent.
Adaption to the JVM platform is the key reason for differences concerning basic types (Bool, String), low-level system functions, the module system and the foreign function interface, which is specialized for the JVM.
But now let’s jump right into LYAH!
Instead of ghci
we use a similar tool which is known as Frege REPL.
The only prerequisite for this is a Java7 (or higher) installation.
You can check your java version like this:
$ java -version
If you don't have java installed, download the latest Java 8 JDK from Oracle. Or if you prefer, you can install the openjdk-8-jdk
for your OS. For example, here is what would work on my box:
$ sudo apt-get install openjdk-8-jdk
Here is how you install and run the Frege REPL:
-
Download the latest distribution from the REPL download page.
-
Unzip the archive. This is done best in some directory dedicated solely to the REPL. After unzipping, it should look like this (apart from differences in the version numbers):
$ cd; mkdir repl
$ unzip ~/Downloads/frege-repl-1.1.1-SNAPSHOT.zip
Archive: ~/Downloads/frege-repl-1.1.1-SNAPSHOT.zip
[ ... snipped ... ]
$ tree
.
├── frege-repl-1.1.1-SNAPSHOT.jar
└── lib
├── ecj-4.2.2.jar
├── frege-3.22.367-g2737683.jar
├── frege-interpreter-core-1.0.3-SNAPSHOT.jar
├── frege-interpreter-java-support-1.0.3-SNAPSHOT.jar
├── frege-repl-nativedeps-1.1.1-SNAPSHOT.jar
└── jline-2.12.jar
The command to run this no matter what your current working directory is, would be:
$ java -Xss4m -Xmx1g -jar ~/repl/frege-repl-1.1.1-SNAPSHOT.jar -terminal jline
It is recommended to make this a shell alias, like
$ alias frepl='java -Xss4m -Xmx1g -jar ~/repl/frege-repl-1.1.1-SNAPSHOT.jar -terminal jline'
This way you just type frepl
and that's it.
The -terminal jline
adds command history and command editing capabilities. This may not work on Windows. OTOH, the Windows cmd.exe
can be configured to do the same, so you can remove the -terminal jline
.
The Fast Way: The Online REPL
An even faster way to dive in may be the online version of the REPL. It's just one click away, and will be fine for the first exercises!
Congratulations, you're in GHCI!
Not surprisingly, the greeting of the Frege REPL is a bit different:
$ frepl # you did make that alias, didn't you?
Welcome to Frege 3.22.367-g2737683 (Oracle Corporation OpenJDK Server VM, 1.8.0_40-internal)
frege> 2+2
4
The prompt is frege>
for the time being, and cannot be changed. To get help, enter :help
.
Note that after starting the program, it will need a few seconds to answer even simple questions as 2+2
. The reason is that the JVM needs to warm up a bit. Later on, the delays will get shorter.
If we want to have a negative number, it's always best to surround it with parentheses.
Indeed. Though, Frege doesn't have problems with
frege> 5 * -3
-15
frege> -5 * 3
-15
frege> -5 * -3
15
but this will only work in infix expressions. So, when in doubt, surround negative numbers with parentheses.
One more thing: you must set apart operators by at least one space character, lest the following happens:
frege> 5*-3
[ERROR: 4]: E <console>.fr:4: can't resolve `*-`, did you mean `*` perhaps?
Boolean algebra is also pretty straightforward.
frege> True || False
[ERROR: 4]: E <console>.fr:4: can't resolve `True`, did you mean `Byte` perhaps?
[ERROR: 4]: E <console>.fr:4: can't resolve `False`, did you mean `File` perhaps?
In Frege, we must always write true
and false
instead of True
and False
. This is because we use the primitive boolean
type of the JVM, which appears as an abstract type Bool
in Frege. Consequently, we also have the boolean literals true
and false
.
Confusing? Don't panic! A deeper explanation follows later, when we learn about algebraic data types. Please just mark this for now: write true
and false
, not True
and False
.
frege> true || false
true
5 /= 5
We can as well write !=
in Frege, but /=
still works for Haskell compatibility. (To my knowledge, no other language uses /=
for not equal.)
"hello" == "hello"
Remark for Java programmers: this actually compares the values, not the references, as Java would do. That is, this is equivalent to the Java expression:
"hello".equals("hello")
No instance for (Num [Char])
Frege will instead tell you:
frege> 5 + "llama"
[ERROR: 4]: E <console>.fr:4: String is not an instance of Num
In general, the error message will always come out different. No wonder, we have two different compilers, written by different people at different times.
Once inside GHCI, do
:l baby
.
In Frege, you must give the file name with extension. So, if you saved as baby.hs
, type :l baby.hs
. However, Frege source code files should have file extension .fr
.
Later on, when you load again the same file, you must give the :reset
command before to avoid strange warnings, or even errors.
If you're using the Online REPL, you won't be able to access files on your computer. However, you can load scripts from a URL! If you have a GitHub account, you can write gists, and load them into the Online REPL. Try this
:load https://gist.githubusercontent.com/Ingo60/0687891988534f6875bf/raw/f44982c34bc9e0508b630208e5132ad7b91b9273/Welcome.fr
doubleSmallNumber n = if n < 100 then n else n*2
When you try this function in Frege with floating point numbers, you'll get an error message:
frege> doubleSmallNumber 76.7e-99
[ERROR: 8]: E <console>.fr:8: Int is not an instance of Real
This is because in Frege, this function will only work for integers. If you want it to be polymorphic like in Haskell, you need to add this to your script:
doubleSmallNumber :: Num a => a -> a
This is a type annotation, and it is usually written in the line before the function definition. If you don't want to do that, this is fine, as long as you restrict yourself to doubling small integers.
Note that the REPL tells you the type of a function, when you define it. When you loaded the script (without type annotations), you may have noticed this output:
frege> :load double.fr
function doubleMe :: Num a => a -> a
function doubleSmallNumber :: Int -> Int
This tells you that you can use doubleMe
on any number, but doubleSmallNumber
only on Int
(which is the name for the primitive Java type int
).
That apostrophe doesn't have any special meaning in Haskell's syntax. It's a valid character to use in a function name.
frege> conanO'Brien = "It's a-me, Conan O'Brien!"
[ERROR: 9]: E <console>.fr:9: can't resolve `Brien`, did you mean `Bind` perhaps?
In Frege, the apostrophe is only allowed at the end of a name. (This is how it is used in Haskell code most of the time.)
strings (which are lists)
Not so in Frege. We'll come back to this in a minute.
We can use the let keyword to define a name right in GHCI.
Whereas in the Frege REPL, we simply write the definition without the let, just like we would in a script.
Speaking of characters, strings are just lists of characters. "hello" is just syntactic sugar for ['h','e','l','l','o']. Because strings are lists, we can use list functions on them, which is really handy.
This is one of the more fundamental differences between Frege and Haskell. The reason is that Java has its own, quite sophisticated and versatile string type, (i.e. java.lang.String
).
Despite of this, certain functions that work only on lists in Haskell do work on strings in Frege. In addition, there are functions that convert strings to lists and back, should this be needed. We'll introduce them here as the need arises.
If you are curious how the String type looks in Frege, you can use the REPL to tell you. Type :help String
and browse from there. You could also directly go to the String documentation.
'A':" SMALL CAT"
frege> 'A':" SMALL CAT"
[ERROR: 11]: E <console>.fr:11: type error in expression " SMALL CAT"
type is String
used as [Char]
From the first part of the expression 'A':
the Frege type checker infers that you want to put a character in front of a list, and since lists are homogenous, it must be a character list. But on the right hand side of the :
, it found a string (the type is called String
), and strings are not list of characters (denoted [Char]
) in Frege, as explained above.
There are two ways to fix this:
frege> "A" ++ " SMALL CAT"
A SMALL CAT
since ++
is one of the operators that work on both strings and lists. The result is a string.
Alternatively, we can turn the string into a list of charcters, and then put the 'A'
in front:
frege> 'A' : toList " SMALL CAT"
['A',' ','S','M','A','L','L',' ','C','A','T']
Here, the result is a list of characters.
"Steve Buscemi" !! 6
frege> "Steve Buscemi" !! 6
[ERROR: 12]: E <console>.fr:12: type error in expression "Steve Buscemi"
type is String
used as [t17088]
This means so much as "String
is not a list at all.", which is the case in Frege. You can convert the string using toList
as before. But if you're a Java programmer, you'll be glad to hear that the charAt
method of java.lang.String
is supported in Frege (among many other string methods). That is, you can write:
frege> "Steve Buscemi".charAt 6
'B'
They can also contain lists that contain lists that contain lists
In the following example, remember to omit the let or else it'll be a syntax error:
frege> let b = [[1,2,3,4],[5,3,3,3],[1,2,2,3,4],[1,2,3]]
[ERROR: 12]: E <console>.fr:12: expected token "in", found '}'
If we think of a list as a monster, here's what's what.
Of the functions that are introduced in the rest of the section, the following do work on strings and lists alike: head
, tail
, take
, length
, null
, drop
.
['a'..'z']
This will result in
['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t'
for the reasons explained before: A list of characters is nothing special in Frege, and it is not a string. Hence, it will be shown as what it is: a list of characters.
You may ask why there is
,'u','v','w','x','y','z']
missing in the output? It is because the Frege REPL prints only the first 80 characters of a list representation. This pays off if you enter an expression that results in an infinite list - trying to print that would result in a memory overflow and the death of the interpreter.
Of course, we can pack a list of characters into a much more conscise string, if we want. Try
packed ['a' .. 'z']
Here is another difference between the Frege REPL and ghci
: When the REPL sees that the output is just a string, it just prints it, whereas ghci
shows it. show
is a function that is supposed to create a string that resembles a valid haskell expression which could be used to recreate the value. Sounds complicated? Try the following to see the difference:
"abc\ndef"
versus
show "abc\ndef"
Watch out when using floating point numbers in ranges!
Floating point numbers are not supported in ranges in Frege. You can create sequences with the iterate
function, for example the sequence from LYAH:
frege> iterate (+0.2) 0.1
[0.1,0.30000000000000004,0.5,0.7,0.8999999999999999,1.0999999999999999,1.2999999
There is a difference in the second term 0.30000000000000004
that comes about because Haskell takes it from the range expression, while the iterate
function computes it. Unfortunately, 0.1+0.2
is 0.30000000000000004
on every computer that uses 64-bit IEEE conforming floating point values. Try it out in any language (but make sure the output is not rounded).
To get the exact same sequence as from the Haskell range, try
frege> iterate (+(0.3-0.1)) 0.1
[0.1,0.3,0.5,0.7,0.8999999999999999,1.0999999999999999,1.2999999999999998,1.4999
cycle
You should not be surprised anymore to get an error message when you do this:
frege> take 12 (cycle "LOL ")
[ERROR: 4]: E <console>.fr:4: type error in expression "LOL "
type is String
used as [t17342]
This reminds you again that String
is not a list of anything [t]
.
If you got 5 minutes, it should be instructive to try out the following two adaptions:
frege> packed (take 12 (cycle "LOL ".toList))
frege> take 12 (packed (cycle "LOL ".toList))
Bonus points if you - without trying any one of the two - can avoid the one that gives
java.lang.OutOfMemoryError: GC overhead limit exceeded
How about a list comprehension that combines a list of adjectives and a list of nouns
Remember that we must not write let
in front of the definitions. Also, you can copy and paste the (single-line) function definitions that are given in the examples right into the REPL. There is no need to write them in a file and load it.
removeNonUppercase
Despite a string is not a list, we can use strings in list comprehensions on the right of an arrow! This is because list comprehensions in Frege work on anything that can be viewed as a list. It doesn't have to be a list.
Note, however, that the result is printed as a list of characters, as before.
No comments.
From here on, we'll give all the functions that we make type declarations.
There are two ways to do this in the Frege REPL. The first is to use paste mode
frege> :{
frege> addThree :: Int -> Int -> Int -> Int
frege> addThree x y z = x + y + z
frege> :}
This mode is so called because you can paste from the clipboard once you entered the opening brace. When you're done pasting and typing, enter the closing brace.
The other alternative is to enter the function definition first, and only then the type declaration:
frege> addThree x y z = x + y + z
function addThree :: Num a => a -> a -> a -> a
frege> addThree :: Int -> Int -> Int -> Int
This could be a bit confusing, however, because as soon as you enter the function definition, the interpreter will tell you what it thinks the type should be.
The
[Char]
type is synonymous withString
As explained before, this is not the case in Frege. You can give the removeNonUppercase
function the type
removeNonUppercase :: String -> [Char]
but not:
removeNonUppercase :: String -> String
because the list comprehension always produces lists.
circumference r = 2 * pi * r
It is possible in earlier Frege versions that you get the error:
[ERROR: 14]: E <console>.fr:14: can't resolve `pi`, did you mean `$!` perhaps?
To fix this, enter
frege> import Prelude.Math
and re-enter the definition.
:t head
Unfortunately, head
makes for a bad example in Frege, because it is one of those list functions that also work on String
s, and therefore have a type that is a bit more general than in Haskell. So for the moment, don't get confused by the output of the Frege REPL, use another function like
:t reverse
and try to understand the explanation in LYAH.
The type declaration of
head
states that it takes a list of any type and returns one element of that type.
And the type declaration of reverse
states that it takes a list of any type and returns a list of that (same) type.
Read
The typeclass Read
is currently not implemented in Frege.
Bounded
members have an upper and a lower bound.
For the examples to work, please enclose them in parentheses:
(maxBound :: Char)
otherwise the Frege REPL thinks that this is a type definition, and complains that it already has one.
It appears that whole numbers are also polymorphic constants.
Whereas in Frege, the example just tells you it is Int
. So, Frege numeric literals are not polymorphic, unless you explicitly request it with a type annotation. Remember the function doubleSmallNumber
? It used to work on Int
only, because of the Int
constants in the code. However, we could make it work on any number type by giving the type signature
doubleSmallNumber :: Num a => a -> a
This is also the reason why the examples in the subsequent box actually do work in Frege. If you give a type annotation, the compiler will do its best to make you happy, hence in
20 :: Double
it simply interprets the 20
as floating point quantity with double precision. Even the following is possible:
(5+3) :: Double
From the definition of (+)
, the compiler knows that if the sum is of type Double
, then the summands must both be of type Double
, and since both summands are literals, it is as if you had written
(5::Double) + (3::Double)
Note that this no type-casting, as we know it from other languages! This works only for literals. You can't actually change the type something has. For a proof, try this:
notso :: Int -> Double; notso i = i :: Double
Here, the compiler knows from the type declaration that i
will have type Int
. And you can't change this with a (conflicting) type definition. (There exist, however, functions that convert an Int
value to a Double
value, but that does not change the type of the Int
value.)
In order to try out the multi line function definitions that follow in this chapter, remember the "paste mode" of the REPL if you don't want to edit and load scripts from files.
Pattern matching can also fail.
The Frege compiler and REPL will always warn you when there is the possibility that a set of patterns doesn't match all possible values, and will try to come up with an example that would not match:
frege> :{
frege> charName 'a' = "Albert"
frege> charName 'b' = "Broseph"
frege> charName 'c' = "Cecil"
frege> :}
function charName :: Char -> String
[WARNING: 20]: W <console>.fr:20: pattern match is not exhaustive, consider adding a case for '!'
There's also a thing called as patterns.
Too bad the following example doesn't work at all, because Strings can't be pattern matched against list patterns. Only lists can be matched against list patterns.
But please try the following as substitute, there is an important lesson to learn:
capital' :: [Char] -> String
capital' [] = "empty string"
capital' all@(x:xs) = "The first letter of " ++ packed all ++ " is " ++ show x
This results in an error (although the message is not really instructive, sorry ...). The reason for the error is that in Frege definitions, any pattern which resembles an argument to a function or constructor which is more complex than a variable name, a literal or a record construction must be written in parentheses. This way, the syntax left of the equal sign is exactly the same as in an expressions right from an equal sign! This is the only thing to remember.
From the previous chapter, you should know enough about Haskell expression syntax that you can see that capital'
can never be called like this:
capital' v @ (x:xs)
because this is syntactically the same as, for example
sin v / (x-y)
which might be a valid expression, but not an application of sin
, but of /
. For the same reason, the example before is not an application of capital'
, but of @
(whatever @
is, but for sure some operator). But to find out what exactly will be defined, the compiler needs to find the symbol that is syntactically applied. This symbol is the one that is defined, and the rest are patterns. (We will later understand it better when we learn about pattern bindings.)
For now, we simply put the v@
inside the parentheses:
capital' (v@x:xs) = ....
Now it is clear that capital'
is a function of 1 argument! We read this like "capital'
applied to v
(which, in turn, is a list with head x
and tail xs
) is defined as ..."
The precedence of the (pseudo) operator @
is choosen so that it is lower than the precedence of any other operator that may occur in a pattern, so extra parentheses around x:xs
are not needed here.
bmiTell :: (RealFloat a) => a -> String
The numeric classes are a bit different, apart form Num
and Integral
. You can use just Real
instead of RealFloat
, or skip the type annotation to get a function Double -> String
.
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]
Here, the Frege Syntax is slightly different. Use one of the following:
calcBmis xs = [bmi | (w, h) <- xs, bmi = w / h ^ 2] -- just leave out let!
calcBmis xs = [bmi | (w, h) <- xs, let {bmi = w / h ^ 2}] -- enclose let defs in {}
:{
calcBmis xs = [bmi | (w, h) <- xs,
let bmi = w / h ^ 2
] -- braces are inserted automatically
:}
(no comments)
Num
is not a subclass ofOrd
It is in Frege, which makes the Ord
constraint obsolete when you already have a Num
constraint on the same type variable. However, the Frege REPL doesn't care if you enter superfluous constraints, so the examples should work.
compareWithHundred :: (Num a, Ord a) => a -> Ordering
In these examples, it is imporrtant to give the type declarations, otherwise Frege would infer a much simpler type because of the Int
literal. Please try it out!
Like normal functions, lambdas can take any number of parameters
Small syntactic difference: in Frege, you need to write a backslash before every argument. That is, instead of
\a b -> (a * 30 + 3) / b
you write
\a \b -> (a * 30 + 3) / b
or, if you want:
\a -> \b -> (a * 30 + 3) / b
This latter notation nicely corresponds with the function type syntax.
foldl
Because foldl
is lazy in the accumulator, its use is discouraged in Frege. An alternative function that is even faster is fold
(Haskell knows the same as foldl'
which is an alias for fold
in Frege.)
To see the difference, try the following in the interpreter:
foldl (+) 0 [1..100000] :: Long
fold (+) 0 [1..100000] :: Long
sqrtSums
This is a good time to recall that you need to import Prelude.Math
to get functions like sqrt
. Moreover, sqrt
works on floating point numbers only. Finally, sequences of floating point numbers cannot be produced with [1..]
. I suggest instead:
sqrtSums = length (takeWhile (<1000) (scanl1 (+) (map sqrt (iterate (+1) 1.0)))) + 1
Make sure to embed the function composition operator within spaces or parentheses on both sides! The following will be ok:
(.)
(. f)
(g .)
f . g
while you'll get strange messages for
f. g
f.g
f .g
This is because the dot is also used for other purposes like record selection. You can use (•)
as an alternative, that is
f•g
will be fine.
One script can, of course, import several modules.
You can import modules in a Frege REPL session as well.
ghci> :m + Data.List
In Frege, the syntax for imports is the same in scripts and the REPL.
import qualified Data.Map
The syntax in Frege is
import Data.TreeMap ()
In fact, it is the same synatx as with the selective import before, just that we don't select anything.
Another important difference: In Frege, an import creates a namespace for the imported module. The name of the namespace is by default the last component of the module name, here TreeMap
. No matter what syntax you used for import, you can access all names exported by the module by prepending the namespace name and a dot. The dot must follow the namespace name without a space!
import qualified Data.Map as M
We can do the same in Frege with
import Data.TreeMap as M()
In this case, the namespace for the module is M
. This is most useful when the author of the module used a very long name, or if we want two modules whose last components have the same name by coincidence:
import my.extraordinary.Map
import my.hash.Map
In this case, we must rename one of the namepsaces, as there may not be more than one module per namespace.
Use this handy reference to see which modules are in the standard library.
For Frege, use the documentation site.
import the
on
function fromData.Function
We don't have the Data.Function
module, but the on
function is contained in the Prelude, so no need to import it.
This section is again full of applications of list functions to strings. Menawhile you must think that Frege is totally un-practical when one needs to explicitly convert strings to lists and back all the time!
Arguably, code snippets that work on strings make for good tutorial examples.
But believe it or not, it is not that common in everyday programming.
To the contrary: Advanced Haskell users complain about the enormous storage requirements for character lists and the comparatively slow speed in string processing based on lists. This is why they prefer to use more compact string types like Text.ByteString
, that have at least one thing in common with Frege strings: they are not lists.
Here is how one of the examples could be translated to Frege:
-- Haskell
groupBy ((==) `on` isSpace) "hey guys its me"
-- Frege
map packed $ groupBy ((==) `on` isSpace) "hey guys its me".toList
generalCategory
The names of the general categories differ a bit and resemble the names of the category constants in Java. For example, you get MATH_SYMBOL
instead of MathSymbol
.
(to be continued)
Home
News
Community
- Online Communities
- Frege Ecosystem
- Frege Day 2015
- Protocol
- Simon Peyton-Jones Transcript
- Talks
- Articles
- Books
- Courses
Documentation
- Getting Started
- Online REPL
- FAQ
- Language and API Reference
- Libraries
- Language Interoperability
- Calling Frege From Java (old)
- Calling Java From Frege
- Calling Frege From Java (new)
- Compiler Manpage
- Source Code Doc
- Contributing
- System Properties
- License
- IDE Support
- Eclipse
- Intellij
- VS Code and Language Server
- Haskell
- Differences
- GHC Options vs Frege
- Learn You A Haskell Adaptations
- Official Doc
Downloads